diff --git a/packages/google_sign_in/google_sign_in/CHANGELOG.md b/packages/google_sign_in/google_sign_in/CHANGELOG.md index cb4a65f42fa2..186a1d39a223 100644 --- a/packages/google_sign_in/google_sign_in/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 5.0.5 * Add iOS unit and UI integration test targets. +* Add iOS unit test module map. * Exclude arm64 simulators in example app. ## 5.0.4 diff --git a/packages/google_sign_in/google_sign_in/example/ios/Flutter/AppFrameworkInfo.plist b/packages/google_sign_in/google_sign_in/example/ios/Flutter/AppFrameworkInfo.plist index 6c2de8086bcd..3a9c234f96d4 100644 --- a/packages/google_sign_in/google_sign_in/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/google_sign_in/google_sign_in/example/ios/Flutter/AppFrameworkInfo.plist @@ -25,6 +25,6 @@ arm64 MinimumOSVersion - 8.0 + 9.0 diff --git a/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/project.pbxproj index 0c3cc430d23e..06857ed2bd59 100644 --- a/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/project.pbxproj @@ -539,7 +539,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -589,7 +589,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/packages/google_sign_in/google_sign_in/example/ios/RunnerTests/GoogleSignInTests.m b/packages/google_sign_in/google_sign_in/example/ios/RunnerTests/GoogleSignInTests.m index adbf61326c8d..6f8b821a5299 100644 --- a/packages/google_sign_in/google_sign_in/example/ios/RunnerTests/GoogleSignInTests.m +++ b/packages/google_sign_in/google_sign_in/example/ios/RunnerTests/GoogleSignInTests.m @@ -6,6 +6,7 @@ @import XCTest; @import google_sign_in; +@import google_sign_in.Test; @import GoogleSignIn; // OCMock library doesn't generate a valid modulemap. @@ -16,7 +17,7 @@ @interface FLTGoogleSignInPluginTest : XCTestCase @property(strong, nonatomic) NSObject *mockBinaryMessenger; @property(strong, nonatomic) NSObject *mockPluginRegistrar; @property(strong, nonatomic) FLTGoogleSignInPlugin *plugin; -@property(strong, nonatomic) GIDSignIn *mockSharedInstance; +@property(strong, nonatomic) id mockSignIn; @end @@ -26,39 +27,377 @@ - (void)setUp { [super setUp]; self.mockBinaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); self.mockPluginRegistrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); - self.mockSharedInstance = [OCMockObject partialMockForObject:[GIDSignIn sharedInstance]]; + + id mockSignIn = OCMClassMock([GIDSignIn class]); + self.mockSignIn = mockSignIn; + OCMStub(self.mockPluginRegistrar.messenger).andReturn(self.mockBinaryMessenger); - self.plugin = [[FLTGoogleSignInPlugin alloc] init]; + self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:mockSignIn]; [FLTGoogleSignInPlugin registerWithRegistrar:self.mockPluginRegistrar]; } -- (void)tearDown { - [((OCMockObject *)self.mockSharedInstance) stopMocking]; - [super tearDown]; +- (void)testUnimplementedMethod { + FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"bogus" + arguments:nil]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; + [self.plugin handleMethodCall:methodCall + result:^(id result) { + XCTAssertEqualObjects(result, FlutterMethodNotImplemented); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testSignOut { + FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signOut" + arguments:nil]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; + [self.plugin handleMethodCall:methodCall + result:^(id result) { + XCTAssertNil(result); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; + OCMVerify([self.mockSignIn signOut]); +} + +- (void)testDisconnect { + FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"disconnect" + arguments:nil]; + + [self.plugin handleMethodCall:methodCall + result:^(id result){ + }]; + OCMVerify([self.mockSignIn disconnect]); +} + +- (void)testClearAuthCache { + FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"clearAuthCache" + arguments:nil]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; + [self.plugin handleMethodCall:methodCall + result:^(id result) { + XCTAssertNil(result); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Init + +- (void)testInitGamesSignInUnsupported { + FlutterMethodCall *methodCall = + [FlutterMethodCall methodCallWithMethodName:@"init" + arguments:@{@"signInOption" : @"SignInOption.games"}]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; + [self.plugin handleMethodCall:methodCall + result:^(FlutterError *result) { + XCTAssertEqualObjects(result.code, @"unsupported-options"); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testInitGoogleServiceInfoPlist { + FlutterMethodCall *methodCall = [FlutterMethodCall + methodCallWithMethodName:@"init" + arguments:@{@"scopes" : @[ @"mockScope1" ], @"hostedDomain" : @"example.com"}]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; + [self.plugin handleMethodCall:methodCall + result:^(id result) { + XCTAssertNil(result); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; + + id mockSignIn = self.mockSignIn; + OCMVerify([mockSignIn setScopes:@[ @"mockScope1" ]]); + OCMVerify([mockSignIn setHostedDomain:@"example.com"]); + + // Set in example app GoogleService-Info.plist. + OCMVerify([mockSignIn + setClientID:@"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"]); + OCMVerify([mockSignIn setServerClientID:@"YOUR_SERVER_CLIENT_ID"]); +} + +- (void)testInitNullDomain { + FlutterMethodCall *methodCall = + [FlutterMethodCall methodCallWithMethodName:@"init" + arguments:@{@"hostedDomain" : [NSNull null]}]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; + [self.plugin handleMethodCall:methodCall + result:^(id r) { + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; + OCMVerify([self.mockSignIn setHostedDomain:nil]); +} + +- (void)testInitDynamicClientId { + FlutterMethodCall *methodCall = + [FlutterMethodCall methodCallWithMethodName:@"init" + arguments:@{@"clientId" : @"mockClientId"}]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; + [self.plugin handleMethodCall:methodCall + result:^(id r) { + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; + OCMVerify([self.mockSignIn setClientID:@"mockClientId"]); +} + +#pragma mark - Is signed in + +- (void)testIsNotSignedIn { + OCMStub([self.mockSignIn hasPreviousSignIn]).andReturn(NO); + + FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"isSignedIn" + arguments:nil]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; + [self.plugin handleMethodCall:methodCall + result:^(NSNumber *result) { + XCTAssertFalse(result.boolValue); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testIsSignedIn { + OCMStub([self.mockSignIn hasPreviousSignIn]).andReturn(YES); + + FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"isSignedIn" + arguments:nil]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; + [self.plugin handleMethodCall:methodCall + result:^(NSNumber *result) { + XCTAssertTrue(result.boolValue); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Sign in silently + +- (void)testSignInSilently { + OCMExpect([self.mockSignIn restorePreviousSignIn]); + + FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signInSilently" + arguments:nil]; + + [self.plugin handleMethodCall:methodCall + result:^(id result){ + }]; + OCMVerifyAll(self.mockSignIn); +} + +- (void)testSignInSilentlyFailsConcurrently { + FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signInSilently" + arguments:nil]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; + + OCMExpect([self.mockSignIn restorePreviousSignIn]).andDo(^(NSInvocation *invocation) { + // Simulate calling the same method while the previous one is in flight. + [self.plugin handleMethodCall:methodCall + result:^(FlutterError *result) { + XCTAssertEqualObjects(result.code, @"concurrent-requests"); + [expectation fulfill]; + }]; + }); + + [self.plugin handleMethodCall:methodCall + result:^(id result){ + }]; + + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Sign in + +- (void)testSignIn { + FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn" + arguments:nil]; + + [self.plugin handleMethodCall:methodCall + result:^(NSNumber *result){ + }]; + + id mockSignIn = self.mockSignIn; + OCMVerify([mockSignIn + setPresentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]]]); + OCMVerify([mockSignIn signIn]); +} + +- (void)testSignInExecption { + FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn" + arguments:nil]; + OCMExpect([self.mockSignIn signIn]) + .andThrow([NSException exceptionWithName:@"MockName" reason:@"MockReason" userInfo:nil]); + + __block FlutterError *error; + XCTAssertThrows([self.plugin handleMethodCall:methodCall + result:^(FlutterError *result) { + error = result; + }]); + + XCTAssertEqualObjects(error.code, @"google_sign_in"); + XCTAssertEqualObjects(error.message, @"MockReason"); + XCTAssertEqualObjects(error.details, @"MockName"); +} + +#pragma mark - Get tokens + +- (void)testGetTokens { + id mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + + id mockAuthentication = OCMClassMock([GIDAuthentication class]); + OCMStub([mockAuthentication idToken]).andReturn(@"mockIdToken"); + OCMStub([mockAuthentication accessToken]).andReturn(@"mockAccessToken"); + [[mockAuthentication stub] + getTokensWithHandler:[OCMArg invokeBlockWithArgs:mockAuthentication, [NSNull null], nil]]; + OCMStub([mockUser authentication]).andReturn(mockAuthentication); + + FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"getTokens" + arguments:nil]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; + [self.plugin handleMethodCall:methodCall + result:^(NSDictionary *result) { + XCTAssertEqualObjects(result[@"idToken"], @"mockIdToken"); + XCTAssertEqualObjects(result[@"accessToken"], @"mockAccessToken"); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; } +- (void)testGetTokensNoAuthKeychainError { + id mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + + id mockAuthentication = OCMClassMock([GIDAuthentication class]); + NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain + code:kGIDSignInErrorCodeHasNoAuthInKeychain + userInfo:nil]; + [[mockAuthentication stub] + getTokensWithHandler:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + OCMStub([mockUser authentication]).andReturn(mockAuthentication); + + FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"getTokens" + arguments:nil]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; + [self.plugin handleMethodCall:methodCall + result:^(FlutterError *result) { + XCTAssertEqualObjects(result.code, @"sign_in_required"); + XCTAssertEqualObjects(result.message, kGIDSignInErrorDomain); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGetTokensCancelledError { + id mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + + id mockAuthentication = OCMClassMock([GIDAuthentication class]); + NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain + code:kGIDSignInErrorCodeCanceled + userInfo:nil]; + [[mockAuthentication stub] + getTokensWithHandler:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + OCMStub([mockUser authentication]).andReturn(mockAuthentication); + + FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"getTokens" + arguments:nil]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; + [self.plugin handleMethodCall:methodCall + result:^(FlutterError *result) { + XCTAssertEqualObjects(result.code, @"sign_in_canceled"); + XCTAssertEqualObjects(result.message, kGIDSignInErrorDomain); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGetTokensURLError { + id mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + + id mockAuthentication = OCMClassMock([GIDAuthentication class]); + NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:nil]; + [[mockAuthentication stub] + getTokensWithHandler:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + OCMStub([mockUser authentication]).andReturn(mockAuthentication); + + FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"getTokens" + arguments:nil]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; + [self.plugin handleMethodCall:methodCall + result:^(FlutterError *result) { + XCTAssertEqualObjects(result.code, @"network_error"); + XCTAssertEqualObjects(result.message, NSURLErrorDomain); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +- (void)testGetTokensUnknownError { + id mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + + id mockAuthentication = OCMClassMock([GIDAuthentication class]); + NSError *error = [NSError errorWithDomain:@"BogusDomain" code:42 userInfo:nil]; + [[mockAuthentication stub] + getTokensWithHandler:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + OCMStub([mockUser authentication]).andReturn(mockAuthentication); + + FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"getTokens" + arguments:nil]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; + [self.plugin handleMethodCall:methodCall + result:^(FlutterError *result) { + XCTAssertEqualObjects(result.code, @"sign_in_failed"); + XCTAssertEqualObjects(result.message, @"BogusDomain"); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +#pragma mark - Request scopes + - (void)testRequestScopesResultErrorIfNotSignedIn { - OCMStub(self.mockSharedInstance.currentUser).andReturn(nil); + OCMStub([self.mockSignIn currentUser]).andReturn(nil); FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"requestScopes" arguments:@{@"scopes" : @[ @"mockScope1" ]}]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - __block id result; [self.plugin handleMethodCall:methodCall - result:^(id r) { + result:^(FlutterError *result) { + XCTAssertEqualObjects(result.code, @"sign_in_required"); [expectation fulfill]; - result = r; }]; - [self waitForExpectations:@[ expectation ] timeout:5]; - XCTAssertEqualObjects([((FlutterError *)result) code], @"sign_in_required"); + [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testRequestScopesIfNoMissingScope { // Mock Google Signin internal calls - GIDGoogleUser *mockUser = OCMClassMock(GIDGoogleUser.class); - OCMStub(self.mockSharedInstance.currentUser).andReturn(mockUser); + GIDGoogleUser *mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); NSArray *requestedScopes = @[ @"mockScope1" ]; OCMStub(mockUser.grantedScopes).andReturn(requestedScopes); FlutterMethodCall *methodCall = @@ -66,22 +405,22 @@ - (void)testRequestScopesIfNoMissingScope { arguments:@{@"scopes" : requestedScopes}]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - __block id result; [self.plugin handleMethodCall:methodCall - result:^(id r) { + result:^(NSNumber *result) { + XCTAssertTrue(result.boolValue); [expectation fulfill]; - result = r; }]; - [self waitForExpectations:@[ expectation ] timeout:5]; - XCTAssertTrue([result boolValue]); + [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testRequestScopesRequestsIfNotGranted { // Mock Google Signin internal calls - GIDGoogleUser *mockUser = OCMClassMock(GIDGoogleUser.class); - OCMStub(self.mockSharedInstance.currentUser).andReturn(mockUser); + GIDGoogleUser *mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); NSArray *requestedScopes = @[ @"mockScope1" ]; OCMStub(mockUser.grantedScopes).andReturn(@[]); + id mockSignIn = self.mockSignIn; + OCMStub([mockSignIn scopes]).andReturn(@[]); FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"requestScopes" @@ -91,19 +430,19 @@ - (void)testRequestScopesRequestsIfNotGranted { result:^(id r){ }]; - XCTAssertTrue([self.mockSharedInstance.scopes containsObject:@"mockScope1"]); - OCMVerify([self.mockSharedInstance signIn]); + OCMVerify([mockSignIn setScopes:@[ @"mockScope1" ]]); + OCMVerify([mockSignIn signIn]); } - (void)testRequestScopesReturnsFalseIfNotGranted { // Mock Google Signin internal calls - GIDGoogleUser *mockUser = OCMClassMock(GIDGoogleUser.class); - OCMStub(self.mockSharedInstance.currentUser).andReturn(mockUser); + GIDGoogleUser *mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); NSArray *requestedScopes = @[ @"mockScope1" ]; OCMStub(mockUser.grantedScopes).andReturn(@[]); - OCMStub([self.mockSharedInstance signIn]).andDo(^(NSInvocation *invocation) { - [((NSObject *)self.plugin) signIn:self.mockSharedInstance + OCMStub([self.mockSignIn signIn]).andDo(^(NSInvocation *invocation) { + [((NSObject *)self.plugin) signIn:self.mockSignIn didSignInForUser:mockUser withError:nil]; }); @@ -113,27 +452,25 @@ - (void)testRequestScopesReturnsFalseIfNotGranted { arguments:@{@"scopes" : requestedScopes}]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns false"]; - __block id result; [self.plugin handleMethodCall:methodCall - result:^(id r) { + result:^(NSNumber *result) { + XCTAssertFalse(result.boolValue); [expectation fulfill]; - result = r; }]; - [self waitForExpectations:@[ expectation ] timeout:5]; - XCTAssertFalse([result boolValue]); + [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testRequestScopesReturnsTrueIfGranted { // Mock Google Signin internal calls - GIDGoogleUser *mockUser = OCMClassMock(GIDGoogleUser.class); - OCMStub(self.mockSharedInstance.currentUser).andReturn(mockUser); + GIDGoogleUser *mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); NSArray *requestedScopes = @[ @"mockScope1" ]; NSMutableArray *availableScopes = [NSMutableArray new]; OCMStub(mockUser.grantedScopes).andReturn(availableScopes); - OCMStub([self.mockSharedInstance signIn]).andDo(^(NSInvocation *invocation) { + OCMStub([self.mockSignIn signIn]).andDo(^(NSInvocation *invocation) { [availableScopes addObject:@"mockScope1"]; - [((NSObject *)self.plugin) signIn:self.mockSharedInstance + [((NSObject *)self.plugin) signIn:self.mockSignIn didSignInForUser:mockUser withError:nil]; }); @@ -143,14 +480,12 @@ - (void)testRequestScopesReturnsTrueIfGranted { arguments:@{@"scopes" : requestedScopes}]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; - __block id result; [self.plugin handleMethodCall:methodCall - result:^(id r) { + result:^(NSNumber *result) { + XCTAssertTrue(result.boolValue); [expectation fulfill]; - result = r; }]; - [self waitForExpectations:@[ expectation ] timeout:5]; - XCTAssertTrue([result boolValue]); + [self waitForExpectationsWithTimeout:5.0 handler:nil]; } @end diff --git a/packages/google_sign_in/google_sign_in/ios/Classes/FLTGoogleSignInPlugin.m b/packages/google_sign_in/google_sign_in/ios/Classes/FLTGoogleSignInPlugin.m index 578f64d5a41c..d13d64d2ba04 100644 --- a/packages/google_sign_in/google_sign_in/ios/Classes/FLTGoogleSignInPlugin.m +++ b/packages/google_sign_in/google_sign_in/ios/Classes/FLTGoogleSignInPlugin.m @@ -3,6 +3,8 @@ // found in the LICENSE file. #import "FLTGoogleSignInPlugin.h" +#import "FLTGoogleSignInPlugin_Test.h" + #import // The key within `GoogleService-Info.plist` used to hold the application's @@ -35,11 +37,15 @@ } @interface FLTGoogleSignInPlugin () +@property(strong, readonly) GIDSignIn *signIn; + +// Redeclared as not a designated initializer. +- (instancetype)init; @end @implementation FLTGoogleSignInPlugin { FlutterResult _accountRequest; - NSArray *_additionalScopesRequest; + NSArray *_additionalScopesRequest; } + (void)registerWithRegistrar:(NSObject *)registrar { @@ -52,9 +58,14 @@ + (void)registerWithRegistrar:(NSObject *)registrar { } - (instancetype)init { + return [self initWithSignIn:GIDSignIn.sharedInstance]; +} + +- (instancetype)initWithSignIn:(GIDSignIn *)signIn { self = [super init]; if (self) { - [GIDSignIn sharedInstance].delegate = self; + _signIn = signIn; + _signIn.delegate = self; // On the iOS simulator, we get "Broken pipe" errors after sign-in for some // unknown reason. We can avoid crashing the app by ignoring them. @@ -76,22 +87,22 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result NSString *path = [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"]; if (path) { - NSMutableDictionary *plist = [[NSMutableDictionary alloc] initWithContentsOfFile:path]; - BOOL hasDynamicClientId = - [[call.arguments valueForKey:@"clientId"] isKindOfClass:[NSString class]]; + NSMutableDictionary *plist = + [[NSMutableDictionary alloc] initWithContentsOfFile:path]; + BOOL hasDynamicClientId = [call.arguments[@"clientId"] isKindOfClass:[NSString class]]; if (hasDynamicClientId) { - [GIDSignIn sharedInstance].clientID = [call.arguments valueForKey:@"clientId"]; + self.signIn.clientID = call.arguments[@"clientId"]; } else { - [GIDSignIn sharedInstance].clientID = plist[kClientIdKey]; + self.signIn.clientID = plist[kClientIdKey]; } - [GIDSignIn sharedInstance].serverClientID = plist[kServerClientIdKey]; - [GIDSignIn sharedInstance].scopes = call.arguments[@"scopes"]; + self.signIn.serverClientID = plist[kServerClientIdKey]; + self.signIn.scopes = call.arguments[@"scopes"]; if (call.arguments[@"hostedDomain"] == [NSNull null]) { - [GIDSignIn sharedInstance].hostedDomain = nil; + self.signIn.hostedDomain = nil; } else { - [GIDSignIn sharedInstance].hostedDomain = call.arguments[@"hostedDomain"]; + self.signIn.hostedDomain = call.arguments[@"hostedDomain"]; } result(nil); } else { @@ -102,23 +113,23 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result } } else if ([call.method isEqualToString:@"signInSilently"]) { if ([self setAccountRequest:result]) { - [[GIDSignIn sharedInstance] restorePreviousSignIn]; + [self.signIn restorePreviousSignIn]; } } else if ([call.method isEqualToString:@"isSignedIn"]) { - result(@([[GIDSignIn sharedInstance] hasPreviousSignIn])); + result(@([self.signIn hasPreviousSignIn])); } else if ([call.method isEqualToString:@"signIn"]) { - [GIDSignIn sharedInstance].presentingViewController = [self topViewController]; + self.signIn.presentingViewController = [self topViewController]; if ([self setAccountRequest:result]) { @try { - [[GIDSignIn sharedInstance] signIn]; + [self.signIn signIn]; } @catch (NSException *e) { result([FlutterError errorWithCode:@"google_sign_in" message:e.reason details:e.name]); [e raise]; } } } else if ([call.method isEqualToString:@"getTokens"]) { - GIDGoogleUser *currentUser = [GIDSignIn sharedInstance].currentUser; + GIDGoogleUser *currentUser = self.signIn.currentUser; GIDAuthentication *auth = currentUser.authentication; [auth getTokensWithHandler:^void(GIDAuthentication *authentication, NSError *error) { result(error != nil ? getFlutterError(error) : @{ @@ -127,18 +138,18 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result }); }]; } else if ([call.method isEqualToString:@"signOut"]) { - [[GIDSignIn sharedInstance] signOut]; + [self.signIn signOut]; result(nil); } else if ([call.method isEqualToString:@"disconnect"]) { if ([self setAccountRequest:result]) { - [[GIDSignIn sharedInstance] disconnect]; + [self.signIn disconnect]; } } else if ([call.method isEqualToString:@"clearAuthCache"]) { // There's nothing to be done here on iOS since the expired/invalid // tokens are refreshed automatically by getTokensWithHandler. result(nil); } else if ([call.method isEqualToString:@"requestScopes"]) { - GIDGoogleUser *user = [GIDSignIn sharedInstance].currentUser; + GIDGoogleUser *user = self.signIn.currentUser; if (user == nil) { result([FlutterError errorWithCode:@"sign_in_required" message:@"No account to grant scopes." @@ -146,9 +157,9 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result return; } - NSArray *currentScopes = [GIDSignIn sharedInstance].scopes; - NSArray *scopes = call.arguments[@"scopes"]; - NSArray *missingScopes = [scopes + NSArray *currentScopes = self.signIn.scopes; + NSArray *scopes = call.arguments[@"scopes"]; + NSArray *missingScopes = [scopes filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id scope, NSDictionary *bindings) { return ![user.grantedScopes containsObject:scope]; @@ -161,12 +172,11 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result if ([self setAccountRequest:result]) { _additionalScopesRequest = missingScopes; - [GIDSignIn sharedInstance].scopes = - [currentScopes arrayByAddingObjectsFromArray:missingScopes]; - [GIDSignIn sharedInstance].presentingViewController = [self topViewController]; - [GIDSignIn sharedInstance].loginHint = user.profile.email; + self.signIn.scopes = [currentScopes arrayByAddingObjectsFromArray:missingScopes]; + self.signIn.presentingViewController = [self topViewController]; + self.signIn.loginHint = user.profile.email; @try { - [[GIDSignIn sharedInstance] signIn]; + [self.signIn signIn]; } @catch (NSException *e) { result([FlutterError errorWithCode:@"request_scopes" message:e.reason details:e.name]); } @@ -187,8 +197,10 @@ - (BOOL)setAccountRequest:(FlutterResult)request { return YES; } -- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { - return [[GIDSignIn sharedInstance] handleURL:url]; +- (BOOL)application:(UIApplication *)app + openURL:(NSURL *)url + options:(NSDictionary *)options { + return [self.signIn handleURL:url]; } #pragma mark - protocol @@ -251,7 +263,7 @@ - (void)signIn:(GIDSignIn *)signIn #pragma mark - private methods -- (void)respondWithAccount:(id)account error:(NSError *)error { +- (void)respondWithAccount:(NSDictionary *)account error:(NSError *)error { FlutterResult result = _accountRequest; _accountRequest = nil; result(error != nil ? getFlutterError(error) : account); diff --git a/packages/google_sign_in/google_sign_in/ios/Classes/FLTGoogleSignInPlugin.modulemap b/packages/google_sign_in/google_sign_in/ios/Classes/FLTGoogleSignInPlugin.modulemap new file mode 100644 index 000000000000..271f509e7fd7 --- /dev/null +++ b/packages/google_sign_in/google_sign_in/ios/Classes/FLTGoogleSignInPlugin.modulemap @@ -0,0 +1,10 @@ +framework module google_sign_in { + umbrella header "google_sign_in-umbrella.h" + + export * + module * { export * } + + explicit module Test { + header "FLTGoogleSignInPlugin_Test.h" + } +} diff --git a/packages/google_sign_in/google_sign_in/ios/Classes/FLTGoogleSignInPlugin_Test.h b/packages/google_sign_in/google_sign_in/ios/Classes/FLTGoogleSignInPlugin_Test.h new file mode 100644 index 000000000000..8fa6cf348018 --- /dev/null +++ b/packages/google_sign_in/google_sign_in/ios/Classes/FLTGoogleSignInPlugin_Test.h @@ -0,0 +1,17 @@ +// 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. + +// This header is available in the Test module. Import via "@import google_sign_in.Test;" + +#import + +@class GIDSignIn; + +/// Methods exposed for unit testing. +@interface FLTGoogleSignInPlugin () + +/// Inject @c GIDSignIn for testing. +- (instancetype)initWithSignIn:(GIDSignIn *)signIn NS_DESIGNATED_INITIALIZER; + +@end diff --git a/packages/google_sign_in/google_sign_in/ios/Classes/google_sign_in-umbrella.h b/packages/google_sign_in/google_sign_in/ios/Classes/google_sign_in-umbrella.h new file mode 100644 index 000000000000..343c390f1782 --- /dev/null +++ b/packages/google_sign_in/google_sign_in/ios/Classes/google_sign_in-umbrella.h @@ -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. + +#import +#import + +FOUNDATION_EXPORT double google_sign_inVersionNumber; +FOUNDATION_EXPORT const unsigned char google_sign_inVersionString[]; diff --git a/packages/google_sign_in/google_sign_in/ios/google_sign_in.podspec b/packages/google_sign_in/google_sign_in/ios/google_sign_in.podspec index bf0b75f2957d..6b0741c65122 100644 --- a/packages/google_sign_in/google_sign_in/ios/google_sign_in.podspec +++ b/packages/google_sign_in/google_sign_in/ios/google_sign_in.podspec @@ -12,8 +12,9 @@ Enables Google Sign-In in Flutter apps. s.license = { :type => 'BSD', :file => '../LICENSE' } s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } s.source = { :http => 'https://github.com/flutter/plugins/tree/master/packages/google_sign_in' } - s.source_files = 'Classes/**/*' + s.source_files = 'Classes/**/*.{h,m}' s.public_header_files = 'Classes/**/*.h' + s.module_map = 'Classes/FLTGoogleSignInPlugin.modulemap' s.dependency 'Flutter' s.dependency 'GoogleSignIn', '~> 5.0' s.static_framework = true diff --git a/packages/google_sign_in/google_sign_in/pubspec.yaml b/packages/google_sign_in/google_sign_in/pubspec.yaml index a57f2197576d..14f7d8901301 100644 --- a/packages/google_sign_in/google_sign_in/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Google Sign-In, a secure authentication system for signing in with a Google account on Android and iOS. repository: https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 5.0.4 +version: 5.0.5 environment: sdk: ">=2.12.0 <3.0.0"