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