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 
6 
7 #import <OCMock/OCMock.h>
8 #import <XCTest/XCTest.h>
9 
11 
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 
27 
28 @property(readonly) NSUInteger undoCount;
29 @property(readonly) NSUInteger redoCount;
30 
31 - (instancetype)initWithUndoManager:(NSUndoManager*)undoManager
32  activeTextInputView:(TextInputViewTest*)activeTextInputView;
33 
34 @end
35 
36 @implementation FakeFlutterUndoManagerDelegate
37 
38 @synthesize undoManager = _undoManager;
39 @synthesize activeTextInputView = _activeTextInputView;
40 
41 - (instancetype)initWithUndoManager:(NSUndoManager*)undoManager
42  activeTextInputView:(UIView<UITextInput>*)activeTextInputView {
43  self = [super init];
44  if (self) {
45  _undoManager = undoManager;
46  _activeTextInputView = activeTextInputView;
47  }
48  return self;
49 }
50 
51 - (void)handleUndoWithDirection:(FlutterUndoRedoDirection)direction {
52  if (direction == FlutterUndoRedoDirectionUndo) {
53  _undoCount++;
54  } else {
55  _redoCount++;
56  }
57 }
58 
59 @end
60 
61 @interface FlutterUndoManagerPluginTest : XCTestCase
62 @property(nonatomic) FakeFlutterUndoManagerDelegate* undoManagerDelegate;
63 @property(nonatomic) FlutterUndoManagerPlugin* undoManagerPlugin;
64 @property(nonatomic) TextInputViewTest* activeTextInputView;
65 @property(nonatomic) NSUndoManager* undoManager;
66 @end
67 
68 @implementation FlutterUndoManagerPluginTest
69 
70 - (void)setUp {
71  [super setUp];
72 
73  self.undoManager = OCMClassMock([NSUndoManager class]);
74  self.activeTextInputView = OCMClassMock([TextInputViewTest class]);
75 
76  self.undoManagerDelegate =
77  [[FakeFlutterUndoManagerDelegate alloc] initWithUndoManager:self.undoManager
78  activeTextInputView:self.activeTextInputView];
79 
80  self.undoManagerPlugin =
81  [[FlutterUndoManagerPlugin alloc] initWithDelegate:self.undoManagerDelegate];
82 }
83 
84 - (void)testSetUndoState {
85  __block int registerUndoCount = 0;
86  __block void (^undoHandler)(id target);
87  OCMStub([self.undoManager registerUndoWithTarget:self.undoManagerPlugin handler:[OCMArg any]])
88  .andDo(^(NSInvocation* invocation) {
89  registerUndoCount++;
90  __weak void (^handler)(id target);
91  [invocation retainArguments];
92  [invocation getArgument:&handler atIndex:3];
93  undoHandler = handler;
94  });
95  __block int removeAllActionsCount = 0;
96  OCMStub([self.undoManager removeAllActionsWithTarget:self.undoManagerPlugin])
97  .andDo(^(NSInvocation* invocation) {
98  removeAllActionsCount++;
99  });
100  __block int undoCount = 0;
101  OCMStub([self.undoManager undo]).andDo(^(NSInvocation* invocation) {
102  undoCount++;
103  undoHandler(self.undoManagerPlugin);
104  });
105 
106  // If canUndo and canRedo are false, only removeAllActionsWithTarget is called.
107  FlutterMethodCall* setUndoStateCall =
108  [FlutterMethodCall methodCallWithMethodName:@"UndoManager.setUndoState"
109  arguments:@{@"canUndo" : @NO, @"canRedo" : @NO}];
110  [self.undoManagerPlugin handleMethodCall:setUndoStateCall
111  result:^(id _Nullable result){
112  }];
113  XCTAssertEqual(1, removeAllActionsCount);
114  XCTAssertEqual(0, registerUndoCount);
115 
116  // If canUndo is true, an undo will be registered.
117  setUndoStateCall =
118  [FlutterMethodCall methodCallWithMethodName:@"UndoManager.setUndoState"
119  arguments:@{@"canUndo" : @YES, @"canRedo" : @NO}];
120  [self.undoManagerPlugin handleMethodCall:setUndoStateCall
121  result:^(id _Nullable result){
122  }];
123  XCTAssertEqual(2, removeAllActionsCount);
124  XCTAssertEqual(1, registerUndoCount);
125 
126  // Invoking the undo handler will invoke the handleUndo delegate method with "undo".
127  undoHandler(self.undoManagerPlugin);
128  XCTAssertEqual(1UL, self.undoManagerDelegate.undoCount);
129  XCTAssertEqual(0UL, self.undoManagerDelegate.redoCount);
130  XCTAssertEqual(2, registerUndoCount);
131 
132  // Invoking the redo handler will invoke the handleUndo delegate method with "redo".
133  undoHandler(self.undoManagerPlugin);
134  XCTAssertEqual(1UL, self.undoManagerDelegate.undoCount);
135  XCTAssertEqual(1UL, self.undoManagerDelegate.redoCount);
136  XCTAssertEqual(3, registerUndoCount);
137 
138  // If canRedo is true, an undo will be registered and undo will be called.
139  setUndoStateCall =
140  [FlutterMethodCall methodCallWithMethodName:@"UndoManager.setUndoState"
141  arguments:@{@"canUndo" : @NO, @"canRedo" : @YES}];
142  [self.undoManagerPlugin handleMethodCall:setUndoStateCall
143  result:^(id _Nullable result){
144  }];
145  XCTAssertEqual(3, removeAllActionsCount);
146  XCTAssertEqual(5, registerUndoCount);
147  XCTAssertEqual(1, undoCount);
148 
149  // Invoking the redo handler will invoke the handleUndo delegate method with "redo".
150  undoHandler(self.undoManagerPlugin);
151  XCTAssertEqual(1UL, self.undoManagerDelegate.undoCount);
152  XCTAssertEqual(2UL, self.undoManagerDelegate.redoCount);
153 }
154 
155 - (void)testSetUndoStateDoesInteractWithInputDelegate {
156  // Regression test for https://github.com/flutter/flutter/issues/133424
157  FlutterMethodCall* setUndoStateCall =
158  [FlutterMethodCall methodCallWithMethodName:@"UndoManager.setUndoState"
159  arguments:@{@"canUndo" : @NO, @"canRedo" : @NO}];
160  [self.undoManagerPlugin handleMethodCall:setUndoStateCall
161  result:^(id _Nullable result){
162  }];
163 
164  OCMVerify(never(), [self.activeTextInputView inputDelegate]);
165 }
166 
167 @end
+[FlutterMethodCall methodCallWithMethodName:arguments:]
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
FlutterUndoManagerPluginTest::undoManagerDelegate
FakeFlutterUndoManagerDelegate * undoManagerDelegate
Definition: FlutterUndoManagerPluginTest.mm:62
FlutterUndoManagerPlugin.h
FakeFlutterUndoManagerDelegate::redoCount
NSUInteger redoCount
Definition: FlutterUndoManagerPluginTest.mm:29
FlutterMacros.h
FlutterUndoManagerPluginTest::undoManagerPlugin
FlutterUndoManagerPlugin * undoManagerPlugin
Definition: FlutterUndoManagerPluginTest.mm:63
FlutterUndoManagerPluginTest::undoManager
NSUndoManager * undoManager
Definition: FlutterUndoManagerPluginTest.mm:65
FlutterUndoManagerDelegate-p::activeTextInputView
UIView< UITextInput > * activeTextInputView
Definition: FlutterUndoManagerDelegate.h:35
FakeFlutterUndoManagerDelegate::undoCount
NSUInteger undoCount
Definition: FlutterUndoManagerPluginTest.mm:28
FlutterMethodCall
Definition: FlutterCodecs.h:220
FlutterUndoManagerPluginTest
Definition: FlutterUndoManagerPluginTest.mm:61
FakeFlutterUndoManagerDelegate
Definition: FlutterUndoManagerPluginTest.mm:26
FlutterUndoManagerDelegate-p
Definition: FlutterUndoManagerDelegate.h:23
FlutterUndoManagerPlugin
Definition: FlutterUndoManagerPlugin.h:13
TextInputViewTest::inputAssistantItem
UITextInputAssistantItem * inputAssistantItem
Definition: FlutterUndoManagerPluginTest.mm:19
FlutterUndoManagerDelegate-p::undoManager
NSUndoManager * undoManager
Definition: FlutterUndoManagerDelegate.h:29
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:64