Flutter Engine
 
Loading...
Searching...
No Matches
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
12
14
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
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
const gchar FlBinaryMessengerMessageHandler handler
uint32_t * target
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
void handleMethodCall:result:(FlutterMethodCall *call,[result] FlutterResult result)
id< UITextInputDelegate > inputDelegate