From 231e00e5f8a403275cc52b2386c181730058a6b4 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Thu, 18 Apr 2024 10:45:46 -0700 Subject: [PATCH 1/4] Refactor and migrate FlutterUndoManagerPlugin to ARC --- shell/platform/darwin/ios/BUILD.gn | 6 +- .../ios/framework/Source/FlutterEngine.mm | 2 - .../Source/FlutterUndoManagerDelegate.h | 8 ++ .../Source/FlutterUndoManagerPlugin.h | 4 - .../Source/FlutterUndoManagerPlugin.mm | 79 +++++++++---------- .../Source/FlutterUndoManagerPluginTest.mm | 55 ++++++------- 6 files changed, 73 insertions(+), 81 deletions(-) diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 86a601dab52f9..4135ff9ecab4b 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -84,6 +84,9 @@ source_set("flutter_framework_source_arc") { "framework/Source/FlutterTextureRegistryRelay.mm", "framework/Source/FlutterUIPressProxy.h", "framework/Source/FlutterUIPressProxy.mm", + "framework/Source/FlutterUndoManagerDelegate.h", + "framework/Source/FlutterUndoManagerPlugin.h", + "framework/Source/FlutterUndoManagerPlugin.mm", "framework/Source/KeyCodeMap.g.mm", "framework/Source/KeyCodeMap_Internal.h", "framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h", @@ -157,9 +160,6 @@ source_set("flutter_framework_source") { "framework/Source/FlutterPluginAppLifeCycleDelegate.mm", "framework/Source/FlutterSemanticsScrollView.h", "framework/Source/FlutterSemanticsScrollView.mm", - "framework/Source/FlutterUndoManagerDelegate.h", - "framework/Source/FlutterUndoManagerPlugin.h", - "framework/Source/FlutterUndoManagerPlugin.mm", "framework/Source/FlutterView.h", "framework/Source/FlutterView.mm", "framework/Source/FlutterViewController.mm", diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 8fa12218b8633..4cf5b6a103d50 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -430,7 +430,6 @@ - (void)setViewController:(FlutterViewController*)viewController { [self maybeSetupPlatformViewChannels]; [self updateDisplays]; _textInputPlugin.get().viewController = viewController; - _undoManagerPlugin.get().viewController = viewController; if (viewController) { __block FlutterEngine* blockSelf = self; @@ -465,7 +464,6 @@ - (void)setFlutterViewControllerWillDeallocObserver:(id)observer { - (void)notifyViewControllerDeallocated { [[self lifecycleChannel] sendMessage:@"AppLifecycleState.detached"]; _textInputPlugin.get().viewController = nil; - _undoManagerPlugin.get().viewController = nil; if (!_allowHeadlessExecution) { [self destroyContext]; } else if (_shell) { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerDelegate.h b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerDelegate.h index b81e7510d0114..d9552fe74239c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerDelegate.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerDelegate.h @@ -7,6 +7,8 @@ #import +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" + NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSInteger, FlutterUndoRedoDirection) { @@ -19,8 +21,14 @@ typedef NS_ENUM(NSInteger, FlutterUndoRedoDirection) { @class FlutterUndoManagerPlugin; @protocol FlutterUndoManagerDelegate + +@property(nonatomic, weak) UIResponder* viewController; + +- (FlutterTextInputPlugin*)textInputPlugin; + - (void)flutterUndoManagerPlugin:(FlutterUndoManagerPlugin*)undoManagerPlugin handleUndoWithDirection:(FlutterUndoRedoDirection)direction; + @end NS_ASSUME_NONNULL_END diff --git a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.h index c2dac66f87d47..3a3271f7b360d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.h @@ -7,15 +7,11 @@ #import -#import "flutter/fml/memory/weak_ptr.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" -#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerDelegate.h" @interface FlutterUndoManagerPlugin : NSObject -@property(nonatomic, assign) FlutterViewController* viewController; - - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm index b8f4b65f0f561..020394df85817 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm @@ -3,12 +3,8 @@ // found in the LICENSE file. #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.h" -#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" -#import -#import - -#include "flutter/fml/logging.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" #pragma mark - UndoManager channel method names. static NSString* const kSetUndoStateMethod = @"UndoManager.setUndoState"; @@ -17,15 +13,16 @@ static NSString* const kCanUndo = @"canUndo"; static NSString* const kCanRedo = @"canRedo"; -@implementation FlutterUndoManagerPlugin { - id _undoManagerDelegate; -} +@interface FlutterUndoManagerPlugin () +@property(nonatomic, weak, readonly) id undoManagerDelegate; +@end + +@implementation FlutterUndoManagerPlugin - (instancetype)initWithDelegate:(id)undoManagerDelegate { self = [super init]; if (self) { - // `_undoManagerDelegate` is a weak reference because it should retain FlutterUndoManagerPlugin. _undoManagerDelegate = undoManagerDelegate; } @@ -34,7 +31,6 @@ - (instancetype)initWithDelegate:(id)undoManagerDele - (void)dealloc { [self resetUndoManager]; - [super dealloc]; } - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { @@ -49,7 +45,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } - (NSUndoManager*)undoManager { - return _viewController.undoManager; + return self.undoManagerDelegate.viewController.undoManager; } - (void)resetUndoManager API_AVAILABLE(ios(9.0)) { @@ -57,37 +53,39 @@ - (void)resetUndoManager API_AVAILABLE(ios(9.0)) { } - (void)registerUndoWithDirection:(FlutterUndoRedoDirection)direction API_AVAILABLE(ios(9.0)) { - [[self undoManager] beginUndoGrouping]; - [[self undoManager] registerUndoWithTarget:self - handler:^(id target) { - // Register undo with opposite direction. - FlutterUndoRedoDirection newDirection = - (direction == FlutterUndoRedoDirectionRedo) - ? FlutterUndoRedoDirectionUndo - : FlutterUndoRedoDirectionRedo; - [target registerUndoWithDirection:newDirection]; - // Invoke method on delegate. - [_undoManagerDelegate flutterUndoManagerPlugin:self - handleUndoWithDirection:direction]; - }]; - [[self undoManager] endUndoGrouping]; + NSUndoManager* undoManager = [self undoManager]; + [undoManager beginUndoGrouping]; + [undoManager registerUndoWithTarget:self + handler:^(FlutterUndoManagerPlugin* target) { + // Register undo with opposite direction. + FlutterUndoRedoDirection newDirection = + (direction == FlutterUndoRedoDirectionRedo) + ? FlutterUndoRedoDirectionUndo + : FlutterUndoRedoDirectionRedo; + [target registerUndoWithDirection:newDirection]; + // Invoke method on delegate. + [target.undoManagerDelegate flutterUndoManagerPlugin:target + handleUndoWithDirection:direction]; + }]; + [undoManager endUndoGrouping]; } - (void)registerRedo API_AVAILABLE(ios(9.0)) { - [[self undoManager] beginUndoGrouping]; - [[self undoManager] - registerUndoWithTarget:self - handler:^(id target) { - // Register undo with opposite direction. - [target registerUndoWithDirection:FlutterUndoRedoDirectionRedo]; - }]; - [[self undoManager] endUndoGrouping]; - [[self undoManager] undo]; + NSUndoManager* undoManager = [self undoManager]; + [undoManager beginUndoGrouping]; + [undoManager registerUndoWithTarget:self + handler:^(id target) { + // Register undo with opposite direction. + [target registerUndoWithDirection:FlutterUndoRedoDirectionRedo]; + }]; + [undoManager endUndoGrouping]; + [undoManager undo]; } - (void)setUndoState:(NSDictionary*)dictionary API_AVAILABLE(ios(9.0)) { - BOOL groupsByEvent = [self undoManager].groupsByEvent; - [self undoManager].groupsByEvent = NO; + NSUndoManager* undoManager = [self undoManager]; + BOOL groupsByEvent = undoManager.groupsByEvent; + undoManager.groupsByEvent = NO; BOOL canUndo = [dictionary[kCanUndo] boolValue]; BOOL canRedo = [dictionary[kCanRedo] boolValue]; @@ -99,16 +97,15 @@ - (void)setUndoState:(NSDictionary*)dictionary API_AVAILABLE(ios(9.0)) { if (canRedo) { [self registerRedo]; } - - if (_viewController.engine.textInputPlugin.textInputView != nil) { + UIView* textInputView = [self.undoManagerDelegate.textInputPlugin textInputView]; + if (textInputView != nil) { // This is needed to notify the iPadOS keyboard that it needs to update the // state of the UIBarButtons. Otherwise, the state changes to NSUndoManager // will not show up until the next keystroke (or other trigger). - UITextInputAssistantItem* assistantItem = - _viewController.engine.textInputPlugin.textInputView.inputAssistantItem; + UITextInputAssistantItem* assistantItem = textInputView.inputAssistantItem; assistantItem.leadingBarButtonGroups = assistantItem.leadingBarButtonGroups; } - [self undoManager].groupsByEvent = groupsByEvent; + undoManager.groupsByEvent = groupsByEvent; } @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPluginTest.mm index c9edad986370e..f267e8a0166d9 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPluginTest.mm @@ -8,51 +8,48 @@ #import #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; +@interface FlutterUndoManagerDelegateForTest : NSObject +@property(nonatomic, weak) UIResponder* viewController; +@property(nonatomic) FlutterTextInputPlugin* textInputPlugin; @end -@interface FlutterUndoManagerPluginForTest : FlutterUndoManagerPlugin -@property(nonatomic, assign) NSUndoManager* undoManager; -@end +@implementation FlutterUndoManagerDelegateForTest -@implementation FlutterUndoManagerPluginForTest { +- (void)flutterUndoManagerPlugin:(FlutterUndoManagerPlugin*)undoManagerPlugin + handleUndoWithDirection:(FlutterUndoRedoDirection)direction { } @end @interface FlutterUndoManagerPluginTest : XCTestCase -@property(nonatomic, strong) id engine; -@property(nonatomic, strong) FlutterUndoManagerPluginForTest* undoManagerPlugin; -@property(nonatomic, strong) FlutterViewController* viewController; -@property(nonatomic, strong) NSUndoManager* undoManager; +@property(nonatomic) id undoManagerDelegate; +@property(nonatomic) FlutterUndoManagerPlugin* undoManagerPlugin; +@property(nonatomic) UIResponder* viewController; +@property(nonatomic) NSUndoManager* undoManager; @end -@implementation FlutterUndoManagerPluginTest { -} +@implementation FlutterUndoManagerPluginTest - (void)setUp { [super setUp]; - self.engine = OCMClassMock([FlutterEngine class]); - - self.undoManagerPlugin = [[FlutterUndoManagerPluginForTest alloc] initWithDelegate:self.engine]; + self.undoManagerDelegate = OCMClassMock([FlutterUndoManagerDelegateForTest class]); - self.viewController = [[FlutterViewController alloc] init]; - self.undoManagerPlugin.viewController = self.viewController; + self.undoManagerPlugin = + [[FlutterUndoManagerPlugin alloc] initWithDelegate:self.undoManagerDelegate]; self.undoManager = OCMClassMock([NSUndoManager class]); - self.undoManagerPlugin.undoManager = self.undoManager; + + self.viewController = OCMClassMock([UIResponder class]); + OCMStub([self.viewController undoManager]).andReturn(self.undoManager); + OCMStub([self.undoManagerDelegate viewController]).andReturn(self.viewController); } - (void)tearDown { [self.undoManager removeAllActionsWithTarget:self.undoManagerPlugin]; - self.engine = nil; + self.undoManagerDelegate = nil; self.viewController = nil; self.undoManager = nil; [super tearDown]; @@ -75,14 +72,14 @@ - (void)testSetUndoState { removeAllActionsCount++; }); __block int delegateUndoCount = 0; - OCMStub([self.engine flutterUndoManagerPlugin:[OCMArg any] - handleUndoWithDirection:FlutterUndoRedoDirectionUndo]) + OCMStub([self.undoManagerDelegate flutterUndoManagerPlugin:[OCMArg any] + handleUndoWithDirection:FlutterUndoRedoDirectionUndo]) .andDo(^(NSInvocation* invocation) { delegateUndoCount++; }); __block int delegateRedoCount = 0; - OCMStub([self.engine flutterUndoManagerPlugin:[OCMArg any] - handleUndoWithDirection:FlutterUndoRedoDirectionRedo]) + OCMStub([self.undoManagerDelegate flutterUndoManagerPlugin:[OCMArg any] + handleUndoWithDirection:FlutterUndoRedoDirectionRedo]) .andDo(^(NSInvocation* invocation) { delegateRedoCount++; }); @@ -143,14 +140,10 @@ - (void)testSetUndoState { - (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([self.undoManagerDelegate textInputPlugin]).andReturn(textInputPlugin); OCMStub([textInputPlugin textInputView]).andReturn(textInputView); FlutterMethodCall* setUndoStateCall = From 1dad11d2dd5bf74b27a57773174a6714cada53ba Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Fri, 19 Apr 2024 11:52:11 -0700 Subject: [PATCH 2/4] Rewrite delegate and tests --- .../ios/framework/Source/FlutterEngine.mm | 11 +- .../Source/FlutterUndoManagerDelegate.h | 28 +++-- .../Source/FlutterUndoManagerPlugin.mm | 24 ++-- .../Source/FlutterUndoManagerPluginTest.mm | 104 ++++++++++-------- 4 files changed, 97 insertions(+), 70 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 4cf5b6a103d50..f0fa9c8be5688 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -1187,12 +1187,19 @@ - (void)flutterTextInputView:(FlutterTextInputView*)textInputView #pragma mark - Undo Manager Delegate -- (void)flutterUndoManagerPlugin:(FlutterUndoManagerPlugin*)undoManagerPlugin - handleUndoWithDirection:(FlutterUndoRedoDirection)direction { +- (void)handleUndoWithDirection:(FlutterUndoRedoDirection)direction { NSString* action = (direction == FlutterUndoRedoDirectionUndo) ? @"undo" : @"redo"; [_undoManagerChannel.get() invokeMethod:@"UndoManagerClient.handleUndo" arguments:@[ action ]]; } +- (UIView*)activeTextInputView { + return [[self textInputPlugin] textInputView]; +} + +- (NSUndoManager*)undoManager { + return self.viewController.undoManager; +} + #pragma mark - Screenshot Delegate - (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type diff --git a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerDelegate.h b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerDelegate.h index d9552fe74239c..d0fe0b3f7f2e8 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerDelegate.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerDelegate.h @@ -18,16 +18,28 @@ typedef NS_ENUM(NSInteger, FlutterUndoRedoDirection) { // NOLINTEND(readability-identifier-naming) }; -@class FlutterUndoManagerPlugin; - +/** + * Protocol for undo manager changes from the `FlutterUndoManagerPlugin`, typically a + * `FlutterEngine`. + */ @protocol FlutterUndoManagerDelegate -@property(nonatomic, weak) UIResponder* viewController; - -- (FlutterTextInputPlugin*)textInputPlugin; - -- (void)flutterUndoManagerPlugin:(FlutterUndoManagerPlugin*)undoManagerPlugin - handleUndoWithDirection:(FlutterUndoRedoDirection)direction; +/** + * The `NSUndoManager` that should be managed by the `FlutterUndoManagerPlugin`. + * When the delegate is `FlutterEngine` this will be the `FlutterViewController`'s undo manager. + */ +@property(nonatomic, readonly, nullable) NSUndoManager* undoManager; + +/** + * Used to notify the active view when undo manager state (can redo/can undo) + * changes, in order to force keyboards to update undo/redo buttons. + */ +@property(nonatomic, readonly, nullable) UIView* activeTextInputView; + +/** + * Pass changes to the framework through the undo manager channel. + */ +- (void)handleUndoWithDirection:(FlutterUndoRedoDirection)direction; @end NS_ASSUME_NONNULL_END diff --git a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm index 020394df85817..a73adc75bfbb3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm @@ -15,6 +15,7 @@ @interface FlutterUndoManagerPlugin () @property(nonatomic, weak, readonly) id undoManagerDelegate; +@property(nonatomic, readonly) NSUndoManager* undoManager; @end @implementation FlutterUndoManagerPlugin @@ -30,7 +31,7 @@ - (instancetype)initWithDelegate:(id)undoManagerDele } - (void)dealloc { - [self resetUndoManager]; + [_undoManagerDelegate.undoManager removeAllActionsWithTarget:self]; } - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { @@ -45,15 +46,15 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } - (NSUndoManager*)undoManager { - return self.undoManagerDelegate.viewController.undoManager; + return self.undoManagerDelegate.undoManager; } -- (void)resetUndoManager API_AVAILABLE(ios(9.0)) { - [[self undoManager] removeAllActionsWithTarget:self]; +- (void)resetUndoManager { + [self.undoManager removeAllActionsWithTarget:self]; } -- (void)registerUndoWithDirection:(FlutterUndoRedoDirection)direction API_AVAILABLE(ios(9.0)) { - NSUndoManager* undoManager = [self undoManager]; +- (void)registerUndoWithDirection:(FlutterUndoRedoDirection)direction { + NSUndoManager* undoManager = self.undoManager; [undoManager beginUndoGrouping]; [undoManager registerUndoWithTarget:self handler:^(FlutterUndoManagerPlugin* target) { @@ -64,14 +65,13 @@ - (void)registerUndoWithDirection:(FlutterUndoRedoDirection)direction API_AVAILA : FlutterUndoRedoDirectionRedo; [target registerUndoWithDirection:newDirection]; // Invoke method on delegate. - [target.undoManagerDelegate flutterUndoManagerPlugin:target - handleUndoWithDirection:direction]; + [target.undoManagerDelegate handleUndoWithDirection:direction]; }]; [undoManager endUndoGrouping]; } -- (void)registerRedo API_AVAILABLE(ios(9.0)) { - NSUndoManager* undoManager = [self undoManager]; +- (void)registerRedo { + NSUndoManager* undoManager = self.undoManager; [undoManager beginUndoGrouping]; [undoManager registerUndoWithTarget:self handler:^(id target) { @@ -82,7 +82,7 @@ - (void)registerRedo API_AVAILABLE(ios(9.0)) { [undoManager undo]; } -- (void)setUndoState:(NSDictionary*)dictionary API_AVAILABLE(ios(9.0)) { +- (void)setUndoState:(NSDictionary*)dictionary { NSUndoManager* undoManager = [self undoManager]; BOOL groupsByEvent = undoManager.groupsByEvent; undoManager.groupsByEvent = NO; @@ -97,7 +97,7 @@ - (void)setUndoState:(NSDictionary*)dictionary API_AVAILABLE(ios(9.0)) { if (canRedo) { [self registerRedo]; } - UIView* textInputView = [self.undoManagerDelegate.textInputPlugin textInputView]; + UIView* textInputView = self.undoManagerDelegate.activeTextInputView; if (textInputView != nil) { // This is needed to notify the iPadOS keyboard that it needs to update the // state of the UIBarButtons. Otherwise, the state changes to NSUndoManager diff --git a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPluginTest.mm index f267e8a0166d9..62dcb8089a237 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPluginTest.mm @@ -8,26 +8,60 @@ #import #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" -#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" FLUTTER_ASSERT_ARC -@interface FlutterUndoManagerDelegateForTest : NSObject -@property(nonatomic, weak) UIResponder* viewController; -@property(nonatomic) FlutterTextInputPlugin* textInputPlugin; +/// OCMock does not allow mocking both class and protocol. Use this to mock the methods used on +/// `UIView*` in the plugin. +@interface TextInputViewTest : NSObject + +@property(nonatomic, weak) id inputDelegate; +@property(nonatomic, readonly) UITextInputAssistantItem* inputAssistantItem; + +@end + +@implementation TextInputViewTest @end -@implementation FlutterUndoManagerDelegateForTest +@interface FakeFlutterUndoManagerDelegate : NSObject -- (void)flutterUndoManagerPlugin:(FlutterUndoManagerPlugin*)undoManagerPlugin - handleUndoWithDirection:(FlutterUndoRedoDirection)direction { +@property(readonly) NSUInteger undoCount; +@property(readonly) NSUInteger redoCount; + +- (instancetype)initWithUndoManager:(NSUndoManager*)undoManager + activeTextInputView:(TextInputViewTest*)activeTextInputView; + +@end + +@implementation FakeFlutterUndoManagerDelegate + +@synthesize undoManager = _undoManager; +@synthesize activeTextInputView = _activeTextInputView; + +- (instancetype)initWithUndoManager:(NSUndoManager*)undoManager + activeTextInputView:(UIView*)activeTextInputView { + self = [super init]; + if (self) { + _undoManager = undoManager; + _activeTextInputView = activeTextInputView; + } + return self; } + +- (void)handleUndoWithDirection:(FlutterUndoRedoDirection)direction { + if (direction == FlutterUndoRedoDirectionUndo) { + _undoCount++; + } else { + _redoCount++; + } +} + @end @interface FlutterUndoManagerPluginTest : XCTestCase -@property(nonatomic) id undoManagerDelegate; +@property(nonatomic) FakeFlutterUndoManagerDelegate* undoManagerDelegate; @property(nonatomic) FlutterUndoManagerPlugin* undoManagerPlugin; -@property(nonatomic) UIResponder* viewController; +@property(nonatomic) TextInputViewTest* activeTextInputView; @property(nonatomic) NSUndoManager* undoManager; @end @@ -35,24 +69,16 @@ @implementation FlutterUndoManagerPluginTest - (void)setUp { [super setUp]; - self.undoManagerDelegate = OCMClassMock([FlutterUndoManagerDelegateForTest class]); - - self.undoManagerPlugin = - [[FlutterUndoManagerPlugin alloc] initWithDelegate:self.undoManagerDelegate]; self.undoManager = OCMClassMock([NSUndoManager class]); + self.activeTextInputView = OCMClassMock([TextInputViewTest class]); - self.viewController = OCMClassMock([UIResponder class]); - OCMStub([self.viewController undoManager]).andReturn(self.undoManager); - OCMStub([self.undoManagerDelegate viewController]).andReturn(self.viewController); -} + self.undoManagerDelegate = + [[FakeFlutterUndoManagerDelegate alloc] initWithUndoManager:self.undoManager + activeTextInputView:self.activeTextInputView]; -- (void)tearDown { - [self.undoManager removeAllActionsWithTarget:self.undoManagerPlugin]; - self.undoManagerDelegate = nil; - self.viewController = nil; - self.undoManager = nil; - [super tearDown]; + self.undoManagerPlugin = + [[FlutterUndoManagerPlugin alloc] initWithDelegate:self.undoManagerDelegate]; } - (void)testSetUndoState { @@ -71,18 +97,6 @@ - (void)testSetUndoState { .andDo(^(NSInvocation* invocation) { removeAllActionsCount++; }); - __block int delegateUndoCount = 0; - OCMStub([self.undoManagerDelegate flutterUndoManagerPlugin:[OCMArg any] - handleUndoWithDirection:FlutterUndoRedoDirectionUndo]) - .andDo(^(NSInvocation* invocation) { - delegateUndoCount++; - }); - __block int delegateRedoCount = 0; - OCMStub([self.undoManagerDelegate flutterUndoManagerPlugin:[OCMArg any] - handleUndoWithDirection:FlutterUndoRedoDirectionRedo]) - .andDo(^(NSInvocation* invocation) { - delegateRedoCount++; - }); __block int undoCount = 0; OCMStub([self.undoManager undo]).andDo(^(NSInvocation* invocation) { undoCount++; @@ -111,14 +125,14 @@ - (void)testSetUndoState { // Invoking the undo handler will invoke the handleUndo delegate method with "undo". undoHandler(self.undoManagerPlugin); - XCTAssertEqual(1, delegateUndoCount); - XCTAssertEqual(0, delegateRedoCount); + XCTAssertEqual(1UL, self.undoManagerDelegate.undoCount); + XCTAssertEqual(0UL, self.undoManagerDelegate.redoCount); 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(1UL, self.undoManagerDelegate.undoCount); + XCTAssertEqual(1UL, self.undoManagerDelegate.redoCount); XCTAssertEqual(3, registerUndoCount); // If canRedo is true, an undo will be registered and undo will be called. @@ -134,18 +148,12 @@ - (void)testSetUndoState { // Invoking the redo handler will invoke the handleUndo delegate method with "redo". undoHandler(self.undoManagerPlugin); - XCTAssertEqual(1, delegateUndoCount); - XCTAssertEqual(2, delegateRedoCount); + XCTAssertEqual(1UL, self.undoManagerDelegate.undoCount); + XCTAssertEqual(2UL, self.undoManagerDelegate.redoCount); } - (void)testSetUndoStateDoesInteractWithInputDelegate { // Regression test for https://github.com/flutter/flutter/issues/133424 - FlutterTextInputPlugin* textInputPlugin = OCMClassMock([FlutterTextInputPlugin class]); - FlutterTextInputView* textInputView = OCMClassMock([FlutterTextInputView class]); - - OCMStub([self.undoManagerDelegate textInputPlugin]).andReturn(textInputPlugin); - OCMStub([textInputPlugin textInputView]).andReturn(textInputView); - FlutterMethodCall* setUndoStateCall = [FlutterMethodCall methodCallWithMethodName:@"UndoManager.setUndoState" arguments:@{@"canUndo" : @NO, @"canRedo" : @NO}]; @@ -153,7 +161,7 @@ - (void)testSetUndoStateDoesInteractWithInputDelegate { result:^(id _Nullable result){ }]; - OCMVerify(never(), [textInputView inputDelegate]); + OCMVerify(never(), [self.activeTextInputView inputDelegate]); } @end From f491d7f952500e0407aa5bc2a5c7b2eeb542de7d Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Fri, 19 Apr 2024 11:55:48 -0700 Subject: [PATCH 3/4] More import cleanup --- .../darwin/ios/framework/Source/FlutterUndoManagerDelegate.h | 2 -- .../darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm | 2 -- 2 files changed, 4 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerDelegate.h b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerDelegate.h index d0fe0b3f7f2e8..bcfd609dd185c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerDelegate.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerDelegate.h @@ -7,8 +7,6 @@ #import -#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" - NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSInteger, FlutterUndoRedoDirection) { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm index a73adc75bfbb3..de06811a9eec1 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm @@ -4,8 +4,6 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.h" -#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" - #pragma mark - UndoManager channel method names. static NSString* const kSetUndoStateMethod = @"UndoManager.setUndoState"; From 98725b46c109249a8ad2eb996620a8645747a697 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Fri, 19 Apr 2024 11:57:47 -0700 Subject: [PATCH 4/4] Remove undoManager --- .../framework/Source/FlutterUndoManagerPlugin.mm | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm index de06811a9eec1..6eeb106c16ec3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterUndoManagerPlugin.mm @@ -13,7 +13,6 @@ @interface FlutterUndoManagerPlugin () @property(nonatomic, weak, readonly) id undoManagerDelegate; -@property(nonatomic, readonly) NSUndoManager* undoManager; @end @implementation FlutterUndoManagerPlugin @@ -43,16 +42,12 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } } -- (NSUndoManager*)undoManager { - return self.undoManagerDelegate.undoManager; -} - - (void)resetUndoManager { - [self.undoManager removeAllActionsWithTarget:self]; + [self.undoManagerDelegate.undoManager removeAllActionsWithTarget:self]; } - (void)registerUndoWithDirection:(FlutterUndoRedoDirection)direction { - NSUndoManager* undoManager = self.undoManager; + NSUndoManager* undoManager = self.undoManagerDelegate.undoManager; [undoManager beginUndoGrouping]; [undoManager registerUndoWithTarget:self handler:^(FlutterUndoManagerPlugin* target) { @@ -69,7 +64,7 @@ - (void)registerUndoWithDirection:(FlutterUndoRedoDirection)direction { } - (void)registerRedo { - NSUndoManager* undoManager = self.undoManager; + NSUndoManager* undoManager = self.undoManagerDelegate.undoManager; [undoManager beginUndoGrouping]; [undoManager registerUndoWithTarget:self handler:^(id target) { @@ -81,7 +76,7 @@ - (void)registerRedo { } - (void)setUndoState:(NSDictionary*)dictionary { - NSUndoManager* undoManager = [self undoManager]; + NSUndoManager* undoManager = self.undoManagerDelegate.undoManager; BOOL groupsByEvent = undoManager.groupsByEvent; undoManager.groupsByEvent = NO; BOOL canUndo = [dictionary[kCanUndo] boolValue];