| Flutter iOS Embedder |

FlutterUndoManagerPluginTest.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.h"
6
7 #import <OCMock/OCMock.h>
8 #import <XCTest/XCTest.h>
9
10 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
12
13 FLUTTER_ASSERT_ARC
14
15 @interface FakeFlutterUndoManagerDelegate : NSObject <FlutterUndoManagerDelegate>
16
17 @property(readonly) NSUInteger undoCount;
18 @property(readonly) NSUInteger redoCount;
19 @property(nonatomic, nullable) NSUndoManager* undoManager;
20 @property(nonatomic, nullable) UIView<UITextInput>* activeTextInputView;
21
22 - (instancetype)initWithUndoManager:(NSUndoManager*)undoManager
23 activeTextInputView:(UIView<UITextInput>*)activeTextInputView;
24
25 @end
26
27 @implementation FakeFlutterUndoManagerDelegate
28
29 - (instancetype)initWithUndoManager:(NSUndoManager*)undoManager
30 activeTextInputView:(UIView<UITextInput>*)activeTextInputView {
31 self = [super init];
32 if (self) {
33 _undoManager = undoManager;
34 _activeTextInputView = activeTextInputView;
35 }
36 return self;
37 }
38
39 - (void)handleUndoWithDirection:(FlutterUndoRedoDirection)direction {
40 if (direction == FlutterUndoRedoDirectionUndo) {
41 _undoCount++;
42 } else {
43 _redoCount++;
44 }
45 }
46
47 @end
48
49 @interface FlutterUndoManagerPluginTest : XCTestCase
50 @property(nonatomic) FakeFlutterUndoManagerDelegate* undoManagerDelegate;
51 @property(nonatomic) FlutterUndoManagerPlugin* undoManagerPlugin;
52 @property(nonatomic) UIView<UITextInput>* activeTextInputView;
53 @property(nonatomic) NSUndoManager* undoManager;
54 @end
55
56 @implementation FlutterUndoManagerPluginTest
57
58 - (void)setUp {
59 [super setUp];
60
61 self.undoManager = OCMClassMock([NSUndoManager class]);
62 self.activeTextInputView = OCMClassMock([FlutterTextInputView class]);
63
64 self.undoManagerDelegate =
65 [[FakeFlutterUndoManagerDelegate alloc] initWithUndoManager:self.undoManager
66 activeTextInputView:self.activeTextInputView];
67
68 self.undoManagerPlugin =
69 [[FlutterUndoManagerPlugin alloc] initWithDelegate:self.undoManagerDelegate];
70 }
71
72 - (void)testSetUndoState {
73 __block int registerUndoCount = 0;
74 __block void (^undoHandler)(id target);
75 OCMStub([self.undoManager registerUndoWithTarget:self.undoManagerPlugin handler:[OCMArg any]])
76 .andDo(^(NSInvocation* invocation) {
77 registerUndoCount++;
78 __weak void (^handler)(id target);
79 [invocation retainArguments];
80 [invocation getArgument:&handler atIndex:3];
81 undoHandler = handler;
82 });
83 __block int removeAllActionsCount = 0;
84 OCMStub([self.undoManager removeAllActionsWithTarget:self.undoManagerPlugin])
85 .andDo(^(NSInvocation* invocation) {
86 removeAllActionsCount++;
87 });
88 __block int undoCount = 0;
89 OCMStub([self.undoManager undo]).andDo(^(NSInvocation* invocation) {
90 undoCount++;
91 undoHandler(self.undoManagerPlugin);
92 });
93
94 // If canUndo and canRedo are false, only removeAllActionsWithTarget is called.
95 FlutterMethodCall* setUndoStateCall =
96 [FlutterMethodCall methodCallWithMethodName:@"UndoManager.setUndoState"
97 arguments:@{@"canUndo" : @NO, @"canRedo" : @NO}];
98 [self.undoManagerPlugin handleMethodCall:setUndoStateCall
99 result:^(id _Nullable result){
100 }];
101 XCTAssertEqual(1, removeAllActionsCount);
102 XCTAssertEqual(0, registerUndoCount);
103
104 // If canUndo is true, an undo will be registered.
105 setUndoStateCall =
106 [FlutterMethodCall methodCallWithMethodName:@"UndoManager.setUndoState"
107 arguments:@{@"canUndo" : @YES, @"canRedo" : @NO}];
108 [self.undoManagerPlugin handleMethodCall:setUndoStateCall
109 result:^(id _Nullable result){
110 }];
111 XCTAssertEqual(2, removeAllActionsCount);
112 XCTAssertEqual(1, registerUndoCount);
113
114 // Invoking the undo handler will invoke the handleUndo delegate method with "undo".
115 undoHandler(self.undoManagerPlugin);
116 XCTAssertEqual(1UL, self.undoManagerDelegate.undoCount);
117 XCTAssertEqual(0UL, self.undoManagerDelegate.redoCount);
118 XCTAssertEqual(2, registerUndoCount);
119
120 // Invoking the redo handler will invoke the handleUndo delegate method with "redo".
121 undoHandler(self.undoManagerPlugin);
122 XCTAssertEqual(1UL, self.undoManagerDelegate.undoCount);
123 XCTAssertEqual(1UL, self.undoManagerDelegate.redoCount);
124 XCTAssertEqual(3, registerUndoCount);
125
126 // If canRedo is true, an undo will be registered and undo will be called.
127 setUndoStateCall =
128 [FlutterMethodCall methodCallWithMethodName:@"UndoManager.setUndoState"
129 arguments:@{@"canUndo" : @NO, @"canRedo" : @YES}];
130 [self.undoManagerPlugin handleMethodCall:setUndoStateCall
131 result:^(id _Nullable result){
132 }];
133 XCTAssertEqual(3, removeAllActionsCount);
134 XCTAssertEqual(5, registerUndoCount);
135 XCTAssertEqual(1, undoCount);
136
137 // Invoking the redo handler will invoke the handleUndo delegate method with "redo".
138 undoHandler(self.undoManagerPlugin);
139 XCTAssertEqual(1UL, self.undoManagerDelegate.undoCount);
140 XCTAssertEqual(2UL, self.undoManagerDelegate.redoCount);
141 }
142
143 - (void)testSetUndoStateDoesInteractWithInputDelegate {
144 // Regression test for https://github.com/flutter/flutter/issues/133424
145 FlutterMethodCall* setUndoStateCall =
146 [FlutterMethodCall methodCallWithMethodName:@"UndoManager.setUndoState"
147 arguments:@{@"canUndo" : @NO, @"canRedo" : @NO}];
148 [self.undoManagerPlugin handleMethodCall:setUndoStateCall
149 result:^(id _Nullable result){
150 }];
151
152 OCMVerify(never(), [self.activeTextInputView inputDelegate]);
153 }
154
155 - (void)testDeallocRemovesAllUndoManagerActions {
156 __weak FlutterUndoManagerPlugin* weakUndoManagerPlugin;
157 // Use a real undo manager.
158 NSUndoManager* undoManager = [[NSUndoManager alloc] init];
159 @autoreleasepool {
160 id activeTextInputView = OCMClassMock([FlutterTextInputView class]);
161
162 FakeFlutterUndoManagerDelegate* undoManagerDelegate =
163 [[FakeFlutterUndoManagerDelegate alloc] initWithUndoManager:undoManager
164 activeTextInputView:activeTextInputView];
165
166 FlutterUndoManagerPlugin* undoManagerPlugin =
167 [[FlutterUndoManagerPlugin alloc] initWithDelegate:undoManagerDelegate];
168 weakUndoManagerPlugin = undoManagerPlugin;
169
170 FlutterMethodCall* setUndoStateCall =
171 [FlutterMethodCall methodCallWithMethodName:@"UndoManager.setUndoState"
172 arguments:@{@"canUndo" : @YES, @"canRedo" : @YES}];
173 [undoManagerPlugin handleMethodCall:setUndoStateCall
174 result:^(id _Nullable result){
175 }];
176 XCTAssertTrue(undoManager.canUndo);
177 XCTAssertTrue(undoManager.canRedo);
178 // Fake out the undoManager being nil, which happens when the FlutterViewController deallocs and
179 // the undo manager can't be fetched from the FlutterEngine delegate.
180 undoManagerDelegate.undoManager = nil;
181 }
182 XCTAssertNil(weakUndoManagerPlugin);
183 // Regression test for https://github.com/flutter/flutter/issues/150408.
184 // Undo manager undo and redo stack should be empty after the plugin deallocs.
185 XCTAssertFalse(undoManager.canUndo);
186 XCTAssertFalse(undoManager.canRedo);
187 }
188
189 @end
FlutterMacros.h
FlutterTextInputPlugin.h
inputDelegate
id< UITextInputDelegate > inputDelegate
Definition: FlutterTextInputPlugin.h:141
FlutterUndoManagerPlugin.h
FlutterTextInputView
Definition: FlutterTextInputPlugin.mm:811
FakeFlutterUndoManagerDelegate
Definition: FlutterUndoManagerPluginTest.mm:15
FakeFlutterUndoManagerDelegate::redoCount
NSUInteger redoCount
Definition: FlutterUndoManagerPluginTest.mm:18
FakeFlutterUndoManagerDelegate::activeTextInputView
UIView< UITextInput > * activeTextInputView
Definition: FlutterUndoManagerPluginTest.mm:20
FakeFlutterUndoManagerDelegate::undoCount
NSUInteger undoCount
Definition: FlutterUndoManagerPluginTest.mm:17
FakeFlutterUndoManagerDelegate::undoManager
NSUndoManager * undoManager
Definition: FlutterUndoManagerPluginTest.mm:19
FlutterMethodCall
Definition: FlutterCodecs.h:221
+[FlutterMethodCall methodCallWithMethodName:arguments:]
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
FlutterUndoManagerPlugin
Definition: FlutterUndoManagerPlugin.h:14
-[FlutterUndoManagerPlugin handleMethodCall:result:]
void handleMethodCall:result:(FlutterMethodCall *call,[result] FlutterResult result)
Definition: FlutterUndoManagerPlugin.mm:38
FlutterUndoManagerPluginTest
Definition: FlutterUndoManagerPluginTest.mm:50
FlutterUndoManagerPluginTest::undoManagerDelegate
FakeFlutterUndoManagerDelegate * undoManagerDelegate
Definition: FlutterUndoManagerPluginTest.mm:50
FlutterUndoManagerPluginTest::undoManagerPlugin
FlutterUndoManagerPlugin * undoManagerPlugin
Definition: FlutterUndoManagerPluginTest.mm:51
FlutterUndoManagerPluginTest::undoManager
NSUndoManager * undoManager
Definition: FlutterUndoManagerPluginTest.mm:53
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FlutterUndoManagerDelegate-p
Definition: FlutterUndoManagerDelegate.h:23