| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/browser/ui/save_to_photos/save_to_photos_mediator.h" |
| |
| #import <UIKit/UIKit.h> |
| |
| #import "base/functional/callback_forward.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "base/test/metrics/histogram_tester.h" |
| #import "base/test/task_environment.h" |
| #import "components/prefs/pref_service.h" |
| #import "components/signin/public/base/consent_level.h" |
| #import "components/signin/public/identity_manager/identity_test_utils.h" |
| #import "components/strings/grit/components_strings.h" |
| #import "ios/chrome/browser/photos/model/photos_metrics.h" |
| #import "ios/chrome/browser/photos/model/photos_service_factory.h" |
| #import "ios/chrome/browser/shared/model/application_context/application_context.h" |
| #import "ios/chrome/browser/shared/model/browser/test/test_browser.h" |
| #import "ios/chrome/browser/shared/model/browser_state/test_chrome_browser_state.h" |
| #import "ios/chrome/browser/shared/model/prefs/pref_names.h" |
| #import "ios/chrome/browser/shared/public/commands/application_commands.h" |
| #import "ios/chrome/browser/shared/public/commands/command_dispatcher.h" |
| #import "ios/chrome/browser/shared/public/commands/manage_storage_alert_commands.h" |
| #import "ios/chrome/browser/signin/model/chrome_account_manager_service_factory.h" |
| #import "ios/chrome/browser/signin/model/fake_system_identity.h" |
| #import "ios/chrome/browser/signin/model/fake_system_identity_manager.h" |
| #import "ios/chrome/browser/signin/model/identity_manager_factory.h" |
| #import "ios/chrome/browser/signin/model/identity_test_environment_browser_state_adaptor.h" |
| #import "ios/chrome/browser/ui/account_picker/account_picker_configuration.h" |
| #import "ios/chrome/browser/ui/save_to_photos/save_to_photos_mediator.h" |
| #import "ios/chrome/browser/ui/save_to_photos/save_to_photos_mediator_delegate.h" |
| #import "ios/chrome/browser/web/model/image_fetch/image_fetch_tab_helper.h" |
| #import "ios/chrome/grit/ios_strings.h" |
| #import "ios/chrome/test/providers/photos/test_photos_service.h" |
| #import "ios/web/public/test/fakes/fake_web_state.h" |
| #import "testing/gtest_mac.h" |
| #import "testing/platform_test.h" |
| #import "third_party/ocmock/OCMock/OCMock.h" |
| #import "third_party/ocmock/gtest_support.h" |
| #import "ui/base/l10n/l10n_util_mac.h" |
| |
| namespace { |
| |
| // Fake image URL. |
| const char kFakeImageUrl[] = "http://example.com/image.png"; |
| |
| // To-be-encoded fake image data. |
| const NSString* kFakeImageData = @"kFakeImageData"; |
| |
| // Returns fake image data. |
| NSData* GetFakeImageData() { |
| return [kFakeImageData dataUsingEncoding:NSUTF8StringEncoding]; |
| } |
| |
| // Returns formatted size string. |
| NSString* GetFakeImageSize() { |
| return [NSByteCountFormatter |
| stringFromByteCount:GetFakeImageData().length |
| countStyle:NSByteCountFormatterCountStyleFile]; |
| } |
| |
| NSString* GetFakeImageName() { |
| return base::SysUTF8ToNSString(GURL(kFakeImageUrl).ExtractFileName()); |
| } |
| |
| // Returns the URL to test whether the Google Photos app is installed. |
| NSURL* GetGooglePhotosAppURL() { |
| NSURLComponents* photosAppURLComponents = [[NSURLComponents alloc] init]; |
| photosAppURLComponents.scheme = kGooglePhotosAppURLScheme; |
| return photosAppURLComponents.URL; |
| } |
| |
| } // namespace |
| |
| // Fake implementation of ImageFetchTabHelper which return fake image data. |
| class FakeImageFetchTabHelper : public ImageFetchTabHelper { |
| public: |
| static void CreateForWebState(web::WebState* web_state) { |
| web_state->SetUserData( |
| UserDataKey(), std::make_unique<FakeImageFetchTabHelper>(web_state)); |
| } |
| |
| FakeImageFetchTabHelper(web::WebState* web_state) |
| : ImageFetchTabHelper(web_state), |
| get_image_data_called_(false), |
| quit_closure_(base::DoNothing()) {} |
| FakeImageFetchTabHelper(const ImageFetchTabHelper&) = delete; |
| FakeImageFetchTabHelper& operator=(const ImageFetchTabHelper&) = delete; |
| ~FakeImageFetchTabHelper() override = default; |
| |
| void SetQuitClosure(base::RepeatingClosure quit_closure) { |
| quit_closure_ = std::move(quit_closure); |
| } |
| |
| bool GetImageDataCalled() const { return get_image_data_called_; } |
| |
| const GURL& GetImageUrl() const { return image_url_; } |
| |
| // ImageFetchTabHelper |
| void GetImageData(const GURL& url, |
| const web::Referrer& referrer, |
| ImageDataCallback callback) override { |
| get_image_data_called_ = true; |
| image_url_ = url; |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(callback, GetFakeImageData()).Then(quit_closure_)); |
| } |
| |
| private: |
| bool get_image_data_called_; |
| GURL image_url_; |
| base::RepeatingClosure quit_closure_; |
| }; |
| |
| // SaveToPhotosMediator unit tests. |
| class SaveToPhotosMediatorTest : public PlatformTest { |
| protected: |
| void SetUp() final { |
| TestChromeBrowserState::Builder builder; |
| builder.AddTestingFactory( |
| IdentityManagerFactory::GetInstance(), |
| base::BindRepeating(IdentityTestEnvironmentBrowserStateAdaptor:: |
| BuildIdentityManagerForTests)); |
| browser_state_ = builder.Build(); |
| browser_ = std::make_unique<TestBrowser>(browser_state_.get()); |
| web_state_ = std::make_unique<web::FakeWebState>(); |
| FakeImageFetchTabHelper::CreateForWebState(web_state_.get()); |
| fake_identity_ = [FakeSystemIdentity fakeIdentity1]; |
| FakeSystemIdentityManager* system_identity_manager = |
| FakeSystemIdentityManager::FromSystemIdentityManager( |
| GetApplicationContext()->GetSystemIdentityManager()); |
| system_identity_manager->AddIdentity(fake_identity_); |
| mock_application_handler_ = |
| OCMStrictProtocolMock(@protocol(ApplicationCommands)); |
| [browser_->GetCommandDispatcher() |
| startDispatchingToTarget:mock_application_handler_ |
| forProtocol:@protocol(ApplicationCommands)]; |
| mock_manage_storage_alert_handler_ = |
| OCMStrictProtocolMock(@protocol(ManageStorageAlertCommands)); |
| [browser_->GetCommandDispatcher() |
| startDispatchingToTarget:mock_manage_storage_alert_handler_ |
| forProtocol:@protocol(ManageStorageAlertCommands)]; |
| |
| mock_application_ = OCMClassMock([UIApplication class]); |
| OCMStub([mock_application_ sharedApplication]).andReturn(mock_application_); |
| } |
| |
| // Set-up the FakeImageFetchTabHelper so it quits the run loop when calling |
| // back. |
| void SetUpImageFetchTabHelperQuitClosure() { |
| GetFakeImageFetchTabHelper()->SetQuitClosure( |
| task_environment_.QuitClosure()); |
| } |
| |
| // Set-up the TestPhotosService so it quits the run loop when calling back. |
| void SetUpPhotosServiceQuitClosure() { |
| GetTestPhotosService()->SetQuitClosure(task_environment_.QuitClosure()); |
| } |
| |
| void TearDown() final { [mock_application_ stopMocking]; } |
| |
| // Create a SaveToPhotosMediator with services from the test browser state. |
| SaveToPhotosMediator* CreateSaveToPhotosMediator() { |
| PhotosService* photos_service = |
| PhotosServiceFactory::GetForBrowserState(browser_state_.get()); |
| PrefService* pref_service = browser_state_->GetPrefs(); |
| ChromeAccountManagerService* account_manager_service = |
| ChromeAccountManagerServiceFactory::GetForBrowserState( |
| browser_state_.get()); |
| signin::IdentityManager* identity_manager = |
| IdentityManagerFactory::GetForBrowserState(browser_state_.get()); |
| return [[SaveToPhotosMediator alloc] |
| initWithPhotosService:photos_service |
| prefService:pref_service |
| accountManagerService:account_manager_service |
| identityManager:identity_manager |
| manageStorageAlertHandler:mock_manage_storage_alert_handler_ |
| applicationHandler:mock_application_handler_]; |
| } |
| |
| // Sign-in with a fake account. |
| void SignIn() { |
| signin::MakePrimaryAccountAvailable( |
| IdentityManagerFactory::GetForBrowserState(browser_state_.get()), |
| base::SysNSStringToUTF8(fake_identity_.userEmail), |
| signin::ConsentLevel::kSignin); |
| } |
| |
| // Returns the TestPhotosService tied to the browser state. |
| TestPhotosService* GetTestPhotosService() { |
| return static_cast<TestPhotosService*>( |
| PhotosServiceFactory::GetForBrowserState(browser_state_.get())); |
| } |
| |
| // Returns the FakeImageFetchTabHelper tied to the WebState. |
| FakeImageFetchTabHelper* GetFakeImageFetchTabHelper() { |
| return static_cast<FakeImageFetchTabHelper*>( |
| ImageFetchTabHelper::FromWebState(web_state_.get())); |
| } |
| |
| base::test::TaskEnvironment task_environment_; |
| std::unique_ptr<TestChromeBrowserState> browser_state_; |
| std::unique_ptr<TestBrowser> browser_; |
| std::unique_ptr<web::FakeWebState> web_state_; |
| id mock_application_; |
| id<SystemIdentity> fake_identity_; |
| base::HistogramTester histogram_tester_; |
| id mock_application_handler_; |
| id mock_manage_storage_alert_handler_; |
| }; |
| |
| // Tests that the mediator attempts to fetch the image data when started. |
| TEST_F(SaveToPhotosMediatorTest, StartGetsImageData) { |
| // Create and start mediator. |
| SaveToPhotosMediator* mediator = CreateSaveToPhotosMediator(); |
| [mediator startWithImageURL:GURL(kFakeImageUrl) |
| referrer:web::Referrer() |
| webState:web_state_.get()]; |
| |
| // Test that the image fetch tab helper was called with the given image URL. |
| FakeImageFetchTabHelper* image_fetch_tab_helper = |
| GetFakeImageFetchTabHelper(); |
| EXPECT_TRUE(image_fetch_tab_helper->GetImageDataCalled()); |
| EXPECT_EQ(image_fetch_tab_helper->GetImageUrl(), GURL(kFakeImageUrl)); |
| } |
| |
| // Tests that the mediator shows the account picker if preferences do not |
| // contain a default account choice for Save to Photos. |
| TEST_F(SaveToPhotosMediatorTest, ShowsAccountPickerIfNoDefaultAccountInPrefs) { |
| // The feature requires the user being signed-in. |
| SignIn(); |
| |
| // This test assumes there is no default account memorized for Save to Photos. |
| browser_state_->GetPrefs()->ClearPref(prefs::kIosSaveToPhotosDefaultGaiaId); |
| browser_state_->GetPrefs()->ClearPref( |
| prefs::kIosSaveToPhotosSkipAccountPicker); |
| |
| // Create a mediator and set up with mock delegate. |
| SaveToPhotosMediator* mediator = CreateSaveToPhotosMediator(); |
| id mock_save_to_photos_mediator_delegate = |
| OCMStrictProtocolMock(@protocol(SaveToPhotosMediatorDelegate)); |
| mediator.delegate = static_cast<id<SaveToPhotosMediatorDelegate>>( |
| mock_save_to_photos_mediator_delegate); |
| |
| // Expect that the mediator will show the account picker with this |
| // configuration. |
| NSString* expected_title_text = |
| l10n_util::GetNSString(IDS_IOS_SAVE_TO_PHOTOS_ACCOUNT_PICKER_TITLE); |
| NSString* expected_body_text = |
| l10n_util::GetNSStringF(IDS_IOS_SAVE_TO_PHOTOS_ACCOUNT_PICKER_BODY, |
| base::SysNSStringToUTF16(GetFakeImageName()), |
| base::SysNSStringToUTF16(GetFakeImageSize())); |
| NSString* expected_submit_button_title = |
| l10n_util::GetNSString(IDS_IOS_SAVE_TO_PHOTOS_ACCOUNT_PICKER_SUBMIT); |
| NSString* expected_ask_every_time_switch_label_text = l10n_util::GetNSString( |
| IDS_IOS_SAVE_TO_PHOTOS_ACCOUNT_PICKER_ASK_EVERY_TIME); |
| OCMExpect([mock_save_to_photos_mediator_delegate |
| showAccountPickerWithConfiguration:[OCMArg checkWithBlock:^( |
| AccountPickerConfiguration* |
| configuration) { |
| EXPECT_NSEQ(expected_title_text, configuration.titleText); |
| EXPECT_NSEQ(expected_body_text, configuration.bodyText); |
| EXPECT_NSEQ(expected_submit_button_title, |
| configuration.submitButtonTitle); |
| EXPECT_NSEQ(expected_ask_every_time_switch_label_text, |
| configuration.askEveryTimeSwitchLabelText); |
| return YES; |
| }] |
| selectedIdentity:nil]); |
| |
| // Start the mediator and run until the image has been fetched and |
| // processed by the mediator. |
| SetUpImageFetchTabHelperQuitClosure(); |
| [mediator startWithImageURL:GURL(kFakeImageUrl) |
| referrer:web::Referrer() |
| webState:web_state_.get()]; |
| task_environment_.RunUntilQuit(); |
| |
| EXPECT_OCMOCK_VERIFY(mock_save_to_photos_mediator_delegate); |
| } |
| |
| // Tests that the mediator skips the account picker if preferences do contain a |
| // default account choice for Save to Photos. |
| TEST_F(SaveToPhotosMediatorTest, |
| DoesNotShowAccountPickerIfDefaultAccountInPrefs) { |
| // The feature requires the user being signed-in. |
| SignIn(); |
| |
| // This test assumes there is a default account memorized for Save to Photos |
| // and that the user opted-in skipping the account picker. |
| browser_state_->GetPrefs()->SetString( |
| prefs::kIosSaveToPhotosDefaultGaiaId, |
| base::SysNSStringToUTF8(fake_identity_.gaiaID).c_str()); |
| browser_state_->GetPrefs()->SetBoolean( |
| prefs::kIosSaveToPhotosSkipAccountPicker, true); |
| |
| // Create a mediator and set up with mock delegate. |
| SaveToPhotosMediator* mediator = CreateSaveToPhotosMediator(); |
| id mock_save_to_photos_mediator_delegate = |
| OCMProtocolMock(@protocol(SaveToPhotosMediatorDelegate)); |
| mediator.delegate = static_cast<id<SaveToPhotosMediatorDelegate>>( |
| mock_save_to_photos_mediator_delegate); |
| |
| // Check that the Photos service has not been called yet. |
| EXPECT_TRUE(GetTestPhotosService()->IsAvailable()); |
| EXPECT_EQ(GetTestPhotosService()->GetIdentity(), nil); |
| |
| // Start the mediator and run until the image has been fetched and |
| // processed by the mediator. |
| SetUpImageFetchTabHelperQuitClosure(); |
| [mediator startWithImageURL:GURL(kFakeImageUrl) |
| referrer:web::Referrer() |
| webState:web_state_.get()]; |
| task_environment_.RunUntilQuit(); |
| |
| histogram_tester_.ExpectUniqueSample( |
| kSaveToPhotosAccountPickerActionsHistogram, |
| SaveToPhotosAccountPickerActions::kSkipped, 1); |
| |
| // Test that the Photos service has already been called with the identity from |
| // the prefs. |
| EXPECT_FALSE(GetTestPhotosService()->IsAvailable()); |
| EXPECT_EQ(GetTestPhotosService()->GetIdentity(), fake_identity_); |
| |
| EXPECT_OCMOCK_VERIFY(mock_save_to_photos_mediator_delegate); |
| } |
| |
| // Tests that upon identity selection, the SaveToPhotosMediator starts the |
| // validation spinner in the account picker, it uploads the image it has fetched |
| // from the image fetch tab helper, hides the account picker and shows a |
| // snackbar message when the PhotosService reports upload completion. |
| TEST_F(SaveToPhotosMediatorTest, |
| DidSelectIdentityUploadsImageAndShowsSnackbarMessage) { |
| // The feature requires the user being signed-in. |
| SignIn(); |
| |
| // This test assumes there is no default account memorized for Save to Photos. |
| browser_state_->GetPrefs()->ClearPref(prefs::kIosSaveToPhotosDefaultGaiaId); |
| browser_state_->GetPrefs()->ClearPref( |
| prefs::kIosSaveToPhotosSkipAccountPicker); |
| |
| // Create a mediator and set up with mock delegate. |
| SaveToPhotosMediator* mediator = CreateSaveToPhotosMediator(); |
| id mock_save_to_photos_mediator_delegate = |
| OCMProtocolMock(@protocol(SaveToPhotosMediatorDelegate)); |
| mediator.delegate = static_cast<id<SaveToPhotosMediatorDelegate>>( |
| mock_save_to_photos_mediator_delegate); |
| |
| // Start the mediator and run until the image has been fetched and processed |
| // by the mediator. |
| SetUpImageFetchTabHelperQuitClosure(); |
| [mediator startWithImageURL:GURL(kFakeImageUrl) |
| referrer:web::Referrer() |
| webState:web_state_.get()]; |
| task_environment_.RunUntilQuit(); |
| |
| // Test that the PhotosService has not been used to upload an image yet. |
| EXPECT_TRUE(GetTestPhotosService()->IsAvailable()); |
| EXPECT_EQ(GetTestPhotosService()->GetIdentity(), nil); |
| |
| // Expect that the mediator start the validation spinner in the account picker |
| // when it knows what identity has been selected. |
| OCMExpect([mock_save_to_photos_mediator_delegate |
| startValidationSpinnerForAccountPicker]); |
| |
| // Run until the mediator calls the Photos service. |
| SetUpPhotosServiceQuitClosure(); |
| |
| // Give the selected identity to the mediator and verify that the mediator |
| // asked to start the validation spinner in the account picker. |
| [mediator accountPickerDidSelectIdentity:fake_identity_ askEveryTime:YES]; |
| histogram_tester_.ExpectUniqueSample( |
| kSaveToPhotosAccountPickerActionsHistogram, |
| SaveToPhotosAccountPickerActions::kSelectedIdentity, 1); |
| EXPECT_OCMOCK_VERIFY(mock_save_to_photos_mediator_delegate); |
| |
| // Test that the PhotosService is now unavailable and has been given an image |
| // to upload. |
| EXPECT_FALSE(GetTestPhotosService()->IsAvailable()); |
| EXPECT_NSEQ(GetTestPhotosService()->GetImageName(), |
| base::SysUTF8ToNSString(GURL(kFakeImageUrl).ExtractFileName())); |
| EXPECT_NSEQ(GetTestPhotosService()->GetImageData(), GetFakeImageData()); |
| EXPECT_EQ(GetTestPhotosService()->GetIdentity(), fake_identity_); |
| |
| // Expect that the success snackbar is shown once image is uploaded. |
| NSString* expected_message = l10n_util::GetNSStringF( |
| IDS_IOS_SAVE_TO_PHOTOS_SNACKBAR_IMAGE_SAVED_MESSAGE, |
| base::SysNSStringToUTF16(fake_identity_.userEmail)); |
| NSString* expected_button_text = l10n_util::GetNSString( |
| IDS_IOS_SAVE_TO_PHOTOS_SNACKBAR_IMAGE_SAVED_OPEN_BUTTON); |
| OCMExpect([mock_save_to_photos_mediator_delegate |
| showSnackbarWithMessage:expected_message |
| buttonText:expected_button_text |
| messageAction:[OCMArg isNotNil] |
| completionAction:[OCMArg isNotNil]]); |
| |
| // Run until the PhotosService finishes to upload the image. |
| task_environment_.RunUntilQuit(); |
| |
| // Verify that the success snackbar has been shown. |
| EXPECT_OCMOCK_VERIFY(mock_save_to_photos_mediator_delegate); |
| } |
| |
| // Tests after the account picker has been displayed, the user can dismiss it |
| // using the "Cancel" button. |
| TEST_F(SaveToPhotosMediatorTest, DidCancelBeforeUploadDismissesAccountPicker) { |
| // The feature requires the user being signed-in. |
| SignIn(); |
| |
| // This test assumes there is no default account memorized for Save to Photos. |
| browser_state_->GetPrefs()->ClearPref(prefs::kIosSaveToPhotosDefaultGaiaId); |
| browser_state_->GetPrefs()->ClearPref( |
| prefs::kIosSaveToPhotosSkipAccountPicker); |
| |
| // Create a mediator and set up with mock delegate. |
| SaveToPhotosMediator* mediator = CreateSaveToPhotosMediator(); |
| id mock_save_to_photos_mediator_delegate = |
| OCMProtocolMock(@protocol(SaveToPhotosMediatorDelegate)); |
| mediator.delegate = static_cast<id<SaveToPhotosMediatorDelegate>>( |
| mock_save_to_photos_mediator_delegate); |
| |
| // Start the mediator and run until the image has been fetched and processed |
| // by the mediator. |
| SetUpImageFetchTabHelperQuitClosure(); |
| [mediator startWithImageURL:GURL(kFakeImageUrl) |
| referrer:web::Referrer() |
| webState:web_state_.get()]; |
| task_environment_.RunUntilQuit(); |
| |
| // Test that the PhotosService has not been used to upload an image yet. |
| EXPECT_TRUE(GetTestPhotosService()->IsAvailable()); |
| EXPECT_EQ(GetTestPhotosService()->GetIdentity(), nil); |
| |
| // Expect that the mediator hides the account picker when "Cancel" has been |
| // tapped. |
| OCMExpect([mock_save_to_photos_mediator_delegate hideAccountPicker]); |
| |
| // Give the selected identity to the mediator and verify that the mediator |
| // asked to start the validation spinner in the account picker. |
| [mediator accountPickerDidCancel]; |
| histogram_tester_.ExpectUniqueSample( |
| kSaveToPhotosAccountPickerActionsHistogram, |
| SaveToPhotosAccountPickerActions::kCancelled, 1); |
| EXPECT_OCMOCK_VERIFY(mock_save_to_photos_mediator_delegate); |
| |
| // Expect that the mediator hides Save to Photos the account picker is hidden. |
| OCMExpect([mock_save_to_photos_mediator_delegate hideSaveToPhotos]); |
| // Let the mediator know that the account picker was hidden. |
| [mediator accountPickerWasHidden]; |
| EXPECT_OCMOCK_VERIFY(mock_save_to_photos_mediator_delegate); |
| histogram_tester_.ExpectUniqueSample( |
| kSaveToPhotosActionsHistogram, |
| SaveToPhotosActions::kFailureUserCancelledWithAccountPicker, 1); |
| } |
| |
| // Tests that the SaveToPhotosMediator tries to open the Google Photos app if it |
| // detects that it is installed and the user taps "Open" in the success |
| // snackbar. |
| TEST_F(SaveToPhotosMediatorTest, SnackbarOpenButtonOpensPhotosAppIfInstalled) { |
| // The feature requires the user being signed-in. |
| SignIn(); |
| |
| // Create a mediator and set up with mock delegate. |
| SaveToPhotosMediator* mediator = CreateSaveToPhotosMediator(); |
| id mock_save_to_photos_mediator_delegate = |
| OCMProtocolMock(@protocol(SaveToPhotosMediatorDelegate)); |
| mediator.delegate = static_cast<id<SaveToPhotosMediatorDelegate>>( |
| mock_save_to_photos_mediator_delegate); |
| |
| // Start the mediator and run until the image has been fetched and processed |
| // by the mediator. |
| SetUpImageFetchTabHelperQuitClosure(); |
| [mediator startWithImageURL:GURL(kFakeImageUrl) |
| referrer:web::Referrer() |
| webState:web_state_.get()]; |
| task_environment_.RunUntilQuit(); |
| |
| // Expect the success snackbar (with a non-nil completion) is shown and save |
| // the message and completion actions to later simulate the snackbar being |
| // dismissed by the user tapping the "Open" button. |
| __block ProceduralBlock savedMessageAction = nil; |
| __block void (^savedCompletionAction)(BOOL) = nil; |
| OCMExpect([mock_save_to_photos_mediator_delegate |
| showSnackbarWithMessage:[OCMArg any] |
| buttonText:[OCMArg any] |
| messageAction:[OCMArg checkWithBlock:^( |
| ProceduralBlock messageAction) { |
| savedMessageAction = messageAction; |
| return messageAction != nil; |
| }] |
| completionAction:[OCMArg checkWithBlock:^( |
| void (^completionAction)(BOOL)) { |
| savedCompletionAction = completionAction; |
| return completionAction != nil; |
| }]]); |
| |
| // Run until the PhotosService is done uploading, at which point the success |
| // snackbar is presented to the user. |
| SetUpPhotosServiceQuitClosure(); |
| [mediator accountPickerDidSelectIdentity:fake_identity_ askEveryTime:YES]; |
| [mediator accountPickerWasHidden]; |
| task_environment_.RunUntilQuit(); |
| |
| // Verify that the success snackbar was shown. |
| EXPECT_OCMOCK_VERIFY(mock_save_to_photos_mediator_delegate); |
| ASSERT_TRUE(savedMessageAction); |
| ASSERT_TRUE(savedCompletionAction); |
| |
| // Expect that the mediator will detect that the app is installed when the |
| // user taps "Open". |
| OCMExpect([mock_application_ canOpenURL:GetGooglePhotosAppURL()]) |
| .andReturn(YES); |
| |
| // Expect that the mediator tries to open the Photos app and switch to the |
| // Photos account associated with `fake_identity_`. |
| NSString* recently_added_url_string = [kGooglePhotosRecentlyAddedURLString |
| stringByAppendingString:fake_identity_.gaiaID]; |
| NSURL* photos_url = [NSURL URLWithString:recently_added_url_string]; |
| OCMExpect([mock_application_ |
| openURL:photos_url |
| options:@{ |
| UIApplicationOpenURLOptionUniversalLinksOnly : @YES |
| } |
| completionHandler:nil]); |
| |
| // Expect that the mediator hides Save to Photos when the user has tapped |
| // "Open". |
| OCMExpect([mock_save_to_photos_mediator_delegate hideSaveToPhotos]); |
| |
| // Simulate the user tapped the "Open" button. |
| savedMessageAction(); |
| // Simulate the snackbar was dismissed because of user interaction. |
| savedCompletionAction(/* user_triggered= */ YES); |
| |
| histogram_tester_.ExpectUniqueSample( |
| kSaveToPhotosActionsHistogram, |
| SaveToPhotosActions::kSuccessAndOpenPhotosApp, 1); |
| |
| [mediator disconnect]; |
| |
| // Verify that the mediator detected that the app is installed and tried to |
| // open it. |
| EXPECT_OCMOCK_VERIFY(mock_application_); |
| } |
| |
| // Tests that the SaveToPhotosMediator tries to show the StoreKit if it detects |
| // that the Google Photos app is not installed and the user taps "Open" in the |
| // success snackbar. |
| TEST_F(SaveToPhotosMediatorTest, |
| SnackbarOpenButtonOpensStoreKitIfAppNotInstalled) { |
| // The feature requires the user being signed-in. |
| SignIn(); |
| |
| // Create a mediator and set up with mock delegate. |
| SaveToPhotosMediator* mediator = CreateSaveToPhotosMediator(); |
| id mock_save_to_photos_mediator_delegate = |
| OCMProtocolMock(@protocol(SaveToPhotosMediatorDelegate)); |
| mediator.delegate = static_cast<id<SaveToPhotosMediatorDelegate>>( |
| mock_save_to_photos_mediator_delegate); |
| |
| // Start the mediator and run until the image has been fetched and processed |
| // by the mediator. |
| SetUpImageFetchTabHelperQuitClosure(); |
| [mediator startWithImageURL:GURL(kFakeImageUrl) |
| referrer:web::Referrer() |
| webState:web_state_.get()]; |
| task_environment_.RunUntilQuit(); |
| |
| // Expect the success snackbar (with a non-nil completion) is shown and save |
| // the message and completion actions to later simulate the snackbar being |
| // dismissed by the user tapping the "Open" button. |
| __block ProceduralBlock savedMessageAction = nil; |
| __block void (^savedCompletionAction)(BOOL) = nil; |
| OCMExpect([mock_save_to_photos_mediator_delegate |
| showSnackbarWithMessage:[OCMArg any] |
| buttonText:[OCMArg any] |
| messageAction:[OCMArg checkWithBlock:^( |
| ProceduralBlock messageAction) { |
| savedMessageAction = messageAction; |
| return messageAction != nil; |
| }] |
| completionAction:[OCMArg checkWithBlock:^( |
| void (^completionAction)(BOOL)) { |
| savedCompletionAction = completionAction; |
| return completionAction != nil; |
| }]]); |
| |
| // Run until the PhotosService is done uploading, at which point the success |
| // snackbar is presented to the user. |
| SetUpPhotosServiceQuitClosure(); |
| [mediator accountPickerDidSelectIdentity:fake_identity_ askEveryTime:YES]; |
| [mediator accountPickerWasHidden]; |
| task_environment_.RunUntilQuit(); |
| |
| // Verify that the success snackbar was shown. |
| EXPECT_OCMOCK_VERIFY(mock_save_to_photos_mediator_delegate); |
| ASSERT_TRUE(savedMessageAction); |
| ASSERT_TRUE(savedCompletionAction); |
| |
| // Expect that the mediator will detect that the app is not installed when the |
| // user taps "Open". |
| OCMExpect([mock_application_ canOpenURL:GetGooglePhotosAppURL()]) |
| .andReturn(NO); |
| |
| // Expect that the mediator tries to show StoreKit with the expected product |
| // identifier. |
| OCMExpect([mock_save_to_photos_mediator_delegate |
| showStoreKitWithProductIdentifier:kGooglePhotosAppProductIdentifier |
| providerToken:kGooglePhotosStoreKitProviderToken |
| campaignToken:kGooglePhotosStoreKitCampaignToken]); |
| |
| // Simulate the user tapped the "Open" button. |
| savedMessageAction(); |
| // Simulate the snackbar was dismissed because of user interaction. |
| savedCompletionAction(/* user_triggered= */ YES); |
| |
| // Verify that the mediator detected that the app is not installed and tried |
| // to open show the app in StoreKit. |
| EXPECT_OCMOCK_VERIFY(mock_save_to_photos_mediator_delegate); |
| |
| // Test that the mediator asks to hide Save to Photos when StoreKit wants to |
| // hide. |
| OCMExpect([mock_save_to_photos_mediator_delegate hideSaveToPhotos]); |
| [mediator storeKitWantsToHide]; |
| EXPECT_OCMOCK_VERIFY(mock_save_to_photos_mediator_delegate); |
| histogram_tester_.ExpectUniqueSample( |
| kSaveToPhotosActionsHistogram, |
| SaveToPhotosActions::kSuccessAndOpenStoreKitAndAppNotInstalled, 1); |
| |
| [mediator disconnect]; |
| } |
| |
| // Tests that the SaveToPhotosMediator shows an alert with Try Again and Cancel |
| // options if the PhotosService fails to upload the image. |
| TEST_F(SaveToPhotosMediatorTest, ShowsTryAgainOrCancelAlertIfUploadFails) { |
| // The feature requires the user being signed-in. |
| SignIn(); |
| |
| // Create a mediator and set up with mock delegate. |
| SaveToPhotosMediator* mediator = CreateSaveToPhotosMediator(); |
| id mock_save_to_photos_mediator_delegate = |
| OCMProtocolMock(@protocol(SaveToPhotosMediatorDelegate)); |
| mediator.delegate = static_cast<id<SaveToPhotosMediatorDelegate>>( |
| mock_save_to_photos_mediator_delegate); |
| |
| // Start the mediator and run until the image has been fetched and processed |
| // by the mediator. |
| SetUpImageFetchTabHelperQuitClosure(); |
| [mediator startWithImageURL:GURL(kFakeImageUrl) |
| referrer:web::Referrer() |
| webState:web_state_.get()]; |
| task_environment_.RunUntilQuit(); |
| |
| // Set up the PhotosService to simulate upload failure. |
| GetTestPhotosService()->SetUploadResult({.successful = false}); |
| |
| // Expect that the failure alert is shown by the mediator upon upload failure. |
| NSString* expected_title = l10n_util::GetNSString( |
| IDS_IOS_SAVE_TO_PHOTOS_THIS_FILE_COULD_NOT_BE_UPLOADED_TITLE); |
| NSString* expected_message = l10n_util::GetNSStringF( |
| IDS_IOS_SAVE_TO_PHOTOS_THIS_FILE_COULD_NOT_BE_UPLOADED_MESSAGE, |
| base::SysNSStringToUTF16(GetFakeImageName()), |
| base::SysNSStringToUTF16(GetFakeImageSize())); |
| NSString* expected_cancel_title = l10n_util::GetNSString(IDS_CANCEL); |
| NSString* expected_try_again_title = l10n_util::GetNSString( |
| IDS_IOS_SAVE_TO_PHOTOS_THIS_FILE_COULD_NOT_BE_UPLOADED_TRY_AGAIN); |
| OCMExpect([mock_save_to_photos_mediator_delegate |
| showTryAgainOrCancelAlertWithTitle:expected_title |
| message:expected_message |
| tryAgainTitle:expected_try_again_title |
| tryAgainAction:[OCMArg isNotNil] |
| cancelTitle:expected_cancel_title |
| cancelAction:[OCMArg isNotNil]]); |
| |
| // Run until the PhotosService fails to upload. |
| SetUpPhotosServiceQuitClosure(); |
| [mediator accountPickerDidSelectIdentity:fake_identity_ askEveryTime:YES]; |
| [mediator accountPickerWasHidden]; |
| task_environment_.RunUntilQuit(); |
| |
| // Verify that the failure alert has been presented. |
| EXPECT_OCMOCK_VERIFY(mock_save_to_photos_mediator_delegate); |
| } |