| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.h" |
| |
| #import <OCMock/OCMock.h> |
| #import <XCTest/XCTest.h> |
| |
| #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" |
| #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h" |
| #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" |
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" |
| |
| FLUTTER_ASSERT_ARC |
| |
| @interface FlutterEngine () |
| - (nonnull FlutterUndoManagerPlugin*)undoManagerPlugin; |
| - (nonnull FlutterTextInputPlugin*)textInputPlugin; |
| @end |
| |
| @interface FlutterUndoManagerPluginForTest : FlutterUndoManagerPlugin |
| @property(nonatomic, assign) NSUndoManager* undoManager; |
| @end |
| |
| @implementation FlutterUndoManagerPluginForTest { |
| } |
| @end |
| |
| @interface FlutterUndoManagerPluginTest : XCTestCase |
| @property(nonatomic, strong) id engine; |
| @property(nonatomic, strong) FlutterUndoManagerPluginForTest* undoManagerPlugin; |
| @property(nonatomic, strong) FlutterViewController* viewController; |
| @property(nonatomic, strong) NSUndoManager* undoManager; |
| @end |
| |
| @implementation FlutterUndoManagerPluginTest { |
| } |
| |
| - (void)setUp { |
| [super setUp]; |
| self.engine = OCMClassMock([FlutterEngine class]); |
| |
| self.undoManagerPlugin = [[FlutterUndoManagerPluginForTest alloc] initWithDelegate:self.engine]; |
| |
| self.viewController = [[FlutterViewController alloc] init]; |
| self.undoManagerPlugin.viewController = self.viewController; |
| |
| self.undoManager = OCMClassMock([NSUndoManager class]); |
| self.undoManagerPlugin.undoManager = self.undoManager; |
| } |
| |
| - (void)tearDown { |
| [self.undoManager removeAllActionsWithTarget:self.undoManagerPlugin]; |
| self.engine = nil; |
| self.viewController = nil; |
| self.undoManager = nil; |
| [super tearDown]; |
| } |
| |
| - (void)testSetUndoState { |
| __block int registerUndoCount = 0; |
| __block void (^undoHandler)(id target); |
| OCMStub([self.undoManager registerUndoWithTarget:self.undoManagerPlugin handler:[OCMArg any]]) |
| .andDo(^(NSInvocation* invocation) { |
| registerUndoCount++; |
| __weak void (^handler)(id target); |
| [invocation retainArguments]; |
| [invocation getArgument:&handler atIndex:3]; |
| undoHandler = handler; |
| }); |
| __block int removeAllActionsCount = 0; |
| OCMStub([self.undoManager removeAllActionsWithTarget:self.undoManagerPlugin]) |
| .andDo(^(NSInvocation* invocation) { |
| removeAllActionsCount++; |
| }); |
| __block int delegateUndoCount = 0; |
| OCMStub([self.engine flutterUndoManagerPlugin:[OCMArg any] |
| handleUndoWithDirection:FlutterUndoRedoDirectionUndo]) |
| .andDo(^(NSInvocation* invocation) { |
| delegateUndoCount++; |
| }); |
| __block int delegateRedoCount = 0; |
| OCMStub([self.engine flutterUndoManagerPlugin:[OCMArg any] |
| handleUndoWithDirection:FlutterUndoRedoDirectionRedo]) |
| .andDo(^(NSInvocation* invocation) { |
| delegateRedoCount++; |
| }); |
| __block int undoCount = 0; |
| OCMStub([self.undoManager undo]).andDo(^(NSInvocation* invocation) { |
| undoCount++; |
| undoHandler(self.undoManagerPlugin); |
| }); |
| |
| // If canUndo and canRedo are false, only removeAllActionsWithTarget is called. |
| FlutterMethodCall* setUndoStateCall = |
| [FlutterMethodCall methodCallWithMethodName:@"UndoManager.setUndoState" |
| arguments:@{@"canUndo" : @NO, @"canRedo" : @NO}]; |
| [self.undoManagerPlugin handleMethodCall:setUndoStateCall |
| result:^(id _Nullable result){ |
| }]; |
| XCTAssertEqual(1, removeAllActionsCount); |
| XCTAssertEqual(0, registerUndoCount); |
| |
| // If canUndo is true, an undo will be registered. |
| setUndoStateCall = |
| [FlutterMethodCall methodCallWithMethodName:@"UndoManager.setUndoState" |
| arguments:@{@"canUndo" : @YES, @"canRedo" : @NO}]; |
| [self.undoManagerPlugin handleMethodCall:setUndoStateCall |
| result:^(id _Nullable result){ |
| }]; |
| XCTAssertEqual(2, removeAllActionsCount); |
| XCTAssertEqual(1, registerUndoCount); |
| |
| // Invoking the undo handler will invoke the handleUndo delegate method with "undo". |
| undoHandler(self.undoManagerPlugin); |
| XCTAssertEqual(1, delegateUndoCount); |
| XCTAssertEqual(0, delegateRedoCount); |
| XCTAssertEqual(2, registerUndoCount); |
| |
| // Invoking the redo handler will invoke the handleUndo delegate method with "redo". |
| undoHandler(self.undoManagerPlugin); |
| XCTAssertEqual(1, delegateUndoCount); |
| XCTAssertEqual(1, delegateRedoCount); |
| XCTAssertEqual(3, registerUndoCount); |
| |
| // If canRedo is true, an undo will be registered and undo will be called. |
| setUndoStateCall = |
| [FlutterMethodCall methodCallWithMethodName:@"UndoManager.setUndoState" |
| arguments:@{@"canUndo" : @NO, @"canRedo" : @YES}]; |
| [self.undoManagerPlugin handleMethodCall:setUndoStateCall |
| result:^(id _Nullable result){ |
| }]; |
| XCTAssertEqual(3, removeAllActionsCount); |
| XCTAssertEqual(5, registerUndoCount); |
| XCTAssertEqual(1, undoCount); |
| |
| // Invoking the redo handler will invoke the handleUndo delegate method with "redo". |
| undoHandler(self.undoManagerPlugin); |
| XCTAssertEqual(1, delegateUndoCount); |
| XCTAssertEqual(2, delegateRedoCount); |
| } |
| |
| - (void)testSetUndoStateDoesInteractWithInputDelegate { |
| // Regression test for https://github.com/flutter/flutter/issues/133424 |
| FlutterViewController* viewController = OCMPartialMock(self.viewController); |
| self.undoManagerPlugin.viewController = self.viewController; |
| |
| FlutterTextInputPlugin* textInputPlugin = OCMClassMock([FlutterTextInputPlugin class]); |
| FlutterTextInputView* textInputView = OCMClassMock([FlutterTextInputView class]); |
| |
| OCMStub([viewController engine]).andReturn(self.engine); |
| OCMStub([self.engine textInputPlugin]).andReturn(textInputPlugin); |
| OCMStub([textInputPlugin textInputView]).andReturn(textInputView); |
| |
| FlutterMethodCall* setUndoStateCall = |
| [FlutterMethodCall methodCallWithMethodName:@"UndoManager.setUndoState" |
| arguments:@{@"canUndo" : @NO, @"canRedo" : @NO}]; |
| [self.undoManagerPlugin handleMethodCall:setUndoStateCall |
| result:^(id _Nullable result){ |
| }]; |
| |
| OCMVerify(never(), [textInputView inputDelegate]); |
| } |
| |
| @end |