From 6457d0a729faba5332e45e166b7d1e7d77f8163f Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Thu, 20 Oct 2022 15:44:27 -0700 Subject: [PATCH 01/13] [quick_actions]migrate shortcut state manager, deprecate OCMock and use POP --- .../ios/Runner.xcodeproj/project.pbxproj | 42 +++- .../RunnerTests/FLTQuickActionsPluginTests.m | 210 ----------------- .../FLTShortcutStateManagerTests.m | 62 ----- .../RunnerTests/Mocks/MockMethodChannel.swift | 13 ++ .../Mocks/MockShortcutItemService.swift | 9 + .../Mocks/MockShortcutStateManager.swift | 13 ++ .../RunnerTests/QuickActionsPluginTests.swift | 220 ++++++++++++++++++ .../ShortcutStateManagerTests.swift | 55 +++++ .../ios/Classes/FLTShortcutStateManager.h | 18 -- .../ios/Classes/FLTShortcutStateManager.m | 32 --- .../ios/Classes/MethodChannel.swift | 16 ++ .../ios/Classes/QuickActionsPlugin.swift | 12 +- .../ios/Classes/ShortcutItemServicing.swift | 15 ++ .../ios/Classes/ShortcutStateManaging.swift | 46 ++++ 14 files changed, 426 insertions(+), 337 deletions(-) delete mode 100644 packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTQuickActionsPluginTests.m delete mode 100644 packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTShortcutStateManagerTests.m create mode 100644 packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockMethodChannel.swift create mode 100644 packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemService.swift create mode 100644 packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutStateManager.swift create mode 100644 packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift create mode 100644 packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/ShortcutStateManagerTests.swift delete mode 100644 packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.h delete mode 100644 packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.m create mode 100644 packages/quick_actions/quick_actions_ios/ios/Classes/MethodChannel.swift create mode 100644 packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemServicing.swift create mode 100644 packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift diff --git a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj index c853a197ca9b..1690ff918474 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj @@ -17,8 +17,11 @@ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; E092A7F628D128EB005C7F67 /* RunnerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F528D128EB005C7F67 /* RunnerUITests.swift */; }; - E0C09C29289C729D00E6977E /* FLTQuickActionsPluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C09C28289C729D00E6977E /* FLTQuickActionsPluginTests.m */; }; - E0C09C32289DBFCA00E6977E /* FLTShortcutStateManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C09C31289DBFCA00E6977E /* FLTShortcutStateManagerTests.m */; }; + E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */; }; + E092A7EE28D10802005C7F67 /* QuickActionsPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */; }; + E092A7EF28D10802005C7F67 /* MockShortcutStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EC28D10802005C7F67 /* MockShortcutStateManager.swift */; }; + E092A7F128D10890005C7F67 /* MockShortcutItemService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F028D10890005C7F67 /* MockShortcutItemService.swift */; }; + E092A7F428D110B3005C7F67 /* ShortcutStateManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F328D110B3005C7F67 /* ShortcutStateManagerTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -76,8 +79,11 @@ 9D27FE1F0F21D4D47DDA16DE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; C35AD3650AB6BF850E016715 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; E092A7F528D128EB005C7F67 /* RunnerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerUITests.swift; sourceTree = ""; }; - E0C09C28289C729D00E6977E /* FLTQuickActionsPluginTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTQuickActionsPluginTests.m; sourceTree = ""; }; - E0C09C31289DBFCA00E6977E /* FLTShortcutStateManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTShortcutStateManagerTests.m; sourceTree = ""; }; + E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockMethodChannel.swift; sourceTree = ""; }; + E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickActionsPluginTests.swift; sourceTree = ""; }; + E092A7EC28D10802005C7F67 /* MockShortcutStateManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockShortcutStateManager.swift; sourceTree = ""; }; + E092A7F028D10890005C7F67 /* MockShortcutItemService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemService.swift; sourceTree = ""; }; + E092A7F328D110B3005C7F67 /* ShortcutStateManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutStateManagerTests.swift; sourceTree = ""; }; F0609304FBCAEC2289164BD5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -111,9 +117,10 @@ 33E20B3326EFCDFC00A4A191 /* RunnerTests */ = { isa = PBXGroup; children = ( + E092A7F228D10908005C7F67 /* Mocks */, 33E20B3626EFCDFC00A4A191 /* Info.plist */, - E0C09C31289DBFCA00E6977E /* FLTShortcutStateManagerTests.m */, - E0C09C28289C729D00E6977E /* FLTQuickActionsPluginTests.m */, + E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */, + E092A7F328D110B3005C7F67 /* ShortcutStateManagerTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -205,6 +212,16 @@ name = Pods; sourceTree = ""; }; + E092A7F228D10908005C7F67 /* Mocks */ = { + isa = PBXGroup; + children = ( + E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */, + E092A7F028D10890005C7F67 /* MockShortcutItemService.swift */, + E092A7EC28D10802005C7F67 /* MockShortcutStateManager.swift */, + ); + path = Mocks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -277,6 +294,7 @@ TargetAttributes = { 33E20B3126EFCDFC00A4A191 = { CreatedOnToolsVersion = 12.5; + LastSwiftMigration = 1330; TestTargetID = 97C146ED1CF9000F007C117D; }; 686BE82C25E58CCF00862533 = { @@ -416,8 +434,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E0C09C32289DBFCA00E6977E /* FLTShortcutStateManagerTests.m in Sources */, - E0C09C29289C729D00E6977E /* FLTQuickActionsPluginTests.m in Sources */, + E092A7EE28D10802005C7F67 /* QuickActionsPluginTests.swift in Sources */, + E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */, + E092A7F128D10890005C7F67 /* MockShortcutItemService.swift in Sources */, + E092A7EF28D10802005C7F67 /* MockShortcutStateManager.swift in Sources */, + E092A7F428D110B3005C7F67 /* ShortcutStateManagerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -479,6 +500,7 @@ baseConfigurationReference = 9D27FE1F0F21D4D47DDA16DE /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -487,6 +509,8 @@ ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Debug; @@ -496,6 +520,7 @@ baseConfigurationReference = 96F949A6B78E2DC62B93C4F8 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -504,6 +529,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTQuickActionsPluginTests.m b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTQuickActionsPluginTests.m deleted file mode 100644 index 89651b573822..000000000000 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTQuickActionsPluginTests.m +++ /dev/null @@ -1,210 +0,0 @@ -// 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; -@import quick_actions_ios; -@import XCTest; -#import - -@interface FLTQuickActionsPluginTests : XCTestCase - -@end - -@implementation FLTQuickActionsPluginTests - -- (void)testHandleMethodCall_setShortcutItems { - NSDictionary *rawItem = @{ - @"type" : @"SearchTheThing", - @"localizedTitle" : @"Search the thing", - @"icon" : @"search_the_thing.png", - }; - - FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"setShortcutItems" - arguments:@[ rawItem ]]; - - FLTShortcutStateManager *mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]); - - QuickActionsPlugin *plugin = - [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class]) - shortcutStateManager:mockShortcutStateManager]; - XCTestExpectation *resultExpectation = - [self expectationWithDescription:@"result block must be called."]; - [plugin handleMethodCall:call - result:^(id _Nullable result) { - XCTAssertNil(result, @"result block must be called with nil."); - [resultExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:1 handler:nil]; - - OCMVerify([mockShortcutStateManager setShortcutItems:@[ rawItem ]]); -} - -- (void)testHandleMethodCall_clearShortcutItems { - FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"clearShortcutItems" - arguments:nil]; - FLTShortcutStateManager *mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]); - QuickActionsPlugin *plugin = - [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class]) - shortcutStateManager:mockShortcutStateManager]; - XCTestExpectation *resultExpectation = - [self expectationWithDescription:@"result block must be called."]; - [plugin handleMethodCall:call - result:^(id _Nullable result) { - XCTAssertNil(result, @"result block must be called with nil."); - [resultExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:1 handler:nil]; - OCMVerify([mockShortcutStateManager setShortcutItems:@[]]); -} - -- (void)testHandleMethodCall_getLaunchAction { - FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"getLaunchAction" - arguments:nil]; - - QuickActionsPlugin *plugin = - [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class]) - shortcutStateManager:OCMClassMock([FLTShortcutStateManager class])]; - XCTestExpectation *resultExpectation = - [self expectationWithDescription:@"result block must be called."]; - [plugin handleMethodCall:call - result:^(id _Nullable result) { - XCTAssertNil(result, @"result block must be called with nil."); - [resultExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:1 handler:nil]; -} - -- (void)testHandleMethodCall_nonExistMethods { - FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"nonExist" arguments:nil]; - - QuickActionsPlugin *plugin = - [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class]) - shortcutStateManager:OCMClassMock([FLTShortcutStateManager class])]; - XCTestExpectation *resultExpectation = - [self expectationWithDescription:@"result must be called."]; - [plugin - handleMethodCall:call - result:^(id _Nullable result) { - XCTAssertEqual(result, FlutterMethodNotImplemented, - @"result block must be called with FlutterMethodNotImplemented"); - [resultExpectation fulfill]; - }]; - - [self waitForExpectationsWithTimeout:1 handler:nil]; -} - -- (void)testApplicationPerformActionForShortcutItem { - id mockChannel = OCMClassMock([FlutterMethodChannel class]); - QuickActionsPlugin *plugin = - [[QuickActionsPlugin alloc] initWithChannel:mockChannel - shortcutStateManager:OCMClassMock([FLTShortcutStateManager class])]; - - UIApplicationShortcutItem *item = [[UIApplicationShortcutItem alloc] - initWithType:@"SearchTheThing" - localizedTitle:@"Search the thing" - localizedSubtitle:nil - icon:[UIApplicationShortcutIcon - iconWithTemplateImageName:@"search_the_thing.png"] - userInfo:nil]; - - BOOL actionResult = [plugin application:[UIApplication sharedApplication] - performActionForShortcutItem:item - completionHandler:^(BOOL succeeded){/* no-op */}]; - XCTAssert(actionResult, @"performActionForShortcutItem must return true."); - OCMVerify([mockChannel invokeMethod:@"launch" arguments:item.type]); -} - -- (void)testApplicationDidFinishLaunchingWithOptions_launchWithShortcut { - id mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]); - QuickActionsPlugin *plugin = - [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class]) - shortcutStateManager:mockShortcutStateManager]; - - UIApplicationShortcutItem *item = [[UIApplicationShortcutItem alloc] - initWithType:@"SearchTheThing" - localizedTitle:@"Search the thing" - localizedSubtitle:nil - icon:[UIApplicationShortcutIcon - iconWithTemplateImageName:@"search_the_thing.png"] - userInfo:nil]; - - BOOL launchResult = [plugin application:[UIApplication sharedApplication] - didFinishLaunchingWithOptions:@{UIApplicationLaunchOptionsShortcutItemKey : item}]; - - XCTAssertFalse(launchResult, - @"didFinishLaunchingWithOptions must return false if launched from shortcut."); -} - -- (void)testApplicationDidFinishLaunchingWithOptions_launchWithoutShortcut { - QuickActionsPlugin *plugin = - [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class]) - shortcutStateManager:OCMClassMock([FLTShortcutStateManager class])]; - BOOL launchResult = [plugin application:[UIApplication sharedApplication] - didFinishLaunchingWithOptions:@{}]; - XCTAssertTrue(launchResult, - @"didFinishLaunchingWithOptions must return true if not launched from shortcut."); -} - -- (void)testApplicationDidBecomeActive_launchWithoutShortcut { - id mockChannel = OCMClassMock([FlutterMethodChannel class]); - id mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]); - QuickActionsPlugin *plugin = - [[QuickActionsPlugin alloc] initWithChannel:mockChannel - shortcutStateManager:mockShortcutStateManager]; - - BOOL launchResult = [plugin application:[UIApplication sharedApplication] - didFinishLaunchingWithOptions:@{}]; - XCTAssertTrue(launchResult, - @"didFinishLaunchingWithOptions must return true if not launched from shortcut."); - [plugin applicationDidBecomeActive:[UIApplication sharedApplication]]; - OCMVerify(never(), [mockChannel invokeMethod:OCMOCK_ANY arguments:OCMOCK_ANY]); -} - -- (void)testApplicationDidBecomeActive_launchWithShortcut { - id mockChannel = OCMClassMock([FlutterMethodChannel class]); - id mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]); - QuickActionsPlugin *plugin = - [[QuickActionsPlugin alloc] initWithChannel:mockChannel - shortcutStateManager:mockShortcutStateManager]; - - UIApplicationShortcutItem *item = [[UIApplicationShortcutItem alloc] - initWithType:@"SearchTheThing" - localizedTitle:@"Search the thing" - localizedSubtitle:nil - icon:[UIApplicationShortcutIcon - iconWithTemplateImageName:@"search_the_thing.png"] - userInfo:nil]; - BOOL launchResult = [plugin application:[UIApplication sharedApplication] - didFinishLaunchingWithOptions:@{UIApplicationLaunchOptionsShortcutItemKey : item}]; - XCTAssertFalse(launchResult, - @"didFinishLaunchingWithOptions must return false if launched from shortcut."); - [plugin applicationDidBecomeActive:[UIApplication sharedApplication]]; - OCMVerify([mockChannel invokeMethod:@"launch" arguments:item.type]); -} - -- (void)testApplicationDidBecomeActive_launchWithShortcut_becomeActiveTwice { - id mockChannel = OCMClassMock([FlutterMethodChannel class]); - id mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]); - QuickActionsPlugin *plugin = - [[QuickActionsPlugin alloc] initWithChannel:mockChannel - shortcutStateManager:mockShortcutStateManager]; - - UIApplicationShortcutItem *item = [[UIApplicationShortcutItem alloc] - initWithType:@"SearchTheThing" - localizedTitle:@"Search the thing" - localizedSubtitle:nil - icon:[UIApplicationShortcutIcon - iconWithTemplateImageName:@"search_the_thing.png"] - userInfo:nil]; - BOOL launchResult = [plugin application:[UIApplication sharedApplication] - didFinishLaunchingWithOptions:@{UIApplicationLaunchOptionsShortcutItemKey : item}]; - XCTAssertFalse(launchResult, - @"didFinishLaunchingWithOptions must return false if launched from shortcut."); - [plugin applicationDidBecomeActive:[UIApplication sharedApplication]]; - [plugin applicationDidBecomeActive:[UIApplication sharedApplication]]; - // shortcut should only be handled once per launch. - OCMVerify(times(1), [mockChannel invokeMethod:@"launch" arguments:item.type]); -} - -@end diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTShortcutStateManagerTests.m b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTShortcutStateManagerTests.m deleted file mode 100644 index 96fbf229e566..000000000000 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTShortcutStateManagerTests.m +++ /dev/null @@ -1,62 +0,0 @@ -// 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 quick_actions_ios; -@import XCTest; -#import - -@interface FLTShortcutStateManagerTests : XCTestCase -@end - -@implementation FLTShortcutStateManagerTests - -- (void)testSetShortcutItems_shouldSetItem { - id mockApplication = OCMPartialMock([UIApplication sharedApplication]); - OCMStub([mockApplication sharedApplication]).andReturn(mockApplication); - - FLTShortcutStateManager *shortcutStateManager = [[FLTShortcutStateManager alloc] init]; - - NSDictionary *rawItem = @{ - @"type" : @"SearchTheThing", - @"localizedTitle" : @"Search the thing", - @"icon" : @"search_the_thing.png", - }; - - [shortcutStateManager setShortcutItems:@[ rawItem ]]; - - UIApplicationShortcutItem *expectedItem = [[UIApplicationShortcutItem alloc] - initWithType:@"SearchTheThing" - localizedTitle:@"Search the thing" - localizedSubtitle:nil - icon:[UIApplicationShortcutIcon - iconWithTemplateImageName:@"search_the_thing.png"] - userInfo:nil]; - - OCMVerify([mockApplication setShortcutItems:@[ expectedItem ]]); -} - -- (void)testSetShortcutItems_shouldSetItemWithoutIcon { - id mockApplication = OCMPartialMock([UIApplication sharedApplication]); - OCMStub([mockApplication sharedApplication]).andReturn(mockApplication); - - NSDictionary *rawItem = @{ - @"type" : @"SearchTheThing", - @"localizedTitle" : @"Search the thing", - // Dart's null value is passed to iOS as `NSNull`. - // The key value pair is still present in the dictionary. - @"icon" : [NSNull null], - }; - FLTShortcutStateManager *shortcutStateManager = [[FLTShortcutStateManager alloc] init]; - [shortcutStateManager setShortcutItems:@[ rawItem ]]; - - UIApplicationShortcutItem *expectedItem = - [[UIApplicationShortcutItem alloc] initWithType:@"SearchTheThing" - localizedTitle:@"Search the thing" - localizedSubtitle:nil - icon:nil - userInfo:nil]; - OCMVerify([mockApplication setShortcutItems:@[ expectedItem ]]); -} - -@end diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockMethodChannel.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockMethodChannel.swift new file mode 100644 index 000000000000..cf2af02ae49b --- /dev/null +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockMethodChannel.swift @@ -0,0 +1,13 @@ +// 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 Foundation +@testable import quick_actions_ios + +final class MockMethodChannel: MethodChannel { + var invokeMethodStub: ((_ methods: String, _ arguments: Any?) -> Void)? = nil + func invokeMethod(_ method: String, arguments: Any?) { + invokeMethodStub?(method, arguments) + } +} diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemService.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemService.swift new file mode 100644 index 000000000000..609858286cee --- /dev/null +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemService.swift @@ -0,0 +1,9 @@ +// 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. + +@testable import quick_actions_ios + +final class MockShortcutItemService: ShortcutItemServicing { + var shortcutItems: [UIApplicationShortcutItem]? = nil +} diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutStateManager.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutStateManager.swift new file mode 100644 index 000000000000..43c22cf19fe4 --- /dev/null +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutStateManager.swift @@ -0,0 +1,13 @@ +// 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. + +@testable import quick_actions_ios + +final class MockShortcutStateManager: ShortcutStateManaging { + var setShortcutItemsStub: (([[String: Any]]) -> Void)? + + func setShortcutItems(_ items: [[String : Any]]) { + setShortcutItemsStub?(items) + } +} diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift new file mode 100644 index 000000000000..7fa621891847 --- /dev/null +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift @@ -0,0 +1,220 @@ +// 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 +import XCTest +@testable import quick_actions_ios + +class QuickActionsPluginTests: XCTestCase { + + func testHandleMethodCall_setShortcutItems() { + let rawItem = [ + "type" : "SearchTheThing", + "localizedTitle" : "Search the thing", + "icon" : "search_the_thing.png", + ] + let call = FlutterMethodCall(methodName: "setShortcutItems", arguments: [rawItem]) + + let mockChannel = MockMethodChannel() + let mockShortcutStateManager = MockShortcutStateManager() + let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + + let setShortcutItemsExpectation = expectation(description: "setShortcutItems must be called.") + mockShortcutStateManager.setShortcutItemsStub = { items in + XCTAssertEqual(items as? [[String:String]], [rawItem]) + setShortcutItemsExpectation.fulfill() + } + + let resultExpectation = expectation(description: "result block must be called.") + plugin.handle(call) { result in + XCTAssertNil(result, "result block must be called with nil.") + resultExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + func testHandleMethodCall_clearShortcutItems() { + let call = FlutterMethodCall(methodName: "clearShortcutItems", arguments: nil) + let mockChannel = MockMethodChannel() + let mockShortcutStateManager = MockShortcutStateManager() + let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + + let setShortcutItemsExpectation = expectation(description: "setShortcutItems must be called.") + mockShortcutStateManager.setShortcutItemsStub = { items in + XCTAssert(items.isEmpty) + setShortcutItemsExpectation.fulfill() + } + + let resultExpectation = expectation(description: "result block must be called.") + plugin.handle(call) { result in + XCTAssertNil(result, "result block must be called with nil.") + resultExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + func testHandleMethodCall_getLaunchAction() { + let call = FlutterMethodCall(methodName: "getLaunchAction", arguments: nil) + + let mockChannel = MockMethodChannel() + let mockShortcutStateManager = MockShortcutStateManager() + let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + + let resultExpectation = expectation(description: "result block must be called.") + plugin.handle(call) { result in + XCTAssertNil(result, "result block must be called with nil.") + resultExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + func testHandleMethodCall_nonExistMethods() { + let call = FlutterMethodCall(methodName: "nonExist", arguments: nil) + + let mockChannel = MockMethodChannel() + let mockShortcutStateManager = MockShortcutStateManager() + let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + + let resultExpectation = expectation(description: "result block must be called.") + + plugin.handle(call) { result in + XCTAssertEqual(result as? NSObject, FlutterMethodNotImplemented, "result block must be called with FlutterMethodNotImplemented") + resultExpectation.fulfill() + } + + waitForExpectations(timeout: 1) + } + + func testApplicationPerformActionForShortcutItem() { + let mockChannel = MockMethodChannel() + let mockShortcutStateManager = MockShortcutStateManager() + let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + + let item = UIApplicationShortcutItem( + type: "SearchTheThing", + localizedTitle: "Search the thing", + localizedSubtitle: nil, + icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), + userInfo: nil) + + let invokeMethodExpectation = expectation(description: "invokeMethod must be called.") + mockChannel.invokeMethodStub = { method, arguments in + XCTAssertEqual(method, "launch") + XCTAssertEqual(arguments as? String, item.type) + invokeMethodExpectation.fulfill() + } + + let actionResult = plugin.application( + UIApplication.shared, + performActionFor: item) { success in /* no-op */} + + XCTAssert(actionResult, "performActionForShortcutItem must return true.") + + waitForExpectations(timeout: 1) + } + + func testApplicationDidFinishLaunchingWithOptions_launchWithShortcut() { + let mockChannel = MockMethodChannel() + let mockShortcutStateManager = MockShortcutStateManager() + let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + + let item = UIApplicationShortcutItem( + type: "SearchTheThing", + localizedTitle: "Search the thing", + localizedSubtitle: nil, + icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), + userInfo: nil) + + let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item]) + XCTAssertFalse(launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.") + + } + + func testApplicationDidFinishLaunchingWithOptions_launchWithoutShortcut() { + let mockChannel = MockMethodChannel() + let mockShortcutStateManager = MockShortcutStateManager() + let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + + let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [:]) + XCTAssert(launchResult, "didFinishLaunchingWithOptions must return true if not launched from shortcut.") + + } + + func testApplicationDidBecomeActive_launchWithoutShortcut() { + let mockChannel = MockMethodChannel() + let mockShortcutStateManager = MockShortcutStateManager() + let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + + mockChannel.invokeMethodStub = { _, _ in + XCTFail("invokeMethod should not be called if launch without shortcut.") + } + + let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [:]) + XCTAssert(launchResult, "didFinishLaunchingWithOptions must return true if not launched from shortcut.") + + plugin.applicationDidBecomeActive(UIApplication.shared) + + } + + func testApplicationDidBecomeActive_launchWithShortcut() { + let item = UIApplicationShortcutItem( + type: "SearchTheThing", + localizedTitle: "Search the thing", + localizedSubtitle: nil, + icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), + userInfo: nil) + + let mockChannel = MockMethodChannel() + let mockShortcutStateManager = MockShortcutStateManager() + let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + + let invokeMethodExpectation = expectation(description: "invokeMethod must be called.") + mockChannel.invokeMethodStub = { method, arguments in + XCTAssertEqual(method, "launch") + XCTAssertEqual(arguments as? String, item.type) + invokeMethodExpectation.fulfill() + } + + let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item]) + + XCTAssertFalse(launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.") + + plugin.applicationDidBecomeActive(UIApplication.shared) + waitForExpectations(timeout: 1) + } + + func testApplicationDidBecomeActive_launchWithShortcut_becomeActiveTwice() { + let item = UIApplicationShortcutItem( + type: "SearchTheThing", + localizedTitle: "Search the thing", + localizedSubtitle: nil, + icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), + userInfo: nil) + + let mockChannel = MockMethodChannel() + let mockShortcutStateManager = MockShortcutStateManager() + let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + + let invokeMethodExpectation = expectation(description: "invokeMethod must be called.") + + var invokeMehtodCount = 0 + mockChannel.invokeMethodStub = { method, arguments in + invokeMehtodCount += 1 + invokeMethodExpectation.fulfill() + } + + let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item]) + + XCTAssertFalse(launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.") + + plugin.applicationDidBecomeActive(UIApplication.shared) + waitForExpectations(timeout: 1) + + XCTAssertEqual(invokeMehtodCount, 1, "shortcut should only be handled once per launch.") + } + +} diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/ShortcutStateManagerTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/ShortcutStateManagerTests.swift new file mode 100644 index 000000000000..f81a920199aa --- /dev/null +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/ShortcutStateManagerTests.swift @@ -0,0 +1,55 @@ +// 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 +import XCTest +@testable import quick_actions_ios + +class ShortcutStateManagerTests: XCTestCase { + + func testSetShortcutItems_shouldSetItem() { + let rawItem = [ + "type" : "SearchTheThing", + "localizedTitle" : "Search the thing", + "icon" : "search_the_thing.png", + ] + + let expectedItem = UIApplicationShortcutItem( + type: "SearchTheThing", + localizedTitle: "Search the thing", + localizedSubtitle: nil, + icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), + userInfo: nil) + + let mockShortcutItemService = MockShortcutItemService() + let manager = ShortcutStateManager(service: mockShortcutItemService) + + manager.setShortcutItems([rawItem]) + + XCTAssertEqual(mockShortcutItemService.shortcutItems, [expectedItem]) + + } + + func testSetShortcutItems_shouldSetItemWithoutIcon() { + let rawItem: [String: Any] = [ + "type" : "SearchTheThing", + "localizedTitle" : "Search the thing", + "icon" : NSNull(), + ] + + let expectedItem = UIApplicationShortcutItem( + type: "SearchTheThing", + localizedTitle: "Search the thing", + localizedSubtitle: nil, + icon: nil, + userInfo: nil) + + let mockShortcutItemService = MockShortcutItemService() + let manager = ShortcutStateManager(service: mockShortcutItemService) + + manager.setShortcutItems([rawItem]) + + XCTAssertEqual(mockShortcutItemService.shortcutItems, [expectedItem]) + } +} diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.h b/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.h deleted file mode 100644 index 05d0433db6e0..000000000000 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.h +++ /dev/null @@ -1,18 +0,0 @@ -// 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 - -NS_ASSUME_NONNULL_BEGIN - -/// Manages the shortcut related states. -@interface FLTShortcutStateManager : NSObject - -/// Sets the list of shortcut items. -/// -/// @param items the list of shortcut items to be parsed and set. -- (void)setShortcutItems:(NSArray *)items API_AVAILABLE(ios(9.0)); -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.m b/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.m deleted file mode 100644 index e39edd2b0f37..000000000000 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.m +++ /dev/null @@ -1,32 +0,0 @@ -// 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 "FLTShortcutStateManager.h" - -@implementation FLTShortcutStateManager - -- (void)setShortcutItems:(NSArray *)items { - NSMutableArray *newShortcuts = [[NSMutableArray alloc] init]; - - for (id item in items) { - UIApplicationShortcutItem *shortcut = [self deserializeShortcutItem:item]; - [newShortcuts addObject:shortcut]; - } - - [UIApplication sharedApplication].shortcutItems = newShortcuts; -} - -- (UIApplicationShortcutItem *)deserializeShortcutItem:(NSDictionary *)serialized { - UIApplicationShortcutIcon *icon = - [serialized[@"icon"] isKindOfClass:[NSNull class]] - ? nil - : [UIApplicationShortcutIcon iconWithTemplateImageName:serialized[@"icon"]]; - return [[UIApplicationShortcutItem alloc] initWithType:serialized[@"type"] - localizedTitle:serialized[@"localizedTitle"] - localizedSubtitle:nil - icon:icon - userInfo:nil]; -} - -@end diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/MethodChannel.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/MethodChannel.swift new file mode 100644 index 000000000000..5d52790dd4b2 --- /dev/null +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/MethodChannel.swift @@ -0,0 +1,16 @@ +// 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 + +/// A channel for platform code to communicate with the Dart code. +protocol MethodChannel { + /// Invokes a method in Dart code. + /// - Parameter method the method name. + /// - Parameter arguments the method arguments. + func invokeMethod(_ method: String, arguments: Any?) +} + +/// A default implementation of the `MethodChannel` protocol. +extension FlutterMethodChannel: MethodChannel {} diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift index 26d6d20f8c02..d653c7ce6917 100644 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift @@ -15,16 +15,14 @@ public final class QuickActionsPlugin: NSObject, FlutterPlugin { registrar.addApplicationDelegate(instance) } - private let channel: FlutterMethodChannel - private let shortcutStateManager: FLTShortcutStateManager + private let channel: MethodChannel + private let shortcutStateManager: ShortcutStateManaging /// The type of the shortcut item selected when launching the app. private var launchingShortcutType: String? = nil - // TODO: (hellohuanlin) remove `@objc` attribute and make it non-public after migrating tests to Swift. - @objc - public init( - channel: FlutterMethodChannel, - shortcutStateManager: FLTShortcutStateManager = FLTShortcutStateManager() + init( + channel: MethodChannel, + shortcutStateManager: ShortcutStateManaging = ShortcutStateManager() ) { self.channel = channel self.shortcutStateManager = shortcutStateManager diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemServicing.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemServicing.swift new file mode 100644 index 000000000000..745b55d0c573 --- /dev/null +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemServicing.swift @@ -0,0 +1,15 @@ +// 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 UIKit + +/// Controls shortcut items. +protocol ShortcutItemServicing: AnyObject { + + /// An array of shortcut items for home screen. + var shortcutItems: [UIApplicationShortcutItem]? { get set } +} + +/// A default implementation of the `ShortcutItemServicing` protocol. +extension UIApplication: ShortcutItemServicing {} diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift new file mode 100644 index 000000000000..861bec57acba --- /dev/null +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift @@ -0,0 +1,46 @@ +// 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 UIKit + +/// Manages the shortcut related states. +protocol ShortcutStateManaging { + + /// Sets the list of shortcut items. + /// + /// - Parameter items the list of shortcut items to be parsed and set. + func setShortcutItems(_ items: [[String:Any]]) +} + +/// A default implementation of `ShortcutStateManaging` protocol. +final class ShortcutStateManager: ShortcutStateManaging { + + private let service: ShortcutItemServicing + + /// Creates a ShortcutStateManager + /// - Parameter service a shortcut item service, with `UIApplication.shared` as default. + init(service: ShortcutItemServicing = UIApplication.shared) { + self.service = service + } + + func setShortcutItems(_ items: [[String : Any]]) { + service.shortcutItems = items.compactMap { deserializeShortcutItem(with:$0) } + } + + private func deserializeShortcutItem(with serialized: [String: Any]) -> UIApplicationShortcutItem { + + let icon = (serialized["icon"] as? String).map { + UIApplicationShortcutIcon(templateImageName: $0) + } + + // type and localizedTitle are required. + return UIApplicationShortcutItem( + type: serialized["type"] as! String, + localizedTitle: serialized["localizedTitle"] as! String, + localizedSubtitle: nil, + icon: icon, + userInfo: nil) + } +} + From 1cebdb19695c5a0ed025e44b898324cb1b3c880e Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Thu, 20 Oct 2022 15:52:24 -0700 Subject: [PATCH 02/13] remove objc proj settings --- packages/quick_actions/quick_actions_ios/example/ios/Podfile | 1 - .../quick_actions_ios/ios/quick_actions_ios.podspec | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/quick_actions/quick_actions_ios/example/ios/Podfile b/packages/quick_actions/quick_actions_ios/example/ios/Podfile index b52805241c18..3924e59aa0f9 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/Podfile +++ b/packages/quick_actions/quick_actions_ios/example/ios/Podfile @@ -31,7 +31,6 @@ target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths - pod 'OCMock', '~> 3.9.1' end end diff --git a/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec index d8090caa8ef6..c866bd5f0785 100644 --- a/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec +++ b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec @@ -15,7 +15,7 @@ Downloaded by pub (not CocoaPods). s.source = { :http => 'https://github.com/flutter/plugins/tree/main/packages/quick_actions' } s.documentation_url = 'https://pub.dev/packages/quick_actions' s.swift_version = '5.0' - s.source_files = 'Classes/**/*.{h,m,swift}' + s.source_files = 'Classes/**/*.swift' s.xcconfig = { 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', From 8bbd0b79f71e2a0eaec0124b9f67e63113dce1ee Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Thu, 20 Oct 2022 16:01:40 -0700 Subject: [PATCH 03/13] rename shortcut state manager --- .../example/ios/Runner.xcodeproj/project.pbxproj | 12 ++++++------ ....swift => DefaultShortcutStateManagerTests.swift} | 4 ++-- .../ios/Classes/QuickActionsPlugin.swift | 2 +- .../ios/Classes/ShortcutStateManaging.swift | 3 +-- 4 files changed, 10 insertions(+), 11 deletions(-) rename packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/{ShortcutStateManagerTests.swift => DefaultShortcutStateManagerTests.swift} (90%) diff --git a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj index 1690ff918474..a898bebabb49 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj @@ -16,12 +16,12 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - E092A7F628D128EB005C7F67 /* RunnerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F528D128EB005C7F67 /* RunnerUITests.swift */; }; E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */; }; E092A7EE28D10802005C7F67 /* QuickActionsPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */; }; E092A7EF28D10802005C7F67 /* MockShortcutStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EC28D10802005C7F67 /* MockShortcutStateManager.swift */; }; E092A7F128D10890005C7F67 /* MockShortcutItemService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F028D10890005C7F67 /* MockShortcutItemService.swift */; }; - E092A7F428D110B3005C7F67 /* ShortcutStateManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F328D110B3005C7F67 /* ShortcutStateManagerTests.swift */; }; + E092A7F428D110B3005C7F67 /* DefaultShortcutStateManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F328D110B3005C7F67 /* DefaultShortcutStateManagerTests.swift */; }; + E092A7F628D128EB005C7F67 /* RunnerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F528D128EB005C7F67 /* RunnerUITests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -78,12 +78,12 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9D27FE1F0F21D4D47DDA16DE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; C35AD3650AB6BF850E016715 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - E092A7F528D128EB005C7F67 /* RunnerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerUITests.swift; sourceTree = ""; }; E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockMethodChannel.swift; sourceTree = ""; }; E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickActionsPluginTests.swift; sourceTree = ""; }; E092A7EC28D10802005C7F67 /* MockShortcutStateManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockShortcutStateManager.swift; sourceTree = ""; }; E092A7F028D10890005C7F67 /* MockShortcutItemService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemService.swift; sourceTree = ""; }; - E092A7F328D110B3005C7F67 /* ShortcutStateManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutStateManagerTests.swift; sourceTree = ""; }; + E092A7F328D110B3005C7F67 /* DefaultShortcutStateManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultShortcutStateManagerTests.swift; sourceTree = ""; }; + E092A7F528D128EB005C7F67 /* RunnerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerUITests.swift; sourceTree = ""; }; F0609304FBCAEC2289164BD5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -120,7 +120,7 @@ E092A7F228D10908005C7F67 /* Mocks */, 33E20B3626EFCDFC00A4A191 /* Info.plist */, E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */, - E092A7F328D110B3005C7F67 /* ShortcutStateManagerTests.swift */, + E092A7F328D110B3005C7F67 /* DefaultShortcutStateManagerTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -438,7 +438,7 @@ E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */, E092A7F128D10890005C7F67 /* MockShortcutItemService.swift in Sources */, E092A7EF28D10802005C7F67 /* MockShortcutStateManager.swift in Sources */, - E092A7F428D110B3005C7F67 /* ShortcutStateManagerTests.swift in Sources */, + E092A7F428D110B3005C7F67 /* DefaultShortcutStateManagerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/ShortcutStateManagerTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutStateManagerTests.swift similarity index 90% rename from packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/ShortcutStateManagerTests.swift rename to packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutStateManagerTests.swift index f81a920199aa..1c2b2d350527 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/ShortcutStateManagerTests.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutStateManagerTests.swift @@ -23,7 +23,7 @@ class ShortcutStateManagerTests: XCTestCase { userInfo: nil) let mockShortcutItemService = MockShortcutItemService() - let manager = ShortcutStateManager(service: mockShortcutItemService) + let manager = DefaultShortcutStateManager(service: mockShortcutItemService) manager.setShortcutItems([rawItem]) @@ -46,7 +46,7 @@ class ShortcutStateManagerTests: XCTestCase { userInfo: nil) let mockShortcutItemService = MockShortcutItemService() - let manager = ShortcutStateManager(service: mockShortcutItemService) + let manager = DefaultShortcutStateManager(service: mockShortcutItemService) manager.setShortcutItems([rawItem]) diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift index d653c7ce6917..d65c9fba8f3e 100644 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift @@ -22,7 +22,7 @@ public final class QuickActionsPlugin: NSObject, FlutterPlugin { init( channel: MethodChannel, - shortcutStateManager: ShortcutStateManaging = ShortcutStateManager() + shortcutStateManager: ShortcutStateManaging = DefaultShortcutStateManager() ) { self.channel = channel self.shortcutStateManager = shortcutStateManager diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift index 861bec57acba..3e07ef000827 100644 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift @@ -14,7 +14,7 @@ protocol ShortcutStateManaging { } /// A default implementation of `ShortcutStateManaging` protocol. -final class ShortcutStateManager: ShortcutStateManaging { +final class DefaultShortcutStateManager: ShortcutStateManaging { private let service: ShortcutItemServicing @@ -43,4 +43,3 @@ final class ShortcutStateManager: ShortcutStateManaging { userInfo: nil) } } - From 0994d9ea85b100e739cc651fd33256a58f2e32e9 Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Thu, 20 Oct 2022 16:07:30 -0700 Subject: [PATCH 04/13] bump version --- packages/quick_actions/quick_actions_ios/CHANGELOG.md | 3 ++- packages/quick_actions/quick_actions_ios/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/quick_actions/quick_actions_ios/CHANGELOG.md b/packages/quick_actions/quick_actions_ios/CHANGELOG.md index 31fe43832d2f..bded35478899 100644 --- a/packages/quick_actions/quick_actions_ios/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_ios/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 1.0.2 +* Migrates remaining components to Swift and removes all Objective-C settings. * Migrates `RunnerUITests` to Swift. ## 1.0.1 diff --git a/packages/quick_actions/quick_actions_ios/pubspec.yaml b/packages/quick_actions/quick_actions_ios/pubspec.yaml index f01ae4aed9c3..6e7fb43dd7ed 100644 --- a/packages/quick_actions/quick_actions_ios/pubspec.yaml +++ b/packages/quick_actions/quick_actions_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: quick_actions_ios description: An implementation for the iOS platform of the Flutter `quick_actions` plugin. repository: https://github.com/flutter/plugins/tree/main/packages/quick_actions/quick_actions_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 1.0.1 +version: 1.0.2 environment: sdk: ">=2.15.0 <3.0.0" From 3bff5245650b7389101c7c5a8babc16d30ba2cb8 Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Thu, 20 Oct 2022 16:13:39 -0700 Subject: [PATCH 05/13] run swift-format --- .../DefaultShortcutStateManagerTests.swift | 13 ++-- .../RunnerTests/Mocks/MockMethodChannel.swift | 1 + .../Mocks/MockShortcutStateManager.swift | 2 +- .../RunnerTests/QuickActionsPluginTests.swift | 73 +++++++++++++------ .../ios/Classes/ShortcutItemServicing.swift | 2 +- .../ios/Classes/ShortcutStateManaging.swift | 9 ++- 6 files changed, 64 insertions(+), 36 deletions(-) diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutStateManagerTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutStateManagerTests.swift index 1c2b2d350527..38ba0b24ca5f 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutStateManagerTests.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutStateManagerTests.swift @@ -4,15 +4,16 @@ import Flutter import XCTest + @testable import quick_actions_ios class ShortcutStateManagerTests: XCTestCase { func testSetShortcutItems_shouldSetItem() { let rawItem = [ - "type" : "SearchTheThing", - "localizedTitle" : "Search the thing", - "icon" : "search_the_thing.png", + "type": "SearchTheThing", + "localizedTitle": "Search the thing", + "icon": "search_the_thing.png", ] let expectedItem = UIApplicationShortcutItem( @@ -33,9 +34,9 @@ class ShortcutStateManagerTests: XCTestCase { func testSetShortcutItems_shouldSetItemWithoutIcon() { let rawItem: [String: Any] = [ - "type" : "SearchTheThing", - "localizedTitle" : "Search the thing", - "icon" : NSNull(), + "type": "SearchTheThing", + "localizedTitle": "Search the thing", + "icon": NSNull(), ] let expectedItem = UIApplicationShortcutItem( diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockMethodChannel.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockMethodChannel.swift index cf2af02ae49b..b52fa1daec95 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockMethodChannel.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockMethodChannel.swift @@ -3,6 +3,7 @@ // found in the LICENSE file. import Foundation + @testable import quick_actions_ios final class MockMethodChannel: MethodChannel { diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutStateManager.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutStateManager.swift index 43c22cf19fe4..e3b1d72e8240 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutStateManager.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutStateManager.swift @@ -7,7 +7,7 @@ final class MockShortcutStateManager: ShortcutStateManaging { var setShortcutItemsStub: (([[String: Any]]) -> Void)? - func setShortcutItems(_ items: [[String : Any]]) { + func setShortcutItems(_ items: [[String: Any]]) { setShortcutItemsStub?(items) } } diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift index 7fa621891847..49cadc73b31d 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift @@ -4,25 +4,27 @@ import Flutter import XCTest + @testable import quick_actions_ios class QuickActionsPluginTests: XCTestCase { func testHandleMethodCall_setShortcutItems() { let rawItem = [ - "type" : "SearchTheThing", - "localizedTitle" : "Search the thing", - "icon" : "search_the_thing.png", + "type": "SearchTheThing", + "localizedTitle": "Search the thing", + "icon": "search_the_thing.png", ] let call = FlutterMethodCall(methodName: "setShortcutItems", arguments: [rawItem]) let mockChannel = MockMethodChannel() let mockShortcutStateManager = MockShortcutStateManager() - let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + let plugin = QuickActionsPlugin( + channel: mockChannel, shortcutStateManager: mockShortcutStateManager) let setShortcutItemsExpectation = expectation(description: "setShortcutItems must be called.") mockShortcutStateManager.setShortcutItemsStub = { items in - XCTAssertEqual(items as? [[String:String]], [rawItem]) + XCTAssertEqual(items as? [[String: String]], [rawItem]) setShortcutItemsExpectation.fulfill() } @@ -39,7 +41,8 @@ class QuickActionsPluginTests: XCTestCase { let call = FlutterMethodCall(methodName: "clearShortcutItems", arguments: nil) let mockChannel = MockMethodChannel() let mockShortcutStateManager = MockShortcutStateManager() - let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + let plugin = QuickActionsPlugin( + channel: mockChannel, shortcutStateManager: mockShortcutStateManager) let setShortcutItemsExpectation = expectation(description: "setShortcutItems must be called.") mockShortcutStateManager.setShortcutItemsStub = { items in @@ -61,7 +64,8 @@ class QuickActionsPluginTests: XCTestCase { let mockChannel = MockMethodChannel() let mockShortcutStateManager = MockShortcutStateManager() - let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + let plugin = QuickActionsPlugin( + channel: mockChannel, shortcutStateManager: mockShortcutStateManager) let resultExpectation = expectation(description: "result block must be called.") plugin.handle(call) { result in @@ -77,12 +81,15 @@ class QuickActionsPluginTests: XCTestCase { let mockChannel = MockMethodChannel() let mockShortcutStateManager = MockShortcutStateManager() - let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + let plugin = QuickActionsPlugin( + channel: mockChannel, shortcutStateManager: mockShortcutStateManager) let resultExpectation = expectation(description: "result block must be called.") plugin.handle(call) { result in - XCTAssertEqual(result as? NSObject, FlutterMethodNotImplemented, "result block must be called with FlutterMethodNotImplemented") + XCTAssertEqual( + result as? NSObject, FlutterMethodNotImplemented, + "result block must be called with FlutterMethodNotImplemented") resultExpectation.fulfill() } @@ -92,7 +99,8 @@ class QuickActionsPluginTests: XCTestCase { func testApplicationPerformActionForShortcutItem() { let mockChannel = MockMethodChannel() let mockShortcutStateManager = MockShortcutStateManager() - let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + let plugin = QuickActionsPlugin( + channel: mockChannel, shortcutStateManager: mockShortcutStateManager) let item = UIApplicationShortcutItem( type: "SearchTheThing", @@ -110,7 +118,8 @@ class QuickActionsPluginTests: XCTestCase { let actionResult = plugin.application( UIApplication.shared, - performActionFor: item) { success in /* no-op */} + performActionFor: item + ) { success in /* no-op */ } XCTAssert(actionResult, "performActionForShortcutItem must return true.") @@ -120,7 +129,8 @@ class QuickActionsPluginTests: XCTestCase { func testApplicationDidFinishLaunchingWithOptions_launchWithShortcut() { let mockChannel = MockMethodChannel() let mockShortcutStateManager = MockShortcutStateManager() - let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + let plugin = QuickActionsPlugin( + channel: mockChannel, shortcutStateManager: mockShortcutStateManager) let item = UIApplicationShortcutItem( type: "SearchTheThing", @@ -129,32 +139,39 @@ class QuickActionsPluginTests: XCTestCase { icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), userInfo: nil) - let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item]) - XCTAssertFalse(launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.") + let launchResult = plugin.application( + UIApplication.shared, + didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item]) + XCTAssertFalse( + launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.") } func testApplicationDidFinishLaunchingWithOptions_launchWithoutShortcut() { let mockChannel = MockMethodChannel() let mockShortcutStateManager = MockShortcutStateManager() - let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + let plugin = QuickActionsPlugin( + channel: mockChannel, shortcutStateManager: mockShortcutStateManager) let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [:]) - XCTAssert(launchResult, "didFinishLaunchingWithOptions must return true if not launched from shortcut.") + XCTAssert( + launchResult, "didFinishLaunchingWithOptions must return true if not launched from shortcut.") } func testApplicationDidBecomeActive_launchWithoutShortcut() { let mockChannel = MockMethodChannel() let mockShortcutStateManager = MockShortcutStateManager() - let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + let plugin = QuickActionsPlugin( + channel: mockChannel, shortcutStateManager: mockShortcutStateManager) mockChannel.invokeMethodStub = { _, _ in XCTFail("invokeMethod should not be called if launch without shortcut.") } let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [:]) - XCTAssert(launchResult, "didFinishLaunchingWithOptions must return true if not launched from shortcut.") + XCTAssert( + launchResult, "didFinishLaunchingWithOptions must return true if not launched from shortcut.") plugin.applicationDidBecomeActive(UIApplication.shared) @@ -170,7 +187,8 @@ class QuickActionsPluginTests: XCTestCase { let mockChannel = MockMethodChannel() let mockShortcutStateManager = MockShortcutStateManager() - let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + let plugin = QuickActionsPlugin( + channel: mockChannel, shortcutStateManager: mockShortcutStateManager) let invokeMethodExpectation = expectation(description: "invokeMethod must be called.") mockChannel.invokeMethodStub = { method, arguments in @@ -179,9 +197,12 @@ class QuickActionsPluginTests: XCTestCase { invokeMethodExpectation.fulfill() } - let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item]) + let launchResult = plugin.application( + UIApplication.shared, + didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item]) - XCTAssertFalse(launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.") + XCTAssertFalse( + launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.") plugin.applicationDidBecomeActive(UIApplication.shared) waitForExpectations(timeout: 1) @@ -197,7 +218,8 @@ class QuickActionsPluginTests: XCTestCase { let mockChannel = MockMethodChannel() let mockShortcutStateManager = MockShortcutStateManager() - let plugin = QuickActionsPlugin(channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + let plugin = QuickActionsPlugin( + channel: mockChannel, shortcutStateManager: mockShortcutStateManager) let invokeMethodExpectation = expectation(description: "invokeMethod must be called.") @@ -207,9 +229,12 @@ class QuickActionsPluginTests: XCTestCase { invokeMethodExpectation.fulfill() } - let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item]) + let launchResult = plugin.application( + UIApplication.shared, + didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item]) - XCTAssertFalse(launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.") + XCTAssertFalse( + launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.") plugin.applicationDidBecomeActive(UIApplication.shared) waitForExpectations(timeout: 1) diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemServicing.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemServicing.swift index 745b55d0c573..12abd62b8335 100644 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemServicing.swift +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemServicing.swift @@ -11,5 +11,5 @@ protocol ShortcutItemServicing: AnyObject { var shortcutItems: [UIApplicationShortcutItem]? { get set } } -/// A default implementation of the `ShortcutItemServicing` protocol. +/// A default implementation of the `ShortcutItemServicing` protocol. extension UIApplication: ShortcutItemServicing {} diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift index 3e07ef000827..ae70f170366f 100644 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift @@ -10,7 +10,7 @@ protocol ShortcutStateManaging { /// Sets the list of shortcut items. /// /// - Parameter items the list of shortcut items to be parsed and set. - func setShortcutItems(_ items: [[String:Any]]) + func setShortcutItems(_ items: [[String: Any]]) } /// A default implementation of `ShortcutStateManaging` protocol. @@ -24,11 +24,12 @@ final class DefaultShortcutStateManager: ShortcutStateManaging { self.service = service } - func setShortcutItems(_ items: [[String : Any]]) { - service.shortcutItems = items.compactMap { deserializeShortcutItem(with:$0) } + func setShortcutItems(_ items: [[String: Any]]) { + service.shortcutItems = items.compactMap { deserializeShortcutItem(with: $0) } } - private func deserializeShortcutItem(with serialized: [String: Any]) -> UIApplicationShortcutItem { + private func deserializeShortcutItem(with serialized: [String: Any]) -> UIApplicationShortcutItem + { let icon = (serialized["icon"] as? String).map { UIApplicationShortcutIcon(templateImageName: $0) From 87a9ec6832883338041f50f8c2c55094aa549041 Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Thu, 20 Oct 2022 16:25:46 -0700 Subject: [PATCH 06/13] nit --- .../quick_actions_ios/ios/Classes/ShortcutItemServicing.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemServicing.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemServicing.swift index 12abd62b8335..018891815eb4 100644 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemServicing.swift +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemServicing.swift @@ -4,7 +4,7 @@ import UIKit -/// Controls shortcut items. +/// Services `UIApplicationShortcutItem`s. protocol ShortcutItemServicing: AnyObject { /// An array of shortcut items for home screen. From 76528a8c218baeb77c7ef0827363b525b1a78ae6 Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Thu, 20 Oct 2022 16:42:04 -0700 Subject: [PATCH 07/13] remove public_header_files --- .../quick_actions_ios/ios/quick_actions_ios.podspec | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec index c866bd5f0785..a6fff92025b2 100644 --- a/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec +++ b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec @@ -20,7 +20,6 @@ Downloaded by pub (not CocoaPods). 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', } - s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' s.platform = :ios, '9.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } From a0ff061af8c47b678f0e046a94187012ef92b267 Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Thu, 3 Nov 2022 16:23:26 -0700 Subject: [PATCH 08/13] use shortcut item parser instead of shortcut state manager --- .../ios/Runner.xcodeproj/project.pbxproj | 24 ++-- ...t => DefaultShortcutItemParserTests.swift} | 22 ++-- ....swift => MockAppShortcutController.swift} | 2 +- .../Mocks/MockShortcutItemParser.swift | 20 ++++ .../Mocks/MockShortcutStateManager.swift | 13 --- .../RunnerTests/QuickActionsPluginTests.swift | 104 +++++++++++++----- ...ing.swift => AppShortcutControlling.swift} | 8 +- .../ios/Classes/QuickActionsPlugin.swift | 13 ++- .../ios/Classes/ShortcutItemParser.swift | 39 +++++++ .../ios/Classes/ShortcutStateManaging.swift | 46 -------- 10 files changed, 165 insertions(+), 126 deletions(-) rename packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/{DefaultShortcutStateManagerTests.swift => DefaultShortcutItemParserTests.swift} (59%) rename packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/{MockShortcutItemService.swift => MockAppShortcutController.swift} (80%) create mode 100644 packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift delete mode 100644 packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutStateManager.swift rename packages/quick_actions/quick_actions_ios/ios/Classes/{ShortcutItemServicing.swift => AppShortcutControlling.swift} (58%) create mode 100644 packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift delete mode 100644 packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift diff --git a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj index a898bebabb49..862128018c52 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj @@ -18,10 +18,10 @@ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */; }; E092A7EE28D10802005C7F67 /* QuickActionsPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */; }; - E092A7EF28D10802005C7F67 /* MockShortcutStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EC28D10802005C7F67 /* MockShortcutStateManager.swift */; }; - E092A7F128D10890005C7F67 /* MockShortcutItemService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F028D10890005C7F67 /* MockShortcutItemService.swift */; }; - E092A7F428D110B3005C7F67 /* DefaultShortcutStateManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F328D110B3005C7F67 /* DefaultShortcutStateManagerTests.swift */; }; + E092A7F128D10890005C7F67 /* MockAppShortcutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F028D10890005C7F67 /* MockAppShortcutController.swift */; }; + E092A7F428D110B3005C7F67 /* DefaultShortcutItemParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */; }; E092A7F628D128EB005C7F67 /* RunnerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F528D128EB005C7F67 /* RunnerUITests.swift */; }; + E0A075D529147FE200329BAE /* MockShortcutItemParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -80,10 +80,10 @@ C35AD3650AB6BF850E016715 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockMethodChannel.swift; sourceTree = ""; }; E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickActionsPluginTests.swift; sourceTree = ""; }; - E092A7EC28D10802005C7F67 /* MockShortcutStateManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockShortcutStateManager.swift; sourceTree = ""; }; - E092A7F028D10890005C7F67 /* MockShortcutItemService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemService.swift; sourceTree = ""; }; - E092A7F328D110B3005C7F67 /* DefaultShortcutStateManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultShortcutStateManagerTests.swift; sourceTree = ""; }; + E092A7F028D10890005C7F67 /* MockAppShortcutController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAppShortcutController.swift; sourceTree = ""; }; + E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultShortcutItemParserTests.swift; sourceTree = ""; }; E092A7F528D128EB005C7F67 /* RunnerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerUITests.swift; sourceTree = ""; }; + E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemParser.swift; sourceTree = ""; }; F0609304FBCAEC2289164BD5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -120,7 +120,7 @@ E092A7F228D10908005C7F67 /* Mocks */, 33E20B3626EFCDFC00A4A191 /* Info.plist */, E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */, - E092A7F328D110B3005C7F67 /* DefaultShortcutStateManagerTests.swift */, + E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -216,8 +216,8 @@ isa = PBXGroup; children = ( E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */, - E092A7F028D10890005C7F67 /* MockShortcutItemService.swift */, - E092A7EC28D10802005C7F67 /* MockShortcutStateManager.swift */, + E092A7F028D10890005C7F67 /* MockAppShortcutController.swift */, + E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */, ); path = Mocks; sourceTree = ""; @@ -436,9 +436,9 @@ files = ( E092A7EE28D10802005C7F67 /* QuickActionsPluginTests.swift in Sources */, E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */, - E092A7F128D10890005C7F67 /* MockShortcutItemService.swift in Sources */, - E092A7EF28D10802005C7F67 /* MockShortcutStateManager.swift in Sources */, - E092A7F428D110B3005C7F67 /* DefaultShortcutStateManagerTests.swift in Sources */, + E092A7F128D10890005C7F67 /* MockAppShortcutController.swift in Sources */, + E0A075D529147FE200329BAE /* MockShortcutItemParser.swift in Sources */, + E092A7F428D110B3005C7F67 /* DefaultShortcutItemParserTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutStateManagerTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift similarity index 59% rename from packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutStateManagerTests.swift rename to packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift index 38ba0b24ca5f..6653ed75b90b 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutStateManagerTests.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift @@ -7,9 +7,9 @@ import XCTest @testable import quick_actions_ios -class ShortcutStateManagerTests: XCTestCase { +class DefaultShortcutItemParserTests: XCTestCase { - func testSetShortcutItems_shouldSetItem() { + func testparseShortcutItems() { let rawItem = [ "type": "SearchTheThing", "localizedTitle": "Search the thing", @@ -23,16 +23,12 @@ class ShortcutStateManagerTests: XCTestCase { icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), userInfo: nil) - let mockShortcutItemService = MockShortcutItemService() - let manager = DefaultShortcutStateManager(service: mockShortcutItemService) - - manager.setShortcutItems([rawItem]) - - XCTAssertEqual(mockShortcutItemService.shortcutItems, [expectedItem]) + let parser = DefaultShortcutItemParser() + XCTAssertEqual(parser.parseShortcutItems([rawItem]), [expectedItem]) } - func testSetShortcutItems_shouldSetItemWithoutIcon() { + func testparseShortcutItems_noIcon() { let rawItem: [String: Any] = [ "type": "SearchTheThing", "localizedTitle": "Search the thing", @@ -46,11 +42,7 @@ class ShortcutStateManagerTests: XCTestCase { icon: nil, userInfo: nil) - let mockShortcutItemService = MockShortcutItemService() - let manager = DefaultShortcutStateManager(service: mockShortcutItemService) - - manager.setShortcutItems([rawItem]) - - XCTAssertEqual(mockShortcutItemService.shortcutItems, [expectedItem]) + let parser = DefaultShortcutItemParser() + XCTAssertEqual(parser.parseShortcutItems([rawItem]), [expectedItem]) } } diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemService.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockAppShortcutController.swift similarity index 80% rename from packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemService.swift rename to packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockAppShortcutController.swift index 609858286cee..de2707e96c97 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemService.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockAppShortcutController.swift @@ -4,6 +4,6 @@ @testable import quick_actions_ios -final class MockShortcutItemService: ShortcutItemServicing { +final class MockAppShortcutController: AppShortcutControlling { var shortcutItems: [UIApplicationShortcutItem]? = nil } diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift new file mode 100644 index 000000000000..0b93ed21b317 --- /dev/null +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift @@ -0,0 +1,20 @@ +// +// MockShortcutItemParser.swift +// RunnerTests +// +// Created by Huan Lin on 11/3/22. +// Copyright © 2022 The Flutter Authors. All rights reserved. +// + +import Foundation + +@testable import quick_actions_ios + +final class MockShortcutItemParser: ShortcutItemParser { + + var parseShortcutItemsStub: ((_ items: [[String: Any]]) -> [UIApplicationShortcutItem])? = nil + + func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem] { + return parseShortcutItemsStub?(items) ?? [] + } +} diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutStateManager.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutStateManager.swift deleted file mode 100644 index e3b1d72e8240..000000000000 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutStateManager.swift +++ /dev/null @@ -1,13 +0,0 @@ -// 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. - -@testable import quick_actions_ios - -final class MockShortcutStateManager: ShortcutStateManaging { - var setShortcutItemsStub: (([[String: Any]]) -> Void)? - - func setShortcutItems(_ items: [[String: Any]]) { - setShortcutItemsStub?(items) - } -} diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift index 49cadc73b31d..cb36443801c9 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift @@ -15,17 +15,30 @@ class QuickActionsPluginTests: XCTestCase { "localizedTitle": "Search the thing", "icon": "search_the_thing.png", ] + let item = UIApplicationShortcutItem( + type: "SearchTheThing", + localizedTitle: "Search the thing", + localizedSubtitle: nil, + icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), + userInfo: nil) + let call = FlutterMethodCall(methodName: "setShortcutItems", arguments: [rawItem]) let mockChannel = MockMethodChannel() - let mockShortcutStateManager = MockShortcutStateManager() + let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemParser = MockShortcutItemParser() + let plugin = QuickActionsPlugin( - channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + channel: mockChannel, + appShortcutController: mockAppShortcutController, + shortcutItemParser: mockShortcutItemParser) - let setShortcutItemsExpectation = expectation(description: "setShortcutItems must be called.") - mockShortcutStateManager.setShortcutItemsStub = { items in + let parseShortcutItemsExpectation = expectation( + description: "parseShortcutItems must be called.") + mockShortcutItemParser.parseShortcutItemsStub = { items in XCTAssertEqual(items as? [[String: String]], [rawItem]) - setShortcutItemsExpectation.fulfill() + parseShortcutItemsExpectation.fulfill() + return [item] } let resultExpectation = expectation(description: "result block must be called.") @@ -33,22 +46,20 @@ class QuickActionsPluginTests: XCTestCase { XCTAssertNil(result, "result block must be called with nil.") resultExpectation.fulfill() } - + XCTAssertEqual(mockAppShortcutController.shortcutItems, [item], "Must set shortcut items.") waitForExpectations(timeout: 1) } func testHandleMethodCall_clearShortcutItems() { let call = FlutterMethodCall(methodName: "clearShortcutItems", arguments: nil) let mockChannel = MockMethodChannel() - let mockShortcutStateManager = MockShortcutStateManager() - let plugin = QuickActionsPlugin( - channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemParser = MockShortcutItemParser() - let setShortcutItemsExpectation = expectation(description: "setShortcutItems must be called.") - mockShortcutStateManager.setShortcutItemsStub = { items in - XCTAssert(items.isEmpty) - setShortcutItemsExpectation.fulfill() - } + let plugin = QuickActionsPlugin( + channel: mockChannel, + appShortcutController: mockAppShortcutController, + shortcutItemParser: mockShortcutItemParser) let resultExpectation = expectation(description: "result block must be called.") plugin.handle(call) { result in @@ -56,6 +67,7 @@ class QuickActionsPluginTests: XCTestCase { resultExpectation.fulfill() } + XCTAssertEqual(mockAppShortcutController.shortcutItems, [], "Must clear shortcut items.") waitForExpectations(timeout: 1) } @@ -63,9 +75,13 @@ class QuickActionsPluginTests: XCTestCase { let call = FlutterMethodCall(methodName: "getLaunchAction", arguments: nil) let mockChannel = MockMethodChannel() - let mockShortcutStateManager = MockShortcutStateManager() + let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemParser = MockShortcutItemParser() + let plugin = QuickActionsPlugin( - channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + channel: mockChannel, + appShortcutController: mockAppShortcutController, + shortcutItemParser: mockShortcutItemParser) let resultExpectation = expectation(description: "result block must be called.") plugin.handle(call) { result in @@ -80,9 +96,13 @@ class QuickActionsPluginTests: XCTestCase { let call = FlutterMethodCall(methodName: "nonExist", arguments: nil) let mockChannel = MockMethodChannel() - let mockShortcutStateManager = MockShortcutStateManager() + let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemParser = MockShortcutItemParser() + let plugin = QuickActionsPlugin( - channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + channel: mockChannel, + appShortcutController: mockAppShortcutController, + shortcutItemParser: mockShortcutItemParser) let resultExpectation = expectation(description: "result block must be called.") @@ -98,9 +118,13 @@ class QuickActionsPluginTests: XCTestCase { func testApplicationPerformActionForShortcutItem() { let mockChannel = MockMethodChannel() - let mockShortcutStateManager = MockShortcutStateManager() + let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemParser = MockShortcutItemParser() + let plugin = QuickActionsPlugin( - channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + channel: mockChannel, + appShortcutController: mockAppShortcutController, + shortcutItemParser: mockShortcutItemParser) let item = UIApplicationShortcutItem( type: "SearchTheThing", @@ -128,9 +152,13 @@ class QuickActionsPluginTests: XCTestCase { func testApplicationDidFinishLaunchingWithOptions_launchWithShortcut() { let mockChannel = MockMethodChannel() - let mockShortcutStateManager = MockShortcutStateManager() + let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemParser = MockShortcutItemParser() + let plugin = QuickActionsPlugin( - channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + channel: mockChannel, + appShortcutController: mockAppShortcutController, + shortcutItemParser: mockShortcutItemParser) let item = UIApplicationShortcutItem( type: "SearchTheThing", @@ -149,9 +177,13 @@ class QuickActionsPluginTests: XCTestCase { func testApplicationDidFinishLaunchingWithOptions_launchWithoutShortcut() { let mockChannel = MockMethodChannel() - let mockShortcutStateManager = MockShortcutStateManager() + let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemParser = MockShortcutItemParser() + let plugin = QuickActionsPlugin( - channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + channel: mockChannel, + appShortcutController: mockAppShortcutController, + shortcutItemParser: mockShortcutItemParser) let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [:]) XCTAssert( @@ -161,9 +193,13 @@ class QuickActionsPluginTests: XCTestCase { func testApplicationDidBecomeActive_launchWithoutShortcut() { let mockChannel = MockMethodChannel() - let mockShortcutStateManager = MockShortcutStateManager() + let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemParser = MockShortcutItemParser() + let plugin = QuickActionsPlugin( - channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + channel: mockChannel, + appShortcutController: mockAppShortcutController, + shortcutItemParser: mockShortcutItemParser) mockChannel.invokeMethodStub = { _, _ in XCTFail("invokeMethod should not be called if launch without shortcut.") @@ -186,9 +222,13 @@ class QuickActionsPluginTests: XCTestCase { userInfo: nil) let mockChannel = MockMethodChannel() - let mockShortcutStateManager = MockShortcutStateManager() + let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemParser = MockShortcutItemParser() + let plugin = QuickActionsPlugin( - channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + channel: mockChannel, + appShortcutController: mockAppShortcutController, + shortcutItemParser: mockShortcutItemParser) let invokeMethodExpectation = expectation(description: "invokeMethod must be called.") mockChannel.invokeMethodStub = { method, arguments in @@ -217,9 +257,13 @@ class QuickActionsPluginTests: XCTestCase { userInfo: nil) let mockChannel = MockMethodChannel() - let mockShortcutStateManager = MockShortcutStateManager() + let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemParser = MockShortcutItemParser() + let plugin = QuickActionsPlugin( - channel: mockChannel, shortcutStateManager: mockShortcutStateManager) + channel: mockChannel, + appShortcutController: mockAppShortcutController, + shortcutItemParser: mockShortcutItemParser) let invokeMethodExpectation = expectation(description: "invokeMethod must be called.") diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemServicing.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/AppShortcutControlling.swift similarity index 58% rename from packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemServicing.swift rename to packages/quick_actions/quick_actions_ios/ios/Classes/AppShortcutControlling.swift index 018891815eb4..11963860311c 100644 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemServicing.swift +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/AppShortcutControlling.swift @@ -4,12 +4,12 @@ import UIKit -/// Services `UIApplicationShortcutItem`s. -protocol ShortcutItemServicing: AnyObject { +/// Controlls app's shortcut behavior. +protocol AppShortcutControlling: AnyObject { /// An array of shortcut items for home screen. var shortcutItems: [UIApplicationShortcutItem]? { get set } } -/// A default implementation of the `ShortcutItemServicing` protocol. -extension UIApplication: ShortcutItemServicing {} +/// A default implementation of the `AppShortcutControlling` protocol. +extension UIApplication: AppShortcutControlling {} diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift index d65c9fba8f3e..236e7b0a6f5d 100644 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift @@ -16,16 +16,19 @@ public final class QuickActionsPlugin: NSObject, FlutterPlugin { } private let channel: MethodChannel - private let shortcutStateManager: ShortcutStateManaging + private let appShortcutController: AppShortcutControlling + private let shortcutItemParser: ShortcutItemParser /// The type of the shortcut item selected when launching the app. private var launchingShortcutType: String? = nil init( channel: MethodChannel, - shortcutStateManager: ShortcutStateManaging = DefaultShortcutStateManager() + appShortcutController: AppShortcutControlling = UIApplication.shared, + shortcutItemParser: ShortcutItemParser = DefaultShortcutItemParser() ) { self.channel = channel - self.shortcutStateManager = shortcutStateManager + self.appShortcutController = appShortcutController + self.shortcutItemParser = shortcutItemParser } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { @@ -33,10 +36,10 @@ public final class QuickActionsPlugin: NSObject, FlutterPlugin { case "setShortcutItems": // `arguments` must be an array of dictionaries let items = call.arguments as! [[String: Any]] - shortcutStateManager.setShortcutItems(items) + appShortcutController.shortcutItems = shortcutItemParser.parseShortcutItems(items) result(nil) case "clearShortcutItems": - shortcutStateManager.setShortcutItems([]) + appShortcutController.shortcutItems = [] result(nil) case "getLaunchAction": result(nil) diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift new file mode 100644 index 000000000000..5d8a8e9e7e0b --- /dev/null +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift @@ -0,0 +1,39 @@ +// 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 UIKit + +/// A parser that parses an array of raw shortcut items. +protocol ShortcutItemParser { + + /// Parses an array of raw shortcut items into an array of UIApplicationShortcutItems + /// + /// - Parameter items an array of raw shortcut items to be parsed. + /// - Returns an array of parsed shortcut items to be set. + /// + func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem] +} + +final class DefaultShortcutItemParser: ShortcutItemParser { + + func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem] { + return items.compactMap { deserializeShortcutItem(with: $0) } + } + + private func deserializeShortcutItem(with serialized: [String: Any]) -> UIApplicationShortcutItem + { + + let icon = (serialized["icon"] as? String).map { + UIApplicationShortcutIcon(templateImageName: $0) + } + + // type and localizedTitle are required. + return UIApplicationShortcutItem( + type: serialized["type"] as! String, + localizedTitle: serialized["localizedTitle"] as! String, + localizedSubtitle: nil, + icon: icon, + userInfo: nil) + } +} diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift deleted file mode 100644 index ae70f170366f..000000000000 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutStateManaging.swift +++ /dev/null @@ -1,46 +0,0 @@ -// 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 UIKit - -/// Manages the shortcut related states. -protocol ShortcutStateManaging { - - /// Sets the list of shortcut items. - /// - /// - Parameter items the list of shortcut items to be parsed and set. - func setShortcutItems(_ items: [[String: Any]]) -} - -/// A default implementation of `ShortcutStateManaging` protocol. -final class DefaultShortcutStateManager: ShortcutStateManaging { - - private let service: ShortcutItemServicing - - /// Creates a ShortcutStateManager - /// - Parameter service a shortcut item service, with `UIApplication.shared` as default. - init(service: ShortcutItemServicing = UIApplication.shared) { - self.service = service - } - - func setShortcutItems(_ items: [[String: Any]]) { - service.shortcutItems = items.compactMap { deserializeShortcutItem(with: $0) } - } - - private func deserializeShortcutItem(with serialized: [String: Any]) -> UIApplicationShortcutItem - { - - let icon = (serialized["icon"] as? String).map { - UIApplicationShortcutIcon(templateImageName: $0) - } - - // type and localizedTitle are required. - return UIApplicationShortcutItem( - type: serialized["type"] as! String, - localizedTitle: serialized["localizedTitle"] as! String, - localizedSubtitle: nil, - icon: icon, - userInfo: nil) - } -} From 009aadfcd2dfd93f340fbe38a416ca44eb8e4138 Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Thu, 3 Nov 2022 16:44:00 -0700 Subject: [PATCH 09/13] some nit --- .../DefaultShortcutItemParserTests.swift | 1 - .../ios/RunnerTests/QuickActionsPluginTests.swift | 13 +++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift index 6653ed75b90b..a6144a846269 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift @@ -25,7 +25,6 @@ class DefaultShortcutItemParserTests: XCTestCase { let parser = DefaultShortcutItemParser() XCTAssertEqual(parser.parseShortcutItems([rawItem]), [expectedItem]) - } func testparseShortcutItems_noIcon() { diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift index cb36443801c9..1336e1971762 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift @@ -51,11 +51,20 @@ class QuickActionsPluginTests: XCTestCase { } func testHandleMethodCall_clearShortcutItems() { + let item = UIApplicationShortcutItem( + type: "SearchTheThing", + localizedTitle: "Search the thing", + localizedSubtitle: nil, + icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), + userInfo: nil) + let call = FlutterMethodCall(methodName: "clearShortcutItems", arguments: nil) let mockChannel = MockMethodChannel() let mockAppShortcutController = MockAppShortcutController() let mockShortcutItemParser = MockShortcutItemParser() + mockAppShortcutController.shortcutItems = [item] + let plugin = QuickActionsPlugin( channel: mockChannel, appShortcutController: mockAppShortcutController, @@ -146,7 +155,6 @@ class QuickActionsPluginTests: XCTestCase { ) { success in /* no-op */ } XCTAssert(actionResult, "performActionForShortcutItem must return true.") - waitForExpectations(timeout: 1) } @@ -172,7 +180,6 @@ class QuickActionsPluginTests: XCTestCase { didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item]) XCTAssertFalse( launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.") - } func testApplicationDidFinishLaunchingWithOptions_launchWithoutShortcut() { @@ -188,7 +195,6 @@ class QuickActionsPluginTests: XCTestCase { let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [:]) XCTAssert( launchResult, "didFinishLaunchingWithOptions must return true if not launched from shortcut.") - } func testApplicationDidBecomeActive_launchWithoutShortcut() { @@ -210,7 +216,6 @@ class QuickActionsPluginTests: XCTestCase { launchResult, "didFinishLaunchingWithOptions must return true if not launched from shortcut.") plugin.applicationDidBecomeActive(UIApplication.shared) - } func testApplicationDidBecomeActive_launchWithShortcut() { From d30e077d31b30456fef8505c88c7f12009c7c394 Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Mon, 7 Nov 2022 12:40:31 -0800 Subject: [PATCH 10/13] rename AppShortcutControlling to ShortcutItemProviding --- .../ios/Runner.xcodeproj/project.pbxproj | 8 ++-- ...r.swift => MockShortcutItemProvider.swift} | 2 +- .../RunnerTests/QuickActionsPluginTests.swift | 46 +++++++++---------- .../ios/Classes/QuickActionsPlugin.swift | 10 ++-- ...ling.swift => ShortcutItemProviding.swift} | 8 ++-- 5 files changed, 37 insertions(+), 37 deletions(-) rename packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/{MockAppShortcutController.swift => MockShortcutItemProvider.swift} (80%) rename packages/quick_actions/quick_actions_ios/ios/Classes/{AppShortcutControlling.swift => ShortcutItemProviding.swift} (54%) diff --git a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj index 862128018c52..f5b708bbb54b 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj @@ -18,7 +18,7 @@ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */; }; E092A7EE28D10802005C7F67 /* QuickActionsPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */; }; - E092A7F128D10890005C7F67 /* MockAppShortcutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F028D10890005C7F67 /* MockAppShortcutController.swift */; }; + E092A7F128D10890005C7F67 /* MockShortcutItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */; }; E092A7F428D110B3005C7F67 /* DefaultShortcutItemParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */; }; E092A7F628D128EB005C7F67 /* RunnerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F528D128EB005C7F67 /* RunnerUITests.swift */; }; E0A075D529147FE200329BAE /* MockShortcutItemParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */; }; @@ -80,7 +80,7 @@ C35AD3650AB6BF850E016715 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockMethodChannel.swift; sourceTree = ""; }; E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickActionsPluginTests.swift; sourceTree = ""; }; - E092A7F028D10890005C7F67 /* MockAppShortcutController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAppShortcutController.swift; sourceTree = ""; }; + E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemProvider.swift; sourceTree = ""; }; E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultShortcutItemParserTests.swift; sourceTree = ""; }; E092A7F528D128EB005C7F67 /* RunnerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerUITests.swift; sourceTree = ""; }; E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemParser.swift; sourceTree = ""; }; @@ -216,7 +216,7 @@ isa = PBXGroup; children = ( E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */, - E092A7F028D10890005C7F67 /* MockAppShortcutController.swift */, + E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */, E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */, ); path = Mocks; @@ -436,7 +436,7 @@ files = ( E092A7EE28D10802005C7F67 /* QuickActionsPluginTests.swift in Sources */, E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */, - E092A7F128D10890005C7F67 /* MockAppShortcutController.swift in Sources */, + E092A7F128D10890005C7F67 /* MockShortcutItemProvider.swift in Sources */, E0A075D529147FE200329BAE /* MockShortcutItemParser.swift in Sources */, E092A7F428D110B3005C7F67 /* DefaultShortcutItemParserTests.swift in Sources */, ); diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockAppShortcutController.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift similarity index 80% rename from packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockAppShortcutController.swift rename to packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift index de2707e96c97..85477415667e 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockAppShortcutController.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift @@ -4,6 +4,6 @@ @testable import quick_actions_ios -final class MockAppShortcutController: AppShortcutControlling { +final class MockShortcutItemProvider: ShortcutItemProviding { var shortcutItems: [UIApplicationShortcutItem]? = nil } diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift index 1336e1971762..268a89ba5a5b 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift @@ -25,12 +25,12 @@ class QuickActionsPluginTests: XCTestCase { let call = FlutterMethodCall(methodName: "setShortcutItems", arguments: [rawItem]) let mockChannel = MockMethodChannel() - let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, - appShortcutController: mockAppShortcutController, + shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let parseShortcutItemsExpectation = expectation( @@ -46,7 +46,7 @@ class QuickActionsPluginTests: XCTestCase { XCTAssertNil(result, "result block must be called with nil.") resultExpectation.fulfill() } - XCTAssertEqual(mockAppShortcutController.shortcutItems, [item], "Must set shortcut items.") + XCTAssertEqual(mockShortcutItemProvider.shortcutItems, [item], "Must set shortcut items.") waitForExpectations(timeout: 1) } @@ -60,14 +60,14 @@ class QuickActionsPluginTests: XCTestCase { let call = FlutterMethodCall(methodName: "clearShortcutItems", arguments: nil) let mockChannel = MockMethodChannel() - let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() - mockAppShortcutController.shortcutItems = [item] + mockShortcutItemProvider.shortcutItems = [item] let plugin = QuickActionsPlugin( channel: mockChannel, - appShortcutController: mockAppShortcutController, + shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let resultExpectation = expectation(description: "result block must be called.") @@ -76,7 +76,7 @@ class QuickActionsPluginTests: XCTestCase { resultExpectation.fulfill() } - XCTAssertEqual(mockAppShortcutController.shortcutItems, [], "Must clear shortcut items.") + XCTAssertEqual(mockShortcutItemProvider.shortcutItems, [], "Must clear shortcut items.") waitForExpectations(timeout: 1) } @@ -84,12 +84,12 @@ class QuickActionsPluginTests: XCTestCase { let call = FlutterMethodCall(methodName: "getLaunchAction", arguments: nil) let mockChannel = MockMethodChannel() - let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, - appShortcutController: mockAppShortcutController, + shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let resultExpectation = expectation(description: "result block must be called.") @@ -105,12 +105,12 @@ class QuickActionsPluginTests: XCTestCase { let call = FlutterMethodCall(methodName: "nonExist", arguments: nil) let mockChannel = MockMethodChannel() - let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, - appShortcutController: mockAppShortcutController, + shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let resultExpectation = expectation(description: "result block must be called.") @@ -127,12 +127,12 @@ class QuickActionsPluginTests: XCTestCase { func testApplicationPerformActionForShortcutItem() { let mockChannel = MockMethodChannel() - let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, - appShortcutController: mockAppShortcutController, + shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let item = UIApplicationShortcutItem( @@ -160,12 +160,12 @@ class QuickActionsPluginTests: XCTestCase { func testApplicationDidFinishLaunchingWithOptions_launchWithShortcut() { let mockChannel = MockMethodChannel() - let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, - appShortcutController: mockAppShortcutController, + shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let item = UIApplicationShortcutItem( @@ -184,12 +184,12 @@ class QuickActionsPluginTests: XCTestCase { func testApplicationDidFinishLaunchingWithOptions_launchWithoutShortcut() { let mockChannel = MockMethodChannel() - let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, - appShortcutController: mockAppShortcutController, + shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [:]) @@ -199,12 +199,12 @@ class QuickActionsPluginTests: XCTestCase { func testApplicationDidBecomeActive_launchWithoutShortcut() { let mockChannel = MockMethodChannel() - let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, - appShortcutController: mockAppShortcutController, + shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) mockChannel.invokeMethodStub = { _, _ in @@ -227,12 +227,12 @@ class QuickActionsPluginTests: XCTestCase { userInfo: nil) let mockChannel = MockMethodChannel() - let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, - appShortcutController: mockAppShortcutController, + shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let invokeMethodExpectation = expectation(description: "invokeMethod must be called.") @@ -262,12 +262,12 @@ class QuickActionsPluginTests: XCTestCase { userInfo: nil) let mockChannel = MockMethodChannel() - let mockAppShortcutController = MockAppShortcutController() + let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, - appShortcutController: mockAppShortcutController, + shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let invokeMethodExpectation = expectation(description: "invokeMethod must be called.") diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift index 236e7b0a6f5d..8522c5ff5288 100644 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift @@ -16,18 +16,18 @@ public final class QuickActionsPlugin: NSObject, FlutterPlugin { } private let channel: MethodChannel - private let appShortcutController: AppShortcutControlling + private let shortcutItemProvider: ShortcutItemProviding private let shortcutItemParser: ShortcutItemParser /// The type of the shortcut item selected when launching the app. private var launchingShortcutType: String? = nil init( channel: MethodChannel, - appShortcutController: AppShortcutControlling = UIApplication.shared, + shortcutItemProvider: ShortcutItemProviding = UIApplication.shared, shortcutItemParser: ShortcutItemParser = DefaultShortcutItemParser() ) { self.channel = channel - self.appShortcutController = appShortcutController + self.shortcutItemProvider = shortcutItemProvider self.shortcutItemParser = shortcutItemParser } @@ -36,10 +36,10 @@ public final class QuickActionsPlugin: NSObject, FlutterPlugin { case "setShortcutItems": // `arguments` must be an array of dictionaries let items = call.arguments as! [[String: Any]] - appShortcutController.shortcutItems = shortcutItemParser.parseShortcutItems(items) + shortcutItemProvider.shortcutItems = shortcutItemParser.parseShortcutItems(items) result(nil) case "clearShortcutItems": - appShortcutController.shortcutItems = [] + shortcutItemProvider.shortcutItems = [] result(nil) case "getLaunchAction": result(nil) diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/AppShortcutControlling.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemProviding.swift similarity index 54% rename from packages/quick_actions/quick_actions_ios/ios/Classes/AppShortcutControlling.swift rename to packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemProviding.swift index 11963860311c..e8854863bf95 100644 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/AppShortcutControlling.swift +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemProviding.swift @@ -4,12 +4,12 @@ import UIKit -/// Controlls app's shortcut behavior. -protocol AppShortcutControlling: AnyObject { +/// Provides the capability to get and set the app's home screen shortcut items. +protocol ShortcutItemProviding: AnyObject { /// An array of shortcut items for home screen. var shortcutItems: [UIApplicationShortcutItem]? { get set } } -/// A default implementation of the `AppShortcutControlling` protocol. -extension UIApplication: AppShortcutControlling {} +/// A default implementation of the `ShortcutItemProviding` protocol. +extension UIApplication: ShortcutItemProviding {} From f354302630db7335c561775aec47732afbcf242f Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Mon, 7 Nov 2022 12:49:11 -0800 Subject: [PATCH 11/13] nit --- .../quick_actions_ios/ios/Classes/ShortcutItemParser.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift index 5d8a8e9e7e0b..5a170c7ab677 100644 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift @@ -15,6 +15,7 @@ protocol ShortcutItemParser { func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem] } +/// A default implementation of the `ShortcutItemParser` protocol. final class DefaultShortcutItemParser: ShortcutItemParser { func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem] { From 6f55400d830f1e03c570169a40c4f11e18891263 Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Mon, 7 Nov 2022 14:19:38 -0800 Subject: [PATCH 12/13] do not crash if no type or title --- .../DefaultShortcutItemParserTests.swift | 24 +++++++++++++++++-- .../ios/Classes/ShortcutItemParser.swift | 12 +++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift index a6144a846269..739f88e90454 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift @@ -9,7 +9,7 @@ import XCTest class DefaultShortcutItemParserTests: XCTestCase { - func testparseShortcutItems() { + func testParseShortcutItems() { let rawItem = [ "type": "SearchTheThing", "localizedTitle": "Search the thing", @@ -27,7 +27,7 @@ class DefaultShortcutItemParserTests: XCTestCase { XCTAssertEqual(parser.parseShortcutItems([rawItem]), [expectedItem]) } - func testparseShortcutItems_noIcon() { + func testParseShortcutItems_noIcon() { let rawItem: [String: Any] = [ "type": "SearchTheThing", "localizedTitle": "Search the thing", @@ -44,4 +44,24 @@ class DefaultShortcutItemParserTests: XCTestCase { let parser = DefaultShortcutItemParser() XCTAssertEqual(parser.parseShortcutItems([rawItem]), [expectedItem]) } + + func testParseShortcutItems_noType() { + let rawItem = [ + "localizedTitle": "Search the thing", + "icon": "search_the_thing.png", + ] + + let parser = DefaultShortcutItemParser() + XCTAssertEqual(parser.parseShortcutItems([rawItem]), []) + } + + func testParseShortcutItems_noLocalizedTitle() { + let rawItem = [ + "type": "SearchTheThing", + "icon": "search_the_thing.png", + ] + + let parser = DefaultShortcutItemParser() + XCTAssertEqual(parser.parseShortcutItems([rawItem]), []) + } } diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift index 5a170c7ab677..0945b4a386f8 100644 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift @@ -22,8 +22,14 @@ final class DefaultShortcutItemParser: ShortcutItemParser { return items.compactMap { deserializeShortcutItem(with: $0) } } - private func deserializeShortcutItem(with serialized: [String: Any]) -> UIApplicationShortcutItem + private func deserializeShortcutItem(with serialized: [String: Any]) -> UIApplicationShortcutItem? { + guard + let type = serialized["type"] as? String, + let localizedTitle = serialized["localizedTitle"] as? String + else { + return nil + } let icon = (serialized["icon"] as? String).map { UIApplicationShortcutIcon(templateImageName: $0) @@ -31,8 +37,8 @@ final class DefaultShortcutItemParser: ShortcutItemParser { // type and localizedTitle are required. return UIApplicationShortcutItem( - type: serialized["type"] as! String, - localizedTitle: serialized["localizedTitle"] as! String, + type: type, + localizedTitle: localizedTitle, localizedSubtitle: nil, icon: icon, userInfo: nil) From 76d835e3ea74b236e7bbb93908e4d1fc8c0363a2 Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Mon, 14 Nov 2022 14:10:58 -0800 Subject: [PATCH 13/13] update license --- .../ios/RunnerTests/Mocks/MockShortcutItemParser.swift | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift index 0b93ed21b317..3b5a09653958 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift @@ -1,10 +1,6 @@ -// -// MockShortcutItemParser.swift -// RunnerTests -// -// Created by Huan Lin on 11/3/22. -// Copyright © 2022 The Flutter Authors. All rights reserved. -// +// 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 Foundation