| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/accelerators/accelerator_controller_impl.h" |
| #include "ash/app_list/app_list_bubble_presenter.h" |
| #include "ash/app_list/app_list_controller_impl.h" |
| #include "ash/app_list/app_list_public_test_util.h" |
| #include "ash/app_list/views/app_list_bubble_view.h" |
| #include "ash/app_list/views/app_list_view.h" |
| #include "ash/app_list/views/search_box_view.h" |
| #include "ash/capture_mode/action_button_container_view.h" |
| #include "ash/capture_mode/action_button_view.h" |
| #include "ash/capture_mode/base_capture_mode_session.h" |
| #include "ash/capture_mode/capture_button_view.h" |
| #include "ash/capture_mode/capture_label_view.h" |
| #include "ash/capture_mode/capture_mode_bar_view.h" |
| #include "ash/capture_mode/capture_mode_constants.h" |
| #include "ash/capture_mode/capture_mode_controller.h" |
| #include "ash/capture_mode/capture_mode_session.h" |
| #include "ash/capture_mode/capture_mode_session_focus_cycler.h" |
| #include "ash/capture_mode/capture_mode_session_test_api.h" |
| #include "ash/capture_mode/capture_mode_test_util.h" |
| #include "ash/capture_mode/capture_mode_types.h" |
| #include "ash/capture_mode/capture_mode_util.h" |
| #include "ash/capture_mode/capture_region_overlay_controller.h" |
| #include "ash/capture_mode/search_results_panel.h" |
| #include "ash/capture_mode/sunfish_capture_bar_view.h" |
| #include "ash/capture_mode/test_capture_mode_delegate.h" |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/constants/ash_switches.h" |
| #include "ash/constants/url_constants.h" |
| #include "ash/public/cpp/capture_mode/capture_mode_api.h" |
| #include "ash/public/cpp/capture_mode/capture_mode_delegate.h" |
| #include "ash/public/cpp/capture_mode/capture_mode_test_api.h" |
| #include "ash/public/cpp/scanner/scanner_delegate.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/public/cpp/system/toast_manager.h" |
| #include "ash/public/cpp/test/test_new_window_delegate.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/scanner/fake_scanner_profile_scoped_delegate.h" |
| #include "ash/scanner/scanner_controller.h" |
| #include "ash/scanner/scanner_disclaimer.h" |
| #include "ash/scanner/scanner_enterprise_policy.h" |
| #include "ash/scanner/scanner_metrics.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shelf/home_button.h" |
| #include "ash/shelf/shelf_navigation_widget.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/style/icon_button.h" |
| #include "ash/style/pill_button.h" |
| #include "ash/style/tab_slider_button.h" |
| #include "ash/system/toast/anchored_nudge_manager_impl.h" |
| #include "ash/test/ash_test_base.h" |
| #include "ash/test/ash_test_util.h" |
| #include "ash/test/test_ash_web_view_factory.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h" |
| #include "ash/wm/window_util.h" |
| #include "base/auto_reset.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_future.h" |
| #include "chromeos/ash/components/specialized_features/feature_access_checker.h" |
| #include "components/manta/manta_status.h" |
| #include "components/manta/proto/scanner.pb.h" |
| #include "components/manta/scanner_provider.h" |
| #include "disclaimer_view.h" |
| #include "testing/gmock/include/gmock/gmock-matchers.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/base/clipboard/clipboard.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| #include "ui/gfx/animation/throb_animation.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/link.h" |
| #include "ui/views/controls/styled_label.h" |
| #include "ui/views/controls/textfield/textfield.h" |
| #include "ui/views/test/widget_test.h" |
| #include "ui/views/view_utils.h" |
| #include "ui/views/widget/widget_delegate.h" |
| #include "ui/wm/core/window_util.h" |
| #include "url/gurl.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| using ::base::test::InvokeFuture; |
| using ::base::test::RunOnceCallback; |
| using ::specialized_features::FeatureAccessFailure; |
| using ::testing::_; |
| using ::testing::AllOf; |
| using ::testing::AnyOf; |
| using ::testing::DoDefault; |
| using ::testing::Each; |
| using ::testing::ElementsAre; |
| using ::testing::Gt; |
| using ::testing::IsEmpty; |
| using ::testing::IsNull; |
| using ::testing::Matcher; |
| using ::testing::Not; |
| using ::testing::NotNull; |
| using ::testing::Property; |
| using ::testing::Return; |
| using ::testing::SizeIs; |
| using ::testing::WithArg; |
| |
| constexpr char kCaptureModeTextCopiedToastId[] = "capture_mode_text_copied"; |
| |
| // The number of focusable points or areas for the region overlay. |
| constexpr int kRegionFocusCount = 9; |
| |
| constexpr base::TimeDelta kImageSearchRequestStartDelay = base::Seconds(1); |
| |
| // The length of an edge for a small capture region where the capture label |
| // button cannot fit inside. |
| constexpr int kSmallRegionEdgeLength = 20; |
| |
| // TODO: crbug.com/402548933 - Update this to work when the Lens Web API |
| // integration is enabled. |
| void WaitForImageCapturedForSearch(PerformCaptureType expected_capture_type) { |
| base::test::TestFuture<void> image_captured_future; |
| CaptureModeTestApi().SetOnImageCapturedForSearchCallback( |
| base::BindLambdaForTesting([&](PerformCaptureType capture_type) { |
| if (capture_type == expected_capture_type) { |
| image_captured_future.SetValue(); |
| } |
| })); |
| EXPECT_TRUE(image_captured_future.Wait()); |
| } |
| |
| // Waits for capture mode widgets to become visible. This is necessary in some |
| // tests which need capture mode widgets to be reshown after performing capture |
| // for text detection. |
| void WaitForCaptureModeWidgetsVisible() { |
| CaptureModeSessionTestApi session_test_api( |
| CaptureModeController::Get()->capture_mode_session()); |
| views::test::WidgetVisibleWaiter(session_test_api.GetCaptureLabelWidget()) |
| .Wait(); |
| } |
| |
| // Acknowledges the Scanner disclaimer for a given entrypoint. Used to disable |
| // Scanner disclaimers for most tests. |
| void AckScannerDisclaimer(ScannerEntryPoint entry_point) { |
| SetScannerDisclaimerAcked(*capture_mode_util::GetActiveUserPrefService(), |
| entry_point); |
| } |
| |
| // Unacknowledges all Scanner disclaimers. Used to test Scanner disclaimer |
| // logic. |
| void UnackAllScannerDisclaimers() { |
| SetAllScannerDisclaimersUnackedForTest( |
| *capture_mode_util::GetActiveUserPrefService()); |
| } |
| |
| // An overload for `GetScannerDisclaimerType` which avoids specifying the active |
| // pref service. |
| ScannerDisclaimerType GetScannerDisclaimerType(ScannerEntryPoint entry_point) { |
| return GetScannerDisclaimerType( |
| *capture_mode_util::GetActiveUserPrefService(), entry_point); |
| } |
| |
| FakeScannerProfileScopedDelegate* GetFakeScannerProfileScopedDelegate( |
| ScannerController& scanner_controller) { |
| return static_cast<FakeScannerProfileScopedDelegate*>( |
| scanner_controller.delegate_for_testing()->GetProfileScopedDelegate()); |
| } |
| |
| // Returns a matcher for an action button that returns true if the action button |
| // has the specified `id`. |
| Matcher<ActionButtonView*> ActionButtonIdIs(int id) { |
| return Property(&ActionButtonView::GetID, id); |
| } |
| |
| // Returns a matcher for an action button that returns true if the action button |
| // is collapsed. |
| Matcher<ActionButtonView*> ActionButtonIsCollapsed() { |
| return Property(&ActionButtonView::label_for_testing, |
| Property(&views::Label::GetVisible, false)); |
| } |
| |
| // Returns whether a `DisclaimerView` is a reminder disclaimer or not. |
| bool DisclaimerIsReminder(views::View& disclaimer) { |
| return !disclaimer.GetViewByID( |
| DisclaimerViewId::kDisclaimerViewDeclineButtonId); |
| } |
| |
| class MockNewWindowDelegate : public testing::NiceMock<TestNewWindowDelegate> { |
| public: |
| // TestNewWindowDelegate: |
| MOCK_METHOD(void, |
| OpenUrl, |
| (const GURL& url, OpenUrlFrom from, Disposition disposition), |
| (override)); |
| }; |
| |
| class SunfishTestBase : public AshTestBase { |
| public: |
| SunfishTestBase() |
| : AshTestBase( |
| base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME) {} |
| SunfishTestBase(const SunfishTestBase&) = delete; |
| SunfishTestBase& operator=(const SunfishTestBase&) = delete; |
| ~SunfishTestBase() override = default; |
| |
| // AshTestBase: |
| void SetUp() override { |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kAshDebugShortcuts); |
| AshTestBase::SetUp(); |
| |
| AckScannerDisclaimer(ScannerEntryPoint::kSmartActionsButton); |
| AckScannerDisclaimer(ScannerEntryPoint::kSunfishSession); |
| } |
| void TearDown() override { |
| // Clear the clipboard in case text was saved during a test. |
| ui::Clipboard::GetForCurrentThread()->Clear( |
| ui::ClipboardBuffer::kCopyPaste); |
| AshTestBase::TearDown(); |
| } |
| |
| private: |
| // Calling the factory constructor is enough to set it up. |
| TestAshWebViewFactory test_web_view_factory_; |
| }; |
| |
| class SunfishDisabledScannerDisabledTest : public SunfishTestBase { |
| public: |
| SunfishDisabledScannerDisabledTest() { |
| scoped_feature_list_.InitWithFeatures(/*enabled_features=*/{}, |
| /*disabled_features=*/{{ |
| features::kSunfishFeature, |
| features::kScannerDogfood, |
| features::kScannerUpdate, |
| }}); |
| } |
| SunfishDisabledScannerDisabledTest( |
| const SunfishDisabledScannerDisabledTest&) = delete; |
| SunfishDisabledScannerDisabledTest& operator=( |
| const SunfishDisabledScannerDisabledTest&) = delete; |
| ~SunfishDisabledScannerDisabledTest() override = default; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Tests that the debug accelerator entry point is a no-op when neither Sunfish |
| // nor Scanner is enabled. |
| TEST_F(SunfishDisabledScannerDisabledTest, DebugAccelEntryPointIsNoop) { |
| PressAndReleaseKey(ui::VKEY_8, |
| ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN); |
| |
| auto* controller = CaptureModeController::Get(); |
| EXPECT_FALSE(controller->IsActive()); |
| } |
| |
| // Tests that the accelerator entry point is a no-op when neither Sunfish nor |
| // Scanner is enabled. |
| TEST_F(SunfishDisabledScannerDisabledTest, AccelEntryPointIsNoop) { |
| PressAndReleaseKey(ui::VKEY_SPACE, ui::EF_COMMAND_DOWN); |
| |
| auto* controller = CaptureModeController::Get(); |
| EXPECT_FALSE(controller->IsActive()); |
| } |
| |
| // Tests that the accelerator entry point does not emit metrics when neither |
| // Sunfish nor Scanner is enabled. |
| TEST_F(SunfishDisabledScannerDisabledTest, |
| DebugAccelEntryPointDoesNotEmitMetrics) { |
| base::HistogramTester histogram_tester; |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSunfishSessionStartedFromDebugShortcut, 0); |
| |
| PressAndReleaseKey(ui::VKEY_8, |
| ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSunfishSessionStartedFromDebugShortcut, 0); |
| } |
| |
| // Tests that the debug accelerator entry point does not emit metrics when |
| // neither Sunfish nor Scanner is enabled. |
| TEST_F(SunfishDisabledScannerDisabledTest, AccelEntryPointDoesNotEmitMetrics) { |
| base::HistogramTester histogram_tester; |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSunfishSessionStartedFromKeyboardShortcut, 0); |
| |
| PressAndReleaseKey(ui::VKEY_8, |
| ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSunfishSessionStartedFromKeyboardShortcut, 0); |
| } |
| |
| TEST_F(SunfishDisabledScannerDisabledTest, |
| SmartActionsButtonNotShownDueToFeatureChecksRecorded) { |
| base::HistogramTester histogram_tester; |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSmartActionsButtonNotShownDueToFeatureChecks, |
| 0); |
| |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSmartActionsButtonNotShownDueToFeatureChecks, |
| 1); |
| } |
| |
| class SunfishDisabledTest : public SunfishTestBase { |
| public: |
| SunfishDisabledTest() { |
| scoped_feature_list_.InitAndDisableFeature(features::kSunfishFeature); |
| } |
| SunfishDisabledTest(const SunfishDisabledTest&) = delete; |
| SunfishDisabledTest& operator=(const SunfishDisabledTest&) = delete; |
| ~SunfishDisabledTest() override = default; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Tests that the search action button is not shown in the default capture mode |
| // if the feature is disabled. |
| TEST_F(SunfishDisabledTest, SearchActionButtonNotShown) { |
| // Start default capture mode *not* region selection. |
| StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kImage); |
| auto* controller = CaptureModeController::Get(); |
| ASSERT_TRUE(controller->IsActive()); |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| CaptureModeSessionTestApi session_test_api(session); |
| // Since we cannot select a region, no action buttons are shown. |
| ASSERT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| |
| // Set the source type to region, then select a region. |
| controller->SetSource(CaptureModeSource::kRegion); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| // There should not be any buttons. |
| EXPECT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| } |
| |
| // Tests that no text detection request is ever made in default capture mode if |
| // the feature is disabled. |
| TEST_F(SunfishDisabledTest, NoTextDetectionInDefaultMode) { |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| |
| // No text detection request should have been made, so there should not be a |
| // pending DLP check. |
| EXPECT_FALSE(CaptureModeTestApi().IsPendingDlpCheck()); |
| } |
| |
| class SunfishEnabledScannerDisabledTest : public SunfishTestBase { |
| public: |
| SunfishEnabledScannerDisabledTest() { |
| scoped_feature_list_.InitWithFeatures(/*enabled_features=*/ |
| {features::kSunfishFeature}, |
| /*disabled_features=*/{{ |
| features::kScannerDogfood, |
| features::kScannerUpdate, |
| }}); |
| } |
| SunfishEnabledScannerDisabledTest(const SunfishEnabledScannerDisabledTest&) = |
| delete; |
| SunfishEnabledScannerDisabledTest& operator=( |
| const SunfishEnabledScannerDisabledTest&) = delete; |
| ~SunfishEnabledScannerDisabledTest() override = default; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| TEST_F(SunfishEnabledScannerDisabledTest, |
| SunfishSessionImageCapturedAndActionsNotFetchedRecorded) { |
| base::HistogramTester histogram_tester; |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSunfishSessionImageCapturedAndActionsNotFetched, |
| 0); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSunfishSessionImageCapturedAndActionsNotFetched, |
| 1); |
| } |
| |
| // Tests that the "smart actions button not shown due to CanShowUi returning |
| // false" metric is emitted after OCR if Scanner is disabled. |
| TEST_F(SunfishEnabledScannerDisabledTest, |
| SmartActionsButtonNotShownDueToCanShowUiFalseRecorded) { |
| base::HistogramTester histogram_tester; |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSmartActionsButtonNotShownDueToCanShowUiFalse, |
| 0); |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run("detected text"); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSmartActionsButtonNotShownDueToCanShowUiFalse, |
| 1); |
| } |
| |
| class SunfishEnabledScannerEnabledTest : public SunfishTestBase { |
| public: |
| SunfishEnabledScannerEnabledTest() { |
| scoped_feature_list_.InitWithFeatures(/*enabled_features=*/ |
| { |
| features::kSunfishFeature, |
| features::kScannerDogfood, |
| features::kScannerUpdate, |
| }, |
| /*disabled_features=*/{}); |
| } |
| SunfishEnabledScannerEnabledTest(const SunfishEnabledScannerEnabledTest&) = |
| delete; |
| SunfishEnabledScannerEnabledTest& operator=( |
| const SunfishEnabledScannerEnabledTest&) = delete; |
| ~SunfishEnabledScannerEnabledTest() override = default; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| TEST_F(SunfishEnabledScannerEnabledTest, |
| SunfishSessionImageCapturedAndActionsNotFetchedRecorded) { |
| base::HistogramTester histogram_tester; |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| Shell::Get()->session_controller()->GetActivePrefService()->SetInteger( |
| prefs::kScannerEnterprisePolicyAllowed, |
| static_cast<int>(ScannerEnterprisePolicy::kDisallowed)); |
| capture_mode_controller->StartSunfishSession(); |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSunfishSessionImageCapturedAndActionsNotFetched, |
| 0); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSunfishSessionImageCapturedAndActionsNotFetched, |
| 1); |
| } |
| |
| class SunfishTest : public SunfishTestBase { |
| public: |
| SunfishTest() = default; |
| SunfishTest(const SunfishTest&) = delete; |
| SunfishTest& operator=(const SunfishTest&) = delete; |
| ~SunfishTest() override = default; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_{features::kSunfishFeature}; |
| }; |
| |
| // Tests that the debug accelerator starts capture mode in a new behavior. |
| TEST_F(SunfishTest, DebugAccelEntryPoint) { |
| PressAndReleaseKey(ui::VKEY_8, |
| ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN); |
| auto* controller = CaptureModeController::Get(); |
| ASSERT_TRUE(controller->IsActive()); |
| CaptureModeBehavior* active_behavior = |
| controller->capture_mode_session()->active_behavior(); |
| ASSERT_TRUE(active_behavior); |
| EXPECT_EQ(active_behavior->behavior_type(), BehaviorType::kSunfish); |
| } |
| |
| // Tests that the debug accelerator entry point emits the correct metrics. |
| TEST_F(SunfishTest, DebugAccelEntryPointMetrics) { |
| base::HistogramTester histogram_tester; |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSunfishSessionStartedFromDebugShortcut, 0); |
| |
| PressAndReleaseKey(ui::VKEY_8, |
| ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSunfishSessionStartedFromDebugShortcut, 1); |
| } |
| |
| // Tests that the debug accelerator entry point is a no-op when Sunfish is |
| // disabled by enterprise policy. |
| TEST_F(SunfishEnabledScannerDisabledTest, |
| DebugAccelEntryPointIsNoopIfEnterpriseDisabled) { |
| auto* controller = CaptureModeController::Get(); |
| ASSERT_TRUE(controller); |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| test_delegate->set_is_search_allowed_by_policy(false); |
| |
| PressAndReleaseKey(ui::VKEY_8, |
| ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN); |
| |
| EXPECT_FALSE(controller->IsActive()); |
| } |
| |
| // Tests that the accelerator starts capture mode in a new behavior. |
| TEST_F(SunfishTest, AccelEntryPoint) { |
| PressAndReleaseKey(ui::VKEY_SPACE, ui::EF_COMMAND_DOWN); |
| auto* controller = CaptureModeController::Get(); |
| ASSERT_TRUE(controller->IsActive()); |
| CaptureModeBehavior* active_behavior = |
| controller->capture_mode_session()->active_behavior(); |
| ASSERT_TRUE(active_behavior); |
| EXPECT_EQ(active_behavior->behavior_type(), BehaviorType::kSunfish); |
| } |
| |
| // Tests that the accelerator entry point emits the correct metrics. |
| TEST_F(SunfishTest, AccelEntryPointMetrics) { |
| base::HistogramTester histogram_tester; |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSunfishSessionStartedFromKeyboardShortcut, 0); |
| |
| PressAndReleaseKey(ui::VKEY_SPACE, ui::EF_COMMAND_DOWN); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSunfishSessionStartedFromKeyboardShortcut, 1); |
| } |
| |
| TEST_F(SunfishEnabledScannerDisabledTest, |
| AccelEntryPointIsNoopIfEnterpriseDisabled) { |
| auto* controller = CaptureModeController::Get(); |
| ASSERT_TRUE(controller); |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| test_delegate->set_is_search_allowed_by_policy(false); |
| |
| PressAndReleaseKey(ui::VKEY_SPACE, ui::EF_COMMAND_DOWN); |
| |
| EXPECT_FALSE(controller->IsActive()); |
| } |
| |
| TEST_F(SunfishTest, RecordsStartSunfishSessionMetric) { |
| base::HistogramTester histogram_tester; |
| |
| CaptureModeController::Get()->StartSunfishSession(); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSunfishScreenEnteredViaShortcut, 1); |
| } |
| |
| // Tests that the ESC key ends capture mode session. |
| TEST_F(SunfishTest, PressEscapeKey) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| |
| // Tests it starts sunfish behavior. |
| auto* session = controller->capture_mode_session(); |
| ASSERT_EQ(BehaviorType::kSunfish, |
| session->active_behavior()->behavior_type()); |
| |
| // Tests pressing ESC ends the session. |
| PressAndReleaseKey(ui::VKEY_ESCAPE, ui::EF_NONE); |
| ASSERT_FALSE(controller->IsActive()); |
| EXPECT_FALSE(controller->capture_mode_session()); |
| } |
| |
| TEST_F(SunfishTest, NoCrashOnEscapeBeforePanelShown) { |
| UpdateDisplay("800x600,800x600"); |
| std::unique_ptr<aura::Window> w1(CreateAppWindow()); |
| std::unique_ptr<aura::Window> w2( |
| CreateAppWindow(gfx::Rect(810, 10, 400, 400))); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(root_windows[0], w1->GetRootWindow()); |
| ASSERT_EQ(root_windows[1], w2->GetRootWindow()); |
| |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| |
| // Simulate pressing the Escape key, then the panel loading after the session |
| // is ended. |
| auto* generator = GetEventGenerator(); |
| generator->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE); |
| controller->OnSearchUrlFetched(gfx::Rect(10, 10, 400, 400), gfx::ImageSkia(), |
| GURL("kTestUrl")); |
| } |
| |
| // Tests that the Enter key does not attempt to perform capture or image search. |
| TEST_F(SunfishTest, PressEnterKey) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| |
| // While we are in waiting to select a capture region phase, pressing the |
| // Enter key will do nothing. |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| auto* capture_button = |
| session_test_api.GetCaptureLabelView()->capture_button_container(); |
| auto* capture_label = session_test_api.GetCaptureLabelInternalView(); |
| ASSERT_FALSE(capture_button->GetVisible()); |
| ASSERT_TRUE(capture_label->GetVisible()); |
| PressAndReleaseKey(ui::VKEY_RETURN); |
| EXPECT_TRUE(controller->IsActive()); |
| |
| // Immediately upon region selection, `PerformImageSearch()` and |
| // `OnCaptureImageAttempted()` will be called once. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| ASSERT_FALSE(capture_button->GetVisible()); |
| ASSERT_FALSE(capture_label->GetVisible()); |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_EQ(1, test_delegate->num_capture_image_attempts()); |
| |
| // Test that pressing the Enter key does not attempt image capture again. |
| PressAndReleaseKey(ui::VKEY_RETURN); |
| EXPECT_EQ(1, test_delegate->num_capture_image_attempts()); |
| } |
| |
| // Tests the session UI after a region is selected, adjusted, or repositioned, |
| // and the results panel is shown. |
| TEST_F(SunfishTest, OnRegionSelectedOrAdjusted) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| CaptureModeSessionTestApi test_api(session); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| auto* search_results_panel_widget = controller->search_results_panel_widget(); |
| EXPECT_TRUE(search_results_panel_widget); |
| EXPECT_TRUE(search_results_panel_widget->IsVisible()); |
| |
| // Test that the region selection UI remains visible. |
| auto* session_layer = controller->capture_mode_session()->layer(); |
| EXPECT_TRUE(session_layer->IsVisible()); |
| EXPECT_EQ(session_layer->GetTargetOpacity(), 1.f); |
| EXPECT_TRUE(test_api.AreAllUisVisible()); |
| |
| // Adjust the region from the northwest corner. |
| gfx::Rect old_region = controller->user_capture_region(); |
| auto* event_generator = GetEventGenerator(); |
| event_generator->MoveMouseTo(gfx::Point(100, 100)); |
| auto* cursor_manager = Shell::Get()->cursor_manager(); |
| EXPECT_EQ(ui::mojom::CursorType::kNorthWestResize, |
| cursor_manager->GetCursor().type()); |
| event_generator->PressLeftButton(); |
| event_generator->MoveMouseTo(gfx::Point(50, 50)); |
| ASSERT_TRUE(controller->capture_mode_session()->is_drag_in_progress()); |
| EXPECT_FALSE(search_results_panel_widget->IsVisible()); |
| event_generator->ReleaseLeftButton(); |
| EXPECT_NE(controller->user_capture_region(), old_region); |
| |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| EXPECT_TRUE(search_results_panel_widget); |
| EXPECT_TRUE(search_results_panel_widget->IsVisible()); |
| |
| // Reposition the region. |
| old_region = controller->user_capture_region(); |
| event_generator->MoveMouseTo(gfx::Point(150, 150)); |
| EXPECT_EQ(ui::mojom::CursorType::kMove, cursor_manager->GetCursor().type()); |
| event_generator->PressLeftButton(); |
| event_generator->MoveMouseTo(gfx::Point(200, 200)); |
| ASSERT_TRUE(controller->capture_mode_session()->is_drag_in_progress()); |
| EXPECT_FALSE(search_results_panel_widget->IsVisible()); |
| event_generator->ReleaseLeftButton(); |
| EXPECT_NE(controller->user_capture_region(), old_region); |
| |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| EXPECT_TRUE(controller->search_results_panel_widget()); |
| EXPECT_TRUE(search_results_panel_widget->IsVisible()); |
| event_generator->ReleaseLeftButton(); |
| } |
| |
| // Tests that the panel is not hidden on mouse down. |
| TEST_F(SunfishTest, DoNotHidePanelOnMousePressed) { |
| // Start sunfish and open the panel. |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| auto* generator = GetEventGenerator(); |
| SelectCaptureModeRegion(generator, gfx::Rect(50, 50, 400, 400), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_TRUE(controller->search_results_panel_widget()); |
| |
| // Press the sunfish bar close button. |
| CaptureModeSessionTestApi test_api(controller->capture_mode_session()); |
| auto* close_button = test_api.GetCaptureModeBarView()->close_button(); |
| ASSERT_TRUE(close_button); |
| |
| // Press down on a location. Test the panel is still visible. |
| generator->MoveMouseTo(10, 10); |
| generator->PressLeftButton(); |
| ASSERT_TRUE(controller->capture_mode_session()->is_drag_in_progress()); |
| ASSERT_TRUE(controller->search_results_panel_widget()); |
| EXPECT_TRUE(controller->search_results_panel_widget()->IsVisible()); |
| EXPECT_EQ( |
| controller->search_results_panel_widget()->GetLayer()->GetTargetOpacity(), |
| 1.f); |
| } |
| |
| // Tests the sunfish capture label view. |
| TEST_F(SunfishTest, CaptureLabelView) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| auto* session = controller->capture_mode_session(); |
| ASSERT_EQ(BehaviorType::kSunfish, |
| session->active_behavior()->behavior_type()); |
| |
| CaptureModeSessionTestApi test_api(session); |
| auto* capture_button = |
| test_api.GetCaptureLabelView()->capture_button_container(); |
| auto* capture_label = test_api.GetCaptureLabelInternalView(); |
| |
| // Before the drag, only the capture label is visible and is in waiting to |
| // select a capture region phase. |
| EXPECT_FALSE(capture_button->GetVisible()); |
| EXPECT_TRUE(capture_label->GetVisible()); |
| EXPECT_EQ(u"Drag or press Space to select an area to search", |
| capture_label->GetText()); |
| |
| // Tests it can drag and select a region. |
| auto* event_generator = GetEventGenerator(); |
| SelectCaptureModeRegion(event_generator, gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/false); |
| auto* dimensions_label = test_api.GetDimensionsLabelWidget(); |
| EXPECT_TRUE(dimensions_label && dimensions_label->IsVisible()); |
| |
| // During the drag, the label and button are both hidden. |
| EXPECT_FALSE(capture_button->GetVisible()); |
| EXPECT_FALSE(capture_label->GetVisible()); |
| |
| // Release the drag. The label and button should still both be hidden. |
| event_generator->ReleaseLeftButton(); |
| EXPECT_FALSE(capture_button->GetVisible()); |
| EXPECT_FALSE(capture_label->GetVisible()); |
| } |
| |
| // Tests the Search button does not show in default mode. |
| TEST_F(SunfishTest, CheckSearchPolicy) { |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| test_delegate->set_is_search_allowed_by_policy(false); |
| |
| // Start default session. Test the Search button does not show. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/false); |
| WaitForCaptureModeWidgetsVisible(); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| ASSERT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| } |
| |
| // Tests that sunfish checks DLP restrictions upon selecting a region. |
| TEST_F(SunfishTest, CheckDlpRestrictions) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| auto* session = controller->capture_mode_session(); |
| ASSERT_EQ(BehaviorType::kSunfish, |
| session->active_behavior()->behavior_type()); |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| test_delegate->set_is_allowed_by_dlp(false); |
| |
| // Tests after selecting a region, the session is ended. |
| auto* event_generator = GetEventGenerator(); |
| SelectCaptureModeRegion(event_generator, gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/false); |
| EXPECT_FALSE(controller->IsActive()); |
| test_delegate->set_is_allowed_by_dlp(true); |
| } |
| |
| // Tests that a full screenshot can be taken while in sunfish session. |
| TEST_F(SunfishTest, PerformFullScreenshot) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| EXPECT_EQ( |
| controller->capture_mode_session()->active_behavior()->behavior_type(), |
| BehaviorType::kSunfish); |
| |
| PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_CONTROL_DOWN); |
| base::FilePath file_saved_path = WaitForCaptureFileToBeSaved(); |
| ASSERT_TRUE(controller->IsActive()); |
| EXPECT_EQ( |
| controller->capture_mode_session()->active_behavior()->behavior_type(), |
| BehaviorType::kSunfish); |
| const base::FilePath default_folder = |
| controller->delegate_for_testing()->GetUserDefaultDownloadsFolder(); |
| EXPECT_EQ(file_saved_path.DirName(), default_folder); |
| } |
| |
| // Tests that the capture region is reset if sunfish is restarted. |
| TEST_F(SunfishTest, ResetCaptureRegion) { |
| // Start sunfish, then select a region. |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_EQ( |
| BehaviorType::kSunfish, |
| controller->capture_mode_session()->active_behavior()->behavior_type()); |
| |
| const gfx::Rect capture_region(100, 100, 600, 500); |
| SelectCaptureModeRegion(GetEventGenerator(), capture_region, |
| /*release_mouse=*/false); |
| EXPECT_EQ(capture_region, controller->user_capture_region()); |
| |
| // Exit sunfish, then restart sunfish. |
| PressAndReleaseKey(ui::VKEY_ESCAPE, ui::EF_NONE); |
| ASSERT_FALSE(controller->IsActive()); |
| |
| controller->StartSunfishSession(); |
| EXPECT_TRUE(controller->user_capture_region().IsEmpty()); |
| CaptureModeSessionTestApi test_api(controller->capture_mode_session()); |
| auto* capture_label = test_api.GetCaptureLabelInternalView(); |
| EXPECT_TRUE(capture_label->GetVisible()); |
| EXPECT_EQ(u"Drag or press Space to select an area to search", |
| capture_label->GetText()); |
| } |
| |
| // Tests the sunfish capture mode bar view. |
| TEST_F(SunfishTest, CaptureBarView) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| auto* session = controller->capture_mode_session(); |
| ASSERT_EQ(BehaviorType::kSunfish, |
| session->active_behavior()->behavior_type()); |
| |
| CaptureModeSessionTestApi test_api(session); |
| auto* bar_view = test_api.GetCaptureModeBarView(); |
| ASSERT_TRUE(bar_view); |
| |
| // The bar view should only have a close button. |
| auto* close_button = bar_view->close_button(); |
| ASSERT_TRUE(close_button); |
| ASSERT_FALSE(bar_view->settings_button()); |
| |
| // The sunfish bar does not have a settings button, so trying to set the menu |
| // shown should instead do nothing. |
| bar_view->SetSettingsMenuShown(true); |
| EXPECT_FALSE(bar_view->settings_button()); |
| |
| // Close the session using the button. |
| LeftClickOn(bar_view->close_button()); |
| ASSERT_FALSE(controller->capture_mode_session()); |
| } |
| |
| // Tests that the search results panel is draggable. |
| TEST_F(SunfishTest, DragSearchResultsPanel) { |
| auto widget = SearchResultsPanel::CreateWidget(Shell::GetPrimaryRootWindow(), |
| /*is_active=*/false); |
| widget->SetBounds(gfx::Rect(100, 100, |
| capture_mode::kSearchResultsPanelTotalWidth, |
| capture_mode::kSearchResultsPanelTotalHeight)); |
| widget->Show(); |
| |
| auto* search_results_panel = |
| views::AsViewClass<SearchResultsPanel>(widget->GetContentsView()); |
| auto* event_generator = GetEventGenerator(); |
| |
| // The results panel can be dragged by points outside the search results view |
| // and searchbox textfield. |
| const gfx::Point draggable_point( |
| search_results_panel->animation_view_for_test() |
| ->GetBoundsInScreen() |
| .origin() + |
| gfx::Vector2d(0, -3)); |
| event_generator->MoveMouseTo(draggable_point); |
| |
| // Test that dragging the panel to arbitrary points repositions the panel. |
| constexpr gfx::Vector2d kTestDragOffsets[] = { |
| gfx::Vector2d(-25, -5), gfx::Vector2d(-10, 20), gfx::Vector2d(0, 30), |
| gfx::Vector2d(35, -15)}; |
| for (const gfx::Vector2d& offset : kTestDragOffsets) { |
| const gfx::Rect widget_bounds(widget->GetWindowBoundsInScreen()); |
| event_generator->DragMouseBy(offset.x(), offset.y()); |
| EXPECT_EQ(widget->GetWindowBoundsInScreen(), widget_bounds + offset); |
| } |
| } |
| |
| TEST_F(SunfishTest, DragPanelInSession) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| auto* event_generator = GetEventGenerator(); |
| SelectCaptureModeRegion(event_generator, gfx::Rect(50, 50, 400, 400), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| auto* panel = controller->GetSearchResultsPanel(); |
| ASSERT_TRUE(panel); |
| ASSERT_TRUE(controller->IsActive()); |
| |
| // Start dragging the panel. |
| const gfx::Point draggable_point( |
| panel->animation_view_for_test()->GetBoundsInScreen().origin() + |
| gfx::Vector2d(0, -3)); |
| event_generator->MoveMouseTo(draggable_point); |
| event_generator->PressLeftButton(); |
| |
| // Test the session is still active. |
| ASSERT_TRUE(panel->IsDragging()); |
| EXPECT_TRUE(controller->IsActive()); |
| |
| // Move the panel. Test the session is still active. |
| event_generator->MoveMouseTo(400, 400); |
| ASSERT_TRUE(panel->IsDragging()); |
| EXPECT_TRUE(controller->IsActive()); |
| } |
| |
| class MockSearchResultsPanel : public SearchResultsPanel { |
| public: |
| MockSearchResultsPanel() = default; |
| MockSearchResultsPanel(MockSearchResultsPanel&) = delete; |
| MockSearchResultsPanel& operator=(MockSearchResultsPanel&) = delete; |
| ~MockSearchResultsPanel() override = default; |
| |
| void OnMouseEvent(ui::MouseEvent* event) override { |
| mouse_events_received_ = true; |
| SearchResultsPanel::OnMouseEvent(event); |
| |
| // Set random cursor types for testing. |
| auto* cursor_manager = Shell::Get()->cursor_manager(); |
| switch (event->type()) { |
| case ui::EventType::kMousePressed: |
| cursor_manager->SetCursorForced(ui::mojom::CursorType::kPointer); |
| break; |
| case ui::EventType::kMouseDragged: |
| cursor_manager->SetCursorForced(ui::mojom::CursorType::kCross); |
| break; |
| case ui::EventType::kMouseReleased: |
| cursor_manager->SetCursorForced(ui::mojom::CursorType::kHand); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| MOCK_METHOD(void, SetSearchBoxImage, (const gfx::ImageSkia&)); |
| MOCK_METHOD(void, Navigate, (const GURL&)); |
| |
| bool mouse_events_received() const { return mouse_events_received_; } |
| |
| private: |
| bool mouse_events_received_ = false; |
| }; |
| |
| // Tests that the search results panel receives mouse events. |
| TEST_F(SunfishTest, OnLocatedEvent) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| |
| // Simulate opening the panel during an active session. |
| auto* event_generator = GetEventGenerator(); |
| SelectCaptureModeRegion(event_generator, gfx::Rect(50, 50, 400, 400), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| views::Widget* widget = controller->search_results_panel_widget(); |
| ASSERT_TRUE(widget && widget->IsVisible()); |
| auto* search_results_panel = |
| widget->SetContentsView(std::make_unique<MockSearchResultsPanel>()); |
| ASSERT_TRUE(controller->IsActive()); |
| EXPECT_FALSE(search_results_panel->mouse_events_received()); |
| |
| // Simulate a click on the panel. |
| event_generator->MoveMouseTo( |
| search_results_panel->GetBoundsInScreen().CenterPoint()); |
| event_generator->ClickLeftButton(); |
| |
| // Test the panel receives mouse events. |
| EXPECT_TRUE(search_results_panel->mouse_events_received()); |
| } |
| |
| // Tests that the search results panel updates the cursor type. |
| TEST_F(SunfishTest, UpdateCursor) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| auto* cursor_manager = Shell::Get()->cursor_manager(); |
| EXPECT_EQ(ui::mojom::CursorType::kCell, cursor_manager->GetCursor().type()); |
| |
| // Select a capture region that doesn't overlap with the panel when it is |
| // shown. |
| auto* event_generator = GetEventGenerator(); |
| SelectCaptureModeRegion(event_generator, gfx::Rect(10, 10, 50, 50), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| views::Widget* widget = controller->search_results_panel_widget(); |
| ASSERT_TRUE(widget && widget->IsVisible()); |
| widget->SetContentsView(std::make_unique<MockSearchResultsPanel>()); |
| |
| const gfx::Rect panel_bounds(widget->GetWindowBoundsInScreen()); |
| ASSERT_FALSE( |
| panel_bounds.Contains(event_generator->current_screen_location())); |
| ASSERT_TRUE(controller->IsActive()); |
| event_generator->MoveMouseTo(panel_bounds.x() - 10, panel_bounds.y() - 10); |
| EXPECT_EQ(ui::mojom::CursorType::kCell, cursor_manager->GetCursor().type()); |
| |
| // Simulate a click on the panel. |
| event_generator->MoveMouseTo(panel_bounds.CenterPoint()); |
| event_generator->PressLeftButton(); |
| EXPECT_EQ(ui::mojom::CursorType::kPointer, |
| cursor_manager->GetCursor().type()); |
| |
| // Simulate a drag in the panel. |
| event_generator->MoveMouseBy(10, 10); |
| EXPECT_EQ(ui::mojom::CursorType::kCross, cursor_manager->GetCursor().type()); |
| |
| // Simulate mouse release in the panel. |
| event_generator->ReleaseLeftButton(); |
| EXPECT_EQ(ui::mojom::CursorType::kHand, cursor_manager->GetCursor().type()); |
| } |
| |
| TEST_F(SunfishTest, IsSearchResultsPanelVisible) { |
| // Show the panel. |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| auto* generator = GetEventGenerator(); |
| auto* cursor_manager = Shell::Get()->cursor_manager(); |
| EXPECT_EQ(ui::mojom::CursorType::kCell, cursor_manager->GetCursor().type()); |
| |
| // Select a capture region that will not overlap with the panel. |
| SelectCaptureModeRegion(generator, gfx::Rect(10, 10, 50, 50), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| views::Widget* widget = controller->search_results_panel_widget(); |
| ASSERT_TRUE(widget); |
| widget->SetContentsView(std::make_unique<MockSearchResultsPanel>()); |
| ASSERT_TRUE(controller->IsActive()); |
| const gfx::Rect panel_bounds(widget->GetWindowBoundsInScreen()); |
| |
| // Start selecting a new region obviously outside the panel. |
| generator->MoveMouseTo(panel_bounds.x() - 50, panel_bounds.y() - 50); |
| ASSERT_FALSE(controller->user_capture_region().Contains( |
| generator->current_screen_location())); |
| generator->PressLeftButton(); |
| generator->MoveMouseTo(panel_bounds.CenterPoint(), /*count=*/10); |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| ASSERT_TRUE(session->is_drag_in_progress()); |
| ASSERT_TRUE(session->is_selecting_region()); |
| EXPECT_FALSE(controller->search_results_panel_widget()->IsVisible()); |
| |
| // Test the panel does not receive events. |
| EXPECT_EQ(ui::mojom::CursorType::kSouthEastResize, |
| cursor_manager->GetCursor().type()); |
| } |
| |
| // Tests that while a video recording is in progress, starting sunfish works |
| // correctly. |
| TEST_F(SunfishTest, StartRecordingThenStartSunfish) { |
| // Start Capture Mode in a fullscreen video recording mode. |
| CaptureModeController* controller = StartCaptureSession( |
| CaptureModeSource::kFullscreen, CaptureModeType::kVideo); |
| EXPECT_TRUE(controller->IsActive()); |
| EXPECT_FALSE(controller->is_recording_in_progress()); |
| |
| // Start a video recording. |
| StartVideoRecordingImmediately(); |
| EXPECT_FALSE(controller->IsActive()); |
| EXPECT_TRUE(controller->is_recording_in_progress()); |
| |
| // Start sunfish session. |
| controller->StartSunfishSession(); |
| EXPECT_TRUE(controller->IsActive()); |
| |
| // Expect the behavior and UI to be updated. |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| EXPECT_EQ(session->active_behavior()->behavior_type(), |
| BehaviorType::kSunfish); |
| CaptureModeSessionTestApi test_api(session); |
| EXPECT_TRUE(views::AsViewClass<SunfishCaptureBarView>( |
| test_api.GetCaptureModeBarView())); |
| |
| // Before the drag, only the capture label is visible and is in waiting to |
| // select a capture region phase. |
| auto* capture_button = |
| test_api.GetCaptureLabelView()->capture_button_container(); |
| auto* capture_label = test_api.GetCaptureLabelInternalView(); |
| EXPECT_FALSE(capture_button->GetVisible()); |
| EXPECT_TRUE(capture_label->GetVisible()); |
| EXPECT_EQ(u"Drag or press Space to select an area to search", |
| capture_label->GetText()); |
| |
| // Test we can select a region and show the search results panel. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| EXPECT_TRUE(controller->search_results_panel_widget()); |
| |
| // Test we can stop video recording. |
| controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton); |
| EXPECT_FALSE(controller->is_recording_in_progress()); |
| } |
| |
| // Tests that when capture mode session is active, switching between behavior |
| // types updates the session type and UI. |
| TEST_F(SunfishTest, SwitchBehaviorTypes) { |
| // Start default capture mode session. |
| PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1, |
| ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN); |
| VerifyActiveBehavior(BehaviorType::kDefault); |
| |
| // Switch to sunfish session. |
| PressAndReleaseKey(ui::VKEY_8, |
| ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN); |
| VerifyActiveBehavior(BehaviorType::kSunfish); |
| |
| // Switch to default capture mode session. |
| PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1, |
| ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN); |
| VerifyActiveBehavior(BehaviorType::kDefault); |
| |
| // Switch to sunfish session. |
| PressAndReleaseKey(ui::VKEY_8, |
| ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN); |
| VerifyActiveBehavior(BehaviorType::kSunfish); |
| } |
| |
| // Tests that while a sunfish session has a region selected, calling the API |
| // will successfully create a new action button. |
| TEST_F(SunfishTest, AddActionButton) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| EXPECT_EQ(session_test_api.GetActionButtons().size(), 0u); |
| |
| // Attempt to add a new action button using the API. |
| capture_mode_util::AddActionButton( |
| views::Button::PressedCallback(), u"Do not show", &kCaptureModeImageIcon, |
| ActionButtonRank(ActionButtonType::kOther, 0), |
| ActionButtonViewID::kScannerButton); |
| |
| // The region has not been selected yet, so attempting to add a button should |
| // do nothing. |
| EXPECT_EQ(session_test_api.GetActionButtons().size(), 0u); |
| |
| // Select a region on the far left of the screen so we have space for the |
| // button between it and the search results panel. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| EXPECT_TRUE(controller->search_results_panel_widget()); |
| |
| // Create another action button that, when clicked, will change the value of a |
| // bool that can be verified later. |
| bool pressed = false; |
| capture_mode_util::AddActionButton( |
| base::BindLambdaForTesting([&]() { pressed = true; }), u"Test", |
| &kCaptureModeImageIcon, ActionButtonRank(ActionButtonType::kOther, 0), |
| ActionButtonViewID::kScannerButton); |
| |
| // There should only be one valid button in the session. |
| const std::vector<ActionButtonView*> action_buttons = |
| session_test_api.GetActionButtons(); |
| ASSERT_EQ(action_buttons.size(), 1u); |
| |
| // Clicking the button should successfully run the callback, and change the |
| // value of the bool. |
| LeftClickOn(action_buttons[0]); |
| ASSERT_TRUE(pressed); |
| } |
| |
| // Tests that action buttons of different types and priorities are displayed in |
| // the correct order. |
| TEST_F(SunfishTest, ActionButtonRank) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| EXPECT_EQ(session_test_api.GetActionButtons().size(), 0u); |
| |
| // Select a region on the far left of the screen so we have space for the |
| // button between it and the search results panel. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| EXPECT_TRUE(controller->search_results_panel_widget()); |
| |
| // Add a default action button. |
| capture_mode_util::AddActionButton( |
| views::Button::PressedCallback(), u"Default 1", &kCaptureModeImageIcon, |
| ActionButtonRank(ActionButtonType::kOther, 1), |
| ActionButtonViewID::kScannerButton); |
| EXPECT_EQ(session_test_api.GetActionButtons().size(), 1u); |
| |
| // Add a sunfish action button, which should appear in the rightmost position. |
| capture_mode_util::AddActionButton( |
| views::Button::PressedCallback(), u"Sunfish", &kCaptureModeImageIcon, |
| ActionButtonRank(ActionButtonType::kSunfish, 0), |
| ActionButtonViewID::kSearchButton); |
| auto action_buttons = session_test_api.GetActionButtons(); |
| EXPECT_EQ(action_buttons.size(), 2u); |
| EXPECT_EQ(action_buttons[1]->label_for_testing()->GetText(), u"Sunfish"); |
| |
| // Add another default action button, which should appear in the leftmost |
| // position. |
| capture_mode_util::AddActionButton( |
| views::Button::PressedCallback(), u"Default 2", &kCaptureModeImageIcon, |
| ActionButtonRank(ActionButtonType::kOther, 0), |
| ActionButtonViewID::kScannerButton); |
| action_buttons = session_test_api.GetActionButtons(); |
| EXPECT_EQ(action_buttons.size(), 3u); |
| EXPECT_EQ(action_buttons[0]->label_for_testing()->GetText(), u"Default 2"); |
| EXPECT_EQ(action_buttons[1]->label_for_testing()->GetText(), u"Default 1"); |
| EXPECT_EQ(action_buttons[2]->label_for_testing()->GetText(), u"Sunfish"); |
| |
| // Add a scanner action button, which should appear to the immediate left of |
| // the Sunfish action button. |
| capture_mode_util::AddActionButton( |
| views::Button::PressedCallback(), u"Scanner", &kCaptureModeImageIcon, |
| ActionButtonRank(ActionButtonType::kScanner, 0), |
| ActionButtonViewID::kScannerButton); |
| action_buttons = session_test_api.GetActionButtons(); |
| EXPECT_EQ(action_buttons.size(), 4u); |
| EXPECT_EQ(action_buttons[0]->label_for_testing()->GetText(), u"Default 2"); |
| EXPECT_EQ(action_buttons[1]->label_for_testing()->GetText(), u"Default 1"); |
| EXPECT_EQ(action_buttons[2]->label_for_testing()->GetText(), u"Scanner"); |
| EXPECT_EQ(action_buttons[3]->label_for_testing()->GetText(), u"Sunfish"); |
| } |
| |
| // Tests the behavior of the region overlay in default capture mode. |
| TEST_F(SunfishTest, CaptureRegionOverlay) { |
| // Start capture mode in a non-region source and type. Test the overlay |
| // controller is created but doesn't support region overlay. |
| StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kImage); |
| auto* controller = CaptureModeController::Get(); |
| ASSERT_TRUE(controller->IsActive()); |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| CaptureModeSessionTestApi test_api(session); |
| EXPECT_TRUE(test_api.GetCaptureRegionOverlayController()); |
| EXPECT_FALSE(session->active_behavior()->CanPaintRegionOverlay()); |
| |
| // Set the source to `kRegion`. Test the overlay controller is created and the |
| // behavior supports region overlay. |
| controller->SetSource(CaptureModeSource::kRegion); |
| EXPECT_TRUE(test_api.GetCaptureRegionOverlayController()); |
| EXPECT_TRUE(session->active_behavior()->CanPaintRegionOverlay()); |
| |
| // Set the source to `kWindow`. Though the overlay controller has been |
| // created, the behavior does not support region overlay. |
| controller->SetSource(CaptureModeSource::kWindow); |
| EXPECT_TRUE(test_api.GetCaptureRegionOverlayController()); |
| EXPECT_FALSE(session->active_behavior()->CanPaintRegionOverlay()); |
| } |
| |
| // Tests that the action container window has a title and accessible title in |
| // default capture mode. |
| TEST_F(SunfishTest, ActionContainerWindowTitleDefaultMode) { |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(10, 10, 50, 50), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| capture_mode_util::AddActionButton( |
| views::Button::PressedCallback(), u"Copy Text", &kCaptureModeImageIcon, |
| ActionButtonRank(ActionButtonType::kCopyText, 0), |
| ActionButtonViewID::kCopyTextButton); |
| |
| views::Widget* action_container_widget = |
| CaptureModeSessionTestApi(controller->capture_mode_session()) |
| .GetActionContainerWidget(); |
| ASSERT_TRUE(action_container_widget); |
| const std::u16string kActionContainerWindowTitle = l10n_util::GetStringUTF16( |
| IDS_ASH_SCREEN_CAPTURE_DEFAULT_ACTION_BUTTON_WINDOW_TITLE); |
| EXPECT_EQ(action_container_widget->widget_delegate()->GetWindowTitle(), |
| kActionContainerWindowTitle); |
| EXPECT_EQ( |
| action_container_widget->widget_delegate()->GetAccessibleWindowTitle(), |
| kActionContainerWindowTitle); |
| } |
| |
| // Tests that the action container window has a title and accessible title in |
| // Sunfish mode. |
| TEST_F(SunfishTest, ActionContainerWindowTitleSunfishMode) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(10, 10, 50, 50), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| capture_mode_util::AddActionButton( |
| views::Button::PressedCallback(), u"Copy Text", &kCaptureModeImageIcon, |
| ActionButtonRank(ActionButtonType::kCopyText, 0), |
| ActionButtonViewID::kCopyTextButton); |
| |
| views::Widget* action_container_widget = |
| CaptureModeSessionTestApi(controller->capture_mode_session()) |
| .GetActionContainerWidget(); |
| ASSERT_TRUE(action_container_widget); |
| const std::u16string kActionContainerWindowTitle = l10n_util::GetStringUTF16( |
| IDS_ASH_SCREEN_CAPTURE_SUNFISH_ACTION_BUTTON_WINDOW_TITLE); |
| EXPECT_EQ(action_container_widget->widget_delegate()->GetWindowTitle(), |
| kActionContainerWindowTitle); |
| EXPECT_EQ( |
| action_container_widget->widget_delegate()->GetAccessibleWindowTitle(), |
| kActionContainerWindowTitle); |
| } |
| |
| // Tests that the action container widget's opacity is updated properly before a |
| // region is selected, while a region is selected, and while the region is being |
| // adjusted. |
| TEST_F(SunfishTest, ActionContainerWidgetOpacity) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| EXPECT_EQ(session_test_api.GetActionButtons().size(), 0u); |
| |
| // The action container widget should be transparent before a region has been |
| // selected. |
| ASSERT_FALSE(session_test_api.GetActionContainerWidget()); |
| |
| // Attempt to add a new action button using the API. The container widget |
| // opacity should not change. |
| capture_mode_util::AddActionButton( |
| views::Button::PressedCallback(), u"Do not show", &kCaptureModeImageIcon, |
| ActionButtonRank(ActionButtonType::kOther, 0), |
| ActionButtonViewID::kScannerButton); |
| ASSERT_FALSE(session_test_api.GetActionContainerWidget()); |
| |
| // Select a new capture region that does not overlap with the search results |
| // panel. |
| auto* event_generator = GetEventGenerator(); |
| SelectCaptureModeRegion(event_generator, gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| auto* container_widget = session_test_api.GetActionContainerWidget(); |
| EXPECT_TRUE(container_widget->IsVisible()); |
| |
| // Begin adjusting the capture region by dragging from the bottom right |
| // corner. The container widget should be transluscent while the region is |
| // being adjusted. |
| event_generator->MoveMouseTo(gfx::Point(150, 150)); |
| event_generator->PressLeftButton(); |
| event_generator->MoveMouseTo(gfx::Point(300, 300)); |
| ASSERT_TRUE(controller->capture_mode_session()->is_drag_in_progress()); |
| EXPECT_FALSE(container_widget->IsVisible()); |
| |
| // Finish adjusting the region. The widget should now be visible. |
| event_generator->ReleaseLeftButton(); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_FALSE(controller->capture_mode_session()->is_drag_in_progress()); |
| EXPECT_TRUE(container_widget->IsVisible()); |
| } |
| |
| TEST_F(SunfishTest, DismissButtonsOnSourceChange) { |
| // Start default mode. Test the buttons are hidden. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| VerifyActiveBehavior(BehaviorType::kDefault); |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| CaptureModeSessionTestApi session_test_api(session); |
| ASSERT_FALSE(session_test_api.GetActionContainerWidget()); |
| |
| // Select a region large enough that the video capture button will not |
| // intersect with where the search action button previously was and wait for |
| // the buttons to show up. |
| auto* generator = GetEventGenerator(); |
| SelectCaptureModeRegion(generator, gfx::Rect(10, 10, 200, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForCaptureModeWidgetsVisible(); |
| auto* container_widget = session_test_api.GetActionContainerWidget(); |
| ASSERT_TRUE(container_widget->IsVisible()); |
| ASSERT_EQ(session_test_api.GetActionButtons().size(), 1u); |
| auto* search_button = session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton); |
| gfx::Rect search_button_bounds(search_button->GetBoundsInScreen()); |
| |
| // Set the type to `kVideo`. Test the buttons are hidden. |
| controller->SetType(CaptureModeType::kVideo); |
| EXPECT_FALSE(container_widget->IsVisible()); |
| EXPECT_EQ(session_test_api.GetActionButtons().size(), 0u); |
| auto* label_view = session_test_api.GetCaptureLabelView(); |
| ASSERT_TRUE(label_view->IsRecordingTypeDropDownButtonVisible()); |
| const gfx::Rect capture_button_bounds(label_view->capture_button_container() |
| ->capture_button() |
| ->GetBoundsInScreen()); |
| ASSERT_FALSE(capture_button_bounds.Intersects(search_button_bounds)); |
| auto* cursor_manager = Shell::Get()->cursor_manager(); |
| |
| // Hover over where the search button was previously shown. |
| generator->MoveMouseTo(search_button_bounds.CenterPoint()); |
| EXPECT_EQ(ui::mojom::CursorType::kCell, cursor_manager->GetCursor().type()); |
| |
| // Switch back to `kImage` then select a region. |
| controller->SetType(CaptureModeType::kImage); |
| CaptureModeTestApi().SetUserSelectedRegion(gfx::Rect()); |
| SelectCaptureModeRegion(generator, gfx::Rect(10, 10, 200, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForCaptureModeWidgetsVisible(); |
| EXPECT_TRUE(container_widget->IsVisible()); |
| EXPECT_EQ(session_test_api.GetActionButtons().size(), 1u); |
| search_button = session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton); |
| ASSERT_TRUE(search_button); |
| EXPECT_TRUE(search_button->GetVisible()); |
| search_button_bounds = search_button->GetBoundsInScreen(); |
| |
| // Set the source to `kFullscreen`. Test the buttons are hidden. |
| controller->SetSource(CaptureModeSource::kFullscreen); |
| EXPECT_FALSE(container_widget->IsVisible()); |
| EXPECT_EQ(session_test_api.GetActionButtons().size(), 0u); |
| EXPECT_EQ(ui::mojom::CursorType::kCustom, cursor_manager->GetCursor().type()); |
| |
| // Hover over where the search button was previously shown. |
| generator->MoveMouseTo(search_button_bounds.CenterPoint()); |
| EXPECT_EQ(ui::mojom::CursorType::kCustom, cursor_manager->GetCursor().type()); |
| |
| // Switch back to `kRegion` then select a region. |
| controller->SetSource(CaptureModeSource::kRegion); |
| CaptureModeTestApi().SetUserSelectedRegion(gfx::Rect()); |
| SelectCaptureModeRegion(generator, gfx::Rect(10, 10, 50, 50), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForCaptureModeWidgetsVisible(); |
| EXPECT_TRUE(container_widget->IsVisible()); |
| EXPECT_EQ(session_test_api.GetActionButtons().size(), 1u); |
| EXPECT_TRUE(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton)); |
| |
| // Set the source to `kWindow`. Test the buttons are hidden. |
| controller->SetSource(CaptureModeSource::kWindow); |
| EXPECT_FALSE(container_widget->IsVisible()); |
| EXPECT_EQ(session_test_api.GetActionButtons().size(), 0u); |
| |
| // TODO(b/377569542): Re-show the action buttons if the mode changes back to |
| // image region. |
| } |
| |
| // Tests that the search button is re-shown on region selected or adjusted in |
| // default mode. |
| TEST_F(SunfishTest, ShowSearchButtonOnRegionAdjusted) { |
| // Start default mode and show the search button. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| VerifyActiveBehavior(BehaviorType::kDefault); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForCaptureModeWidgetsVisible(); |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| CaptureModeSessionTestApi session_test_api(session); |
| ASSERT_EQ(session_test_api.GetActionButtons().size(), 1u); |
| auto* container_widget = session_test_api.GetActionContainerWidget(); |
| ASSERT_TRUE(container_widget); |
| |
| // Adjust the region from the northwest corner. |
| gfx::Rect old_region = controller->user_capture_region(); |
| auto* event_generator = GetEventGenerator(); |
| event_generator->MoveMouseTo(gfx::Point(100, 100)); |
| event_generator->PressLeftButton(); |
| event_generator->MoveMouseTo(gfx::Point(50, 50)); |
| |
| // During the drag, the buttons are hidden. |
| ASSERT_TRUE(session->is_drag_in_progress()); |
| EXPECT_EQ(container_widget->GetLayer()->GetTargetOpacity(), 0.f); |
| EXPECT_EQ(session_test_api.GetActionButtons().size(), 0u); |
| |
| // Release the drag. Test the buttons are re-shown. |
| event_generator->ReleaseLeftButton(); |
| WaitForCaptureModeWidgetsVisible(); |
| EXPECT_NE(controller->user_capture_region(), old_region); |
| EXPECT_EQ(container_widget->GetLayer()->GetTargetOpacity(), 1.f); |
| ASSERT_EQ(session_test_api.GetActionButtons().size(), 1u); |
| EXPECT_TRUE(session_test_api.GetActionButtons()[0]->GetVisible()); |
| |
| // Reposition the region. During the drag, the buttons are hidden. |
| old_region = controller->user_capture_region(); |
| event_generator->MoveMouseTo(gfx::Point(150, 150)); |
| event_generator->PressLeftButton(); |
| event_generator->MoveMouseTo(gfx::Point(200, 200)); |
| ASSERT_TRUE(session->is_drag_in_progress()); |
| EXPECT_EQ(container_widget->GetLayer()->GetTargetOpacity(), 0.f); |
| EXPECT_EQ(session_test_api.GetActionButtons().size(), 0u); |
| |
| // Release the drag. Test the buttons are re-shown. |
| event_generator->ReleaseLeftButton(); |
| WaitForCaptureModeWidgetsVisible(); |
| EXPECT_NE(controller->user_capture_region(), old_region); |
| EXPECT_EQ(container_widget->GetLayer()->GetTargetOpacity(), 1.f); |
| ASSERT_EQ(session_test_api.GetActionButtons().size(), 1u); |
| EXPECT_TRUE(session_test_api.GetActionButtons()[0]->GetVisible()); |
| } |
| |
| // Tests that there is a delay before showing the search button after the user |
| // adjusts a capture region with their keyboard. This is to prevent the search |
| // button repeatedly appearing and disappearing while the user adjusts the |
| // capture region with arrow keys. |
| TEST_F(SunfishTest, SearchButtonShownWithDelayAfterRegionAdjustedWithKeyboard) { |
| // Start default capture mode. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| |
| // Hit space to select a default region, then simulate a delay less than |
| // `kImageSearchRequestStartDelay`. |
| ui::test::EventGenerator* event_generator = GetEventGenerator(); |
| SendKey(ui::VKEY_SPACE, event_generator); |
| task_environment()->FastForwardBy(base::Milliseconds(150)); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| EXPECT_FALSE(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton)); |
| |
| // Press tab until the whole region is focused, shift the region using arrow |
| // keys, then simulate a delay less than `kImageSearchRequestStartDelay`. |
| SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6); |
| SendKey(ui::VKEY_RIGHT, event_generator, /*count=*/3); |
| task_environment()->FastForwardBy(base::Milliseconds(150)); |
| |
| EXPECT_FALSE(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton)); |
| |
| // Simulate a delay of `kImageSearchRequestStartDelay`. |
| task_environment()->FastForwardBy(kImageSearchRequestStartDelay); |
| |
| EXPECT_TRUE(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton)); |
| } |
| |
| // Tests that the search action button is shown in default capture mode. |
| TEST_F(SunfishTest, SearchActionButton) { |
| // Start default capture mode *not* region selection. |
| StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kImage); |
| auto* controller = CaptureModeController::Get(); |
| ASSERT_TRUE(controller->IsActive()); |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| CaptureModeSessionTestApi session_test_api(session); |
| // Since we cannot select a region, no action buttons are shown. |
| EXPECT_EQ(session_test_api.GetActionButtons().size(), 0u); |
| |
| // Set the source type to region, then select a region. |
| controller->SetSource(CaptureModeSource::kRegion); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForCaptureModeWidgetsVisible(); |
| ASSERT_EQ(session_test_api.GetActionButtons().size(), 1u); |
| EXPECT_TRUE(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton)); |
| |
| // Click on the "Search" button. Test we end capture mode. |
| LeftClickOn(session_test_api.GetActionButtons()[0]); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSearch); |
| EXPECT_TRUE(controller->search_results_panel_widget()); |
| EXPECT_FALSE(controller->IsActive()); |
| |
| // Restart capture mode session. The panel will be reset. |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| EXPECT_FALSE(controller->search_results_panel_widget()); |
| } |
| |
| // Tests that the search action button is not shown in the default capture mode |
| // if the user has disabled Sunfish. |
| TEST_F(SunfishTest, SearchActionButtonNotShownIfEnterpriseDisabled) { |
| auto* controller = CaptureModeController::Get(); |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| test_delegate->set_is_search_allowed_by_policy(false); |
| // Start default capture mode *not* region selection. |
| StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kImage); |
| ASSERT_TRUE(controller->IsActive()); |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| CaptureModeSessionTestApi session_test_api(session); |
| // Since we cannot select a region, no action buttons are shown. |
| ASSERT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| |
| // Set the source type to region, then select a region. |
| controller->SetSource(CaptureModeSource::kRegion); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| // There should not be any buttons. |
| EXPECT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| } |
| |
| // Tests that receiving a newly fetched URL updates the panel. Regression test |
| // for http://b/378019622. |
| TEST_F(SunfishTest, SendMultimodalSearch) { |
| // Start default mode. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| VerifyActiveBehavior(BehaviorType::kDefault); |
| |
| // Open the search results panel to end the session. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500)); |
| WaitForCaptureModeWidgetsVisible(); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| ASSERT_EQ(session_test_api.GetActionButtons().size(), 1u); |
| LeftClickOn(session_test_api.GetActionButtons()[0]); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSearch); |
| ASSERT_FALSE(controller->IsActive()); |
| ASSERT_TRUE(controller->GetSearchResultsPanel()); |
| |
| auto* search_results_panel = |
| controller->search_results_panel_widget()->SetContentsView( |
| std::make_unique<MockSearchResultsPanel>()); |
| |
| // Mock getting a new response from the server. Test the panel is updated. |
| EXPECT_CALL(*search_results_panel, Navigate(testing::_)); |
| |
| controller->ShowSearchResultsPanel(); |
| controller->NavigateSearchResultsPanel(GURL("kTestUrl2")); |
| } |
| |
| // Tests that the search results panel is closed when starting a new session. |
| TEST_F(SunfishTest, SwitchSessionsWhilePanelOpen) { |
| // Open the search results panel. |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| auto* generator = GetEventGenerator(); |
| SelectCaptureModeRegion(generator, gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| auto* search_results_panel = controller->GetSearchResultsPanel(); |
| ASSERT_TRUE(search_results_panel); |
| |
| // Switch to default mode. Test the panel is closed. |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| ASSERT_FALSE(controller->GetSearchResultsPanel()); |
| } |
| |
| // Tests no dangling ptr and crash when closing the panel. |
| TEST_F(SunfishTest, NoCrashOnUpdateCaptureUisOpacity) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| auto* generator = GetEventGenerator(); |
| SelectCaptureModeRegion(generator, gfx::Rect(50, 50, 400, 400), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| auto* panel_widget = controller->search_results_panel_widget(); |
| ASSERT_TRUE(panel_widget); |
| |
| // Click the close button and wait for it to close. |
| LeftClickOn(controller->GetSearchResultsPanel()->close_button()); |
| views::test::WidgetDestroyedWaiter(panel_widget).Wait(); |
| EXPECT_FALSE(controller->search_results_panel_widget()); |
| |
| // Start sunfish again and re-select a region. Test no crash. |
| controller->StartSunfishSession(); |
| SelectCaptureModeRegion(generator, gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/false); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| EXPECT_TRUE(controller->search_results_panel_widget()); |
| } |
| |
| TEST_F(SunfishTest, ClosePanelOnLockScreen) { |
| // Open the panel. |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| auto* generator = GetEventGenerator(); |
| SelectCaptureModeRegion(generator, gfx::Rect(50, 50, 400, 400), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_TRUE(controller->search_results_panel_widget()); |
| |
| // Lock the screen. Test the panel is hidden. |
| GetSessionControllerClient()->LockScreen(); |
| ASSERT_FALSE(controller->search_results_panel_widget()); |
| } |
| |
| // Tests no crash when tabbing. |
| TEST_F(SunfishTest, NoCrashOnTabKeyEvent) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| |
| PressAndReleaseKey(ui::VKEY_TAB); |
| CaptureModeSessionTestApi test_api(controller->capture_mode_session()); |
| EXPECT_EQ(test_api.GetCurrentFocusedView()->GetView(), GetCloseButton()); |
| } |
| |
| // Tests that the cursor is re-shown after performing Capture and Search |
| // in quick succession. |
| TEST_F(SunfishTest, IsCursorVisible) { |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| VerifyActiveBehavior(BehaviorType::kDefault); |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| auto* cursor_manager = Shell::Get()->cursor_manager(); |
| EXPECT_TRUE(cursor_manager->IsCursorVisible()); |
| |
| // Immediately capture the image, before the Search button and panel |
| // loads. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500)); |
| CaptureModeSessionTestApi session_test_api(session); |
| controller->CaptureScreenshotsOfAllDisplays(); |
| controller->PerformImageSearch(PerformCaptureType::kSearch); |
| WaitForCaptureFileToBeSaved(); |
| EXPECT_TRUE(cursor_manager->IsCursorVisible()); |
| } |
| |
| // Tests the search button shown and pressed metrics are being properly |
| // recorded. |
| TEST_F(SunfishTest, RecordSearchButtonShownAndPressed) { |
| base::HistogramTester histogram_tester; |
| constexpr char kSearchButtonPressedHistogram[] = |
| "Ash.CaptureModeController.SearchButtonPressed.ClamshellMode"; |
| constexpr char kSearchButtonShownHistogram[] = |
| "Ash.CaptureModeController.SearchButtonShown.ClamshellMode"; |
| |
| histogram_tester.ExpectBucketCount(kSearchButtonShownHistogram, true, 0); |
| histogram_tester.ExpectBucketCount(kSearchButtonPressedHistogram, true, 0); |
| |
| // Start default capture mode. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| VerifyActiveBehavior(BehaviorType::kDefault); |
| |
| // Select a region, which should show the search button. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForCaptureModeWidgetsVisible(); |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| CaptureModeSessionTestApi session_test_api(session); |
| ASSERT_EQ(session_test_api.GetActionButtons().size(), 1u); |
| auto* container_widget = session_test_api.GetActionContainerWidget(); |
| ASSERT_TRUE(container_widget); |
| |
| histogram_tester.ExpectBucketCount(kSearchButtonShownHistogram, true, 1); |
| histogram_tester.ExpectBucketCount(kSearchButtonPressedHistogram, true, 0); |
| |
| // Click on the search button. |
| LeftClickOn(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton)); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSearch); |
| EXPECT_TRUE(controller->search_results_panel_widget()); |
| |
| histogram_tester.ExpectBucketCount(kSearchButtonShownHistogram, true, 1); |
| histogram_tester.ExpectBucketCount(kSearchButtonPressedHistogram, true, 1); |
| } |
| |
| // Tests the `SearchResultsPanel` entry point metrics are being properly |
| // recorded. |
| TEST_F(SunfishTest, RecordSearchResultsPanelEntryType) { |
| base::HistogramTester histogram_tester; |
| constexpr char kSearchResultsPanelEntryPointHistogram[] = |
| "Ash.CaptureModeController.SearchResultsPanelEntryPoint.ClamshellMode"; |
| |
| histogram_tester.ExpectTotalCount(kSearchResultsPanelEntryPointHistogram, 0); |
| |
| // Start a Sunfish sunfish session, and select a region to create the search |
| // results panel. |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| WaitForCaptureModeWidgetsVisible(); |
| ASSERT_TRUE(controller->GetSearchResultsPanel()); |
| |
| histogram_tester.ExpectBucketCount( |
| kSearchResultsPanelEntryPointHistogram, |
| SearchResultsPanelEntryType::kSunfishRegionSelection, 1); |
| |
| // Stop the Sunfish session so we can start a default capture session instead. |
| // The search results panel should automatically close. |
| controller->Stop(); |
| controller->Start(CaptureModeEntryType::kQuickSettings); |
| ASSERT_FALSE(controller->GetSearchResultsPanel()); |
| |
| // The region will be remembered from the Sunfish session, but the action |
| // buttons will not be present, so reselect the region to make the Search |
| // button appear. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(50, 50, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForCaptureModeWidgetsVisible(); |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| CaptureModeSessionTestApi session_test_api(session); |
| ASSERT_EQ(session_test_api.GetActionButtons().size(), 1u); |
| |
| // Click on the search button to perform an image search and open |
| // the search results panel. |
| LeftClickOn(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton)); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSearch); |
| ASSERT_TRUE(controller->search_results_panel_widget()); |
| |
| histogram_tester.ExpectBucketCount( |
| kSearchResultsPanelEntryPointHistogram, |
| SearchResultsPanelEntryType::kDefaultSearchButton, 1); |
| histogram_tester.ExpectTotalCount(kSearchResultsPanelEntryPointHistogram, 2); |
| } |
| |
| // Tests that the panel position is updated properly based on the location of |
| // the selected region. |
| TEST_F(SunfishTest, PanelBounds) { |
| UpdateDisplay("2000x1000"); |
| |
| // Start a Sunfish sunfish session, and select a region to create the search |
| // results panel. |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(1000, 400, 100, 400), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_TRUE(controller->GetSearchResultsPanel()); |
| |
| // Get the in-screen bounds of the work area. |
| const gfx::Rect work_area = |
| controller->search_results_panel_widget()->GetWorkAreaBoundsInScreen(); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| |
| // Define the known possible coordinates of the search results panel. |
| const int left_x = work_area.x() + capture_mode::kPanelWorkAreaSpacing; |
| const int right_x = work_area.right() - |
| capture_mode::kSearchResultsPanelTotalWidth - |
| capture_mode::kPanelWorkAreaSpacing; |
| const int default_y = work_area.bottom() - |
| capture_mode::kSearchResultsPanelTotalHeight - |
| capture_mode::kPanelWorkAreaSpacing; |
| |
| // By default, the panel should appear on the left side. |
| gfx::Rect target_bounds(left_x, default_y, |
| capture_mode::kSearchResultsPanelTotalWidth, |
| capture_mode::kSearchResultsPanelTotalHeight); |
| EXPECT_EQ(controller->GetSearchResultsPanel()->GetBoundsInScreen(), |
| target_bounds); |
| |
| // Readjust the region on the left so it would intersect with the panel. |
| auto* event_generator = GetEventGenerator(); |
| event_generator->MoveMouseTo(gfx::Point(1000, 400)); |
| auto* cursor_manager = Shell::Get()->cursor_manager(); |
| EXPECT_EQ(ui::mojom::CursorType::kNorthWestResize, |
| cursor_manager->GetCursor().type()); |
| event_generator->PressLeftButton(); |
| event_generator->MoveMouseTo(200, 400); |
| event_generator->ReleaseLeftButton(); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_TRUE(controller->GetSearchResultsPanel()); |
| |
| // The panel should now appear on the right side instead. |
| target_bounds.set_x(right_x); |
| EXPECT_EQ(controller->GetSearchResultsPanel()->GetBoundsInScreen(), |
| target_bounds); |
| |
| // Readjust the region on the right so it would intersect with both panel |
| // positions, with slightly more space on the left side. |
| event_generator->MoveMouseTo(gfx::Point(1100, 400)); |
| EXPECT_EQ(ui::mojom::CursorType::kNorthEastResize, |
| cursor_manager->GetCursor().type()); |
| event_generator->PressLeftButton(); |
| event_generator->MoveMouseTo(1900, 400); |
| event_generator->ReleaseLeftButton(); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_TRUE(controller->GetSearchResultsPanel()); |
| |
| // The panel should appear back on the left side since there is more space. |
| target_bounds.set_x(left_x); |
| EXPECT_EQ(controller->GetSearchResultsPanel()->GetBoundsInScreen(), |
| target_bounds); |
| |
| // Readjust the region once more, so there is no space on the left side and |
| // a small amount of space on the right side. |
| event_generator->MoveMouseTo(gfx::Point(200, 400)); |
| EXPECT_EQ(ui::mojom::CursorType::kNorthWestResize, |
| cursor_manager->GetCursor().type()); |
| event_generator->PressLeftButton(); |
| event_generator->MoveMouseTo(0, 400); |
| event_generator->ReleaseLeftButton(); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_TRUE(controller->GetSearchResultsPanel()); |
| |
| // The panel should appear on the right side since there is more space. |
| target_bounds.set_x(right_x); |
| EXPECT_EQ(controller->GetSearchResultsPanel()->GetBoundsInScreen(), |
| target_bounds); |
| } |
| |
| // Tests that the default action button bounds are right aligned below the |
| // capture region. |
| TEST_F(SunfishTest, ActionButtonsRightAlignedBelowCaptureRegionByDefault) { |
| // Start default capture mode. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| |
| // Select a region, which should show the search button. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 50), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForCaptureModeWidgetsVisible(); |
| |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| CaptureModeSessionTestApi session_test_api(session); |
| const views::Widget* action_container_widget = |
| session_test_api.GetActionContainerWidget(); |
| ASSERT_TRUE(action_container_widget); |
| gfx::Rect action_container_bounds = |
| action_container_widget->GetWindowBoundsInScreen(); |
| EXPECT_FALSE(action_container_bounds.IsEmpty()); |
| // Action buttons should be right aligned with the capture region. |
| EXPECT_EQ(action_container_bounds.right(), |
| controller->user_capture_region().right()); |
| // Action buttons should be below the capture region. |
| EXPECT_GT(action_container_bounds.y(), |
| controller->user_capture_region().bottom()); |
| } |
| |
| // Tests that the sunfish launcher nudge appears and closes properly in |
| // clamshell mode, and that the prefs are updated properly. |
| TEST_F(SunfishTest, ClamshellLauncherNudge) { |
| AppListControllerImpl::SetSunfishNudgeDisabledForTest(false); |
| |
| // The nudge shown count should start at zero. |
| auto* pref_service = capture_mode_util::GetActiveUserPrefService(); |
| EXPECT_EQ(pref_service->GetInteger(prefs::kSunfishLauncherNudgeShownCount), |
| 0); |
| |
| // Advance clock so we aren't at zero time. |
| task_environment()->AdvanceClock(base::Hours(25)); |
| |
| // Open the app list by clicking on the home button. |
| LeftClickOn(GetPrimaryShelf()->navigation_widget()->GetHomeButton()); |
| auto* bubble_view = GetAppListBubbleView(); |
| ASSERT_TRUE(bubble_view); |
| auto* sunfish_button = bubble_view->search_box_view()->sunfish_button(); |
| ASSERT_TRUE(sunfish_button); |
| |
| // The Sunfish launcher nudge should be visible. |
| AnchoredNudgeManagerImpl* nudge_manager = |
| Shell::Get()->anchored_nudge_manager(); |
| const AnchoredNudge* nudge = |
| nudge_manager->GetNudgeIfShown(capture_mode::kSunfishLauncherNudgeId); |
| ASSERT_TRUE(nudge); |
| EXPECT_TRUE(nudge->GetVisible()); |
| EXPECT_EQ(pref_service->GetInteger(prefs::kSunfishLauncherNudgeShownCount), |
| 1); |
| |
| // Start the sunfish session using the launcher button. The nudge should |
| // close. |
| LeftClickOn(sunfish_button); |
| VerifyActiveBehavior(BehaviorType::kSunfish); |
| EXPECT_FALSE( |
| nudge_manager->GetNudgeIfShown(capture_mode::kSunfishLauncherNudgeId)); |
| |
| // Using the launcher button should set the nudge shown count to its limit. |
| EXPECT_EQ(pref_service->GetInteger(prefs::kSunfishLauncherNudgeShownCount), |
| capture_mode::kSunfishNudgeMaxShownCount); |
| } |
| |
| // Tests that the sunfish launcher nudge appears and closes properly in |
| // tablet mode, and that the prefs are updated properly. |
| TEST_F(SunfishTest, TabletLauncherNudge) { |
| AppListControllerImpl::SetSunfishNudgeDisabledForTest(false); |
| |
| // The nudge shown count should start at zero. |
| auto* pref_service = capture_mode_util::GetActiveUserPrefService(); |
| EXPECT_EQ(pref_service->GetInteger(prefs::kSunfishLauncherNudgeShownCount), |
| 0); |
| |
| // Advance clock so we aren't at zero time. |
| task_environment()->AdvanceClock(base::Hours(25)); |
| |
| SwitchToTabletMode(); |
| |
| // The app list should be open by default when we enter tablet mode. |
| auto* app_list_view = GetAppListView(); |
| ASSERT_TRUE(app_list_view); |
| auto* sunfish_button = app_list_view->search_box_view()->sunfish_button(); |
| ASSERT_TRUE(sunfish_button); |
| |
| // The Sunfish launcher nudge should be visible. |
| AnchoredNudgeManagerImpl* nudge_manager = |
| Shell::Get()->anchored_nudge_manager(); |
| const AnchoredNudge* nudge = |
| nudge_manager->GetNudgeIfShown(capture_mode::kSunfishLauncherNudgeId); |
| ASSERT_TRUE(nudge); |
| EXPECT_TRUE(nudge->GetVisible()); |
| EXPECT_EQ(pref_service->GetInteger(prefs::kSunfishLauncherNudgeShownCount), |
| 1); |
| |
| // Start the sunfish session using the launcher button. The nudge should |
| // close. |
| GestureTapOn(sunfish_button); |
| VerifyActiveBehavior(BehaviorType::kSunfish); |
| EXPECT_FALSE( |
| nudge_manager->GetNudgeIfShown(capture_mode::kSunfishLauncherNudgeId)); |
| |
| // Using the launcher button should set the nudge shown count to its limit. |
| EXPECT_EQ(pref_service->GetInteger(prefs::kSunfishLauncherNudgeShownCount), |
| capture_mode::kSunfishNudgeMaxShownCount); |
| } |
| |
| // Tests that the sunfish nudge only appears three times at most, with at least |
| // 24 hours between showings. |
| TEST_F(SunfishTest, LauncherNudgeLimits) { |
| AppListControllerImpl::SetSunfishNudgeDisabledForTest(false); |
| // Advance clock so we aren't at zero time. |
| task_environment()->AdvanceClock(base::Hours(25)); |
| |
| AnchoredNudgeManagerImpl* nudge_manager = |
| Shell::Get()->anchored_nudge_manager(); |
| AppListBubblePresenter* bubble_presenter = |
| Shell::Get()->app_list_controller()->bubble_presenter_for_test(); |
| for (int i = 0; i < 3; i++) { |
| // Click on the shelf home button to open up the app list launcher. |
| LeftClickOn(GetPrimaryShelf()->navigation_widget()->GetHomeButton()); |
| ASSERT_TRUE(GetAppListBubbleView()); |
| ASSERT_TRUE(bubble_presenter->IsShowing()); |
| |
| // Get the list of visible nudges from the nudge manager and make sure our |
| // education nudge is in the list and visible. |
| const AnchoredNudge* nudge = |
| nudge_manager->GetNudgeIfShown(capture_mode::kSunfishLauncherNudgeId); |
| ASSERT_TRUE(nudge); |
| EXPECT_TRUE(nudge->GetVisible()); |
| |
| // Showing the nudge should also update the preferences. |
| EXPECT_EQ(capture_mode_util::GetActiveUserPrefService()->GetInteger( |
| prefs::kSunfishLauncherNudgeShownCount), |
| i + 1); |
| |
| // Click on the shelf home button again to close the app list launcher (the |
| // view will still exist but not be visible). |
| LeftClickOn(GetPrimaryShelf()->navigation_widget()->GetHomeButton()); |
| ASSERT_FALSE(bubble_presenter->IsShowing()); |
| |
| // Closing the app list (and therefore the sunfish button) should hide the |
| // nudge. |
| EXPECT_FALSE( |
| nudge_manager->GetNudgeIfShown(capture_mode::kSunfishLauncherNudgeId)); |
| |
| // Advance the clock so we can show the nudge again. |
| task_environment()->AdvanceClock(base::Hours(25)); |
| } |
| |
| // Click on the shelf home button to open up the app list launcher. |
| LeftClickOn(GetPrimaryShelf()->navigation_widget()->GetHomeButton()); |
| ASSERT_TRUE(GetAppListBubbleView()); |
| ASSERT_TRUE(bubble_presenter->IsShowing()); |
| |
| // The nudge should not show anymore, as we have hit the show limit. |
| EXPECT_FALSE( |
| nudge_manager->GetNudgeIfShown(capture_mode::kSunfishLauncherNudgeId)); |
| EXPECT_EQ(capture_mode_util::GetActiveUserPrefService()->GetInteger( |
| prefs::kSunfishLauncherNudgeShownCount), |
| 3); |
| } |
| |
| // Test that the action buttons can be navigated using the keyboard, and that |
| // the indices are correct when new buttons are added. |
| TEST_F(SunfishTest, KeyboardNavigationActionButtons) { |
| // Start default mode. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| VerifyActiveBehavior(BehaviorType::kDefault); |
| |
| // Select a capture region. Only the "Search with Lens" should be present by |
| // default. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500)); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| ASSERT_EQ(session_test_api.GetActionButtons().size(), 1u); |
| |
| // Use tab to navigate to the action button. |
| auto* event_generator = GetEventGenerator(); |
| SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN, /*count=*/3); |
| ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kActionButtons, |
| session_test_api.GetCurrentFocusGroup()); |
| ASSERT_EQ(0u, session_test_api.GetCurrentFocusIndex()); |
| EXPECT_EQ(session_test_api.GetActionButtons()[0], |
| session_test_api.GetCurrentFocusedView()->GetView()); |
| |
| // Add a fake "Copy Text" button to the front of the list. |
| capture_mode_util::AddActionButton( |
| views::Button::PressedCallback(), u"Copy Text", &kCaptureModeImageIcon, |
| ActionButtonRank(ActionButtonType::kOther, 0), |
| ActionButtonViewID::kCopyTextButton); |
| |
| // The "Search with Lens" buttons should still be focused. |
| EXPECT_TRUE(CaptureModeSessionFocusCycler::HighlightHelper::Get( |
| session_test_api.GetActionButtons()[1]) |
| ->has_focus()); |
| |
| // Cycle backwards once. |
| SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN); |
| ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kActionButtons, |
| session_test_api.GetCurrentFocusGroup()); |
| ASSERT_EQ(0u, session_test_api.GetCurrentFocusIndex()); |
| |
| // We should now be focused on the "Copy Text" button, even though the focus |
| // index is still 0. |
| ActionButtonView* copy_text_button = session_test_api.GetActionButtons()[0]; |
| EXPECT_EQ(copy_text_button, session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kCopyTextButton)); |
| EXPECT_TRUE( |
| CaptureModeSessionFocusCycler::HighlightHelper::Get(copy_text_button) |
| ->has_focus()); |
| |
| // Cycle forwards once. |
| SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE); |
| ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kActionButtons, |
| session_test_api.GetCurrentFocusGroup()); |
| ASSERT_EQ(1u, session_test_api.GetCurrentFocusIndex()); |
| |
| // Add another generic test button to the end of the list. |
| capture_mode_util::AddActionButton( |
| views::Button::PressedCallback(), u"Test", &kCaptureModeImageIcon, |
| ActionButtonRank(ActionButtonType::kSunfish, 1), |
| ActionButtonViewID::kSmartActionsButton); |
| |
| // Cycle forwards once. |
| SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE); |
| ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kActionButtons, |
| session_test_api.GetCurrentFocusGroup()); |
| ASSERT_EQ(2u, session_test_api.GetCurrentFocusIndex()); |
| |
| // We should now be focused on the newest test button. |
| ActionButtonView* smart_action_button = |
| session_test_api.GetActionButtons()[2]; |
| EXPECT_EQ(smart_action_button, session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSmartActionsButton)); |
| EXPECT_TRUE( |
| CaptureModeSessionFocusCycler::HighlightHelper::Get(smart_action_button) |
| ->has_focus()); |
| } |
| |
| // Tests the panel stacking order in Sunfish session. |
| TEST_F(SunfishTest, PanelStackingOrder) { |
| // Show the panel in Sunfish session. Test it is stacked to the Search Results |
| // Panel container. |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| auto* generator = GetEventGenerator(); |
| SelectCaptureModeRegion(generator, gfx::Rect(50, 50, 400, 400), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| WaitForCaptureModeWidgetsVisible(); |
| auto* panel_widget = controller->search_results_panel_widget(); |
| ASSERT_TRUE(panel_widget); |
| aura::Window* panel_window = panel_widget->GetNativeWindow(); |
| EXPECT_EQ(Shell::GetContainer(panel_window->GetRootWindow(), |
| kShellWindowId_CaptureModeSearchResultsPanel), |
| panel_window->parent()); |
| |
| // Right click on the panel. Test we end the session and the panel is stacked |
| // to the System Modal container. |
| generator->MoveMouseTo(panel_widget->GetWindowBoundsInScreen().CenterPoint()); |
| EXPECT_TRUE(controller->IsActive()); |
| generator->ClickRightButton(); |
| EXPECT_FALSE(controller->IsActive()); |
| EXPECT_EQ(Shell::GetContainer(panel_window->GetRootWindow(), |
| kShellWindowId_SystemModalContainer), |
| panel_window->parent()); |
| |
| // Start default session. The panel will be closed. |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| EXPECT_FALSE(controller->search_results_panel_widget()); |
| |
| // Select a new region, then press the Search button. Test we end the session |
| // and the panel is stacked to the System Modal container. |
| CaptureModeTestApi test_api; |
| test_api.SetUserSelectedRegion(gfx::Rect()); |
| SelectCaptureModeRegion(generator, gfx::Rect(50, 50, 400, 400), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForCaptureModeWidgetsVisible(); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| LeftClickOn(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton)); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSearch); |
| panel_widget = controller->search_results_panel_widget(); |
| ASSERT_TRUE(panel_widget); |
| panel_window = panel_widget->GetNativeWindow(); |
| EXPECT_EQ(Shell::GetContainer(panel_window->GetRootWindow(), |
| kShellWindowId_SystemModalContainer), |
| panel_window->parent()); |
| } |
| |
| // Tests that keyboard navigation works properly when in a Sunfish session. |
| TEST_F(SunfishTest, KeyboardNavigationSunfishSession) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| |
| // Pressing tab before selecting a region in a Sunfish session should advance |
| // focus to the close button. |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| auto* event_generator = GetEventGenerator(); |
| SendKey(ui::VKEY_TAB, event_generator); |
| ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kSettingsClose, |
| session_test_api.GetCurrentFocusGroup()); |
| ASSERT_EQ(session_test_api.GetCurrentFocusedView()->GetView(), |
| session_test_api.GetCaptureModeBarView()->close_button()); |
| |
| // Since the close button is initially the only focusable view available, |
| // pressing tab again should result in nothing being focused. |
| SendKey(ui::VKEY_TAB, event_generator); |
| ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kNone, |
| session_test_api.GetCurrentFocusGroup()); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500)); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| auto* panel_widget = controller->search_results_panel_widget(); |
| ASSERT_TRUE(panel_widget); |
| |
| // Pressing tab should now focus the close button in the search results panel. |
| SendKey(ui::VKEY_TAB, event_generator); |
| ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kSearchResultsPanel, |
| session_test_api.GetCurrentFocusGroup()); |
| |
| // Press enter to close the panel so we can test the rest of the session |
| // elements. Focus should be restored to the session close button. |
| SendKey(ui::VKEY_RETURN, event_generator); |
| views::test::WidgetDestroyedWaiter(panel_widget).Wait(); |
| ASSERT_FALSE(controller->search_results_panel_widget()); |
| ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kSettingsClose, |
| session_test_api.GetCurrentFocusGroup()); |
| |
| // Pressing tab should cycle back to no elements being focused. |
| SendKey(ui::VKEY_TAB, event_generator); |
| ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kNone, |
| session_test_api.GetCurrentFocusGroup()); |
| |
| // The capture region should now be available for focus. |
| for (int i = 0; i < kRegionFocusCount; ++i) { |
| SendKey(ui::VKEY_TAB, event_generator); |
| ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kSelection, |
| session_test_api.GetCurrentFocusGroup()); |
| } |
| |
| // Add a single action button to test focus. |
| capture_mode_util::AddActionButton( |
| views::Button::PressedCallback(), u"Test", &kCaptureModeImageIcon, |
| ActionButtonRank(ActionButtonType::kOther, 0), |
| ActionButtonViewID::kScannerButton); |
| ASSERT_EQ(session_test_api.GetActionButtons().size(), 1u); |
| |
| // Pressing tab should finally cycle through the action button, the close |
| // button, then nothing. |
| SendKey(ui::VKEY_TAB, event_generator); |
| ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kActionButtons, |
| session_test_api.GetCurrentFocusGroup()); |
| SendKey(ui::VKEY_TAB, event_generator); |
| ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kSettingsClose, |
| session_test_api.GetCurrentFocusGroup()); |
| SendKey(ui::VKEY_TAB, event_generator); |
| ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kNone, |
| session_test_api.GetCurrentFocusGroup()); |
| } |
| |
| // Tests that restarting default mode within a short time will re-show the |
| // default action buttons. |
| TEST_F(SunfishTest, RestartDefaultModeReShowsActionButton) { |
| // Start default mode and select a region. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| VerifyActiveBehavior(BehaviorType::kDefault); |
| const gfx::Rect region(0, 0, 50, 200); |
| SelectCaptureModeRegion(GetEventGenerator(), region, |
| /*release_mouse=*/true, /*verify_region=*/true); |
| |
| // Test the Search button is shown. |
| auto* search_button = |
| CaptureModeSessionTestApi(controller->capture_mode_session()) |
| .GetActionButtonByViewId(ActionButtonViewID::kSearchButton); |
| ASSERT_TRUE(search_button); |
| EXPECT_TRUE(search_button->GetVisible()); |
| |
| // Exit then re-enter capture mode session. |
| controller->Stop(); |
| ASSERT_FALSE(controller->IsActive()); |
| |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| VerifyActiveBehavior(BehaviorType::kDefault); |
| |
| // Test the Capture and Search buttons are both shown. |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| auto* capture_button = |
| session_test_api.GetCaptureLabelView()->capture_button_container(); |
| ASSERT_TRUE(capture_button->GetVisible()); |
| search_button = session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton); |
| ASSERT_TRUE(search_button); |
| EXPECT_TRUE(search_button->GetVisible()); |
| } |
| |
| // Tests that the copy text button is shown in default capture mode if text is |
| // detected in the selected region. |
| TEST_F(SunfishTest, CopyTextButtonShownForDetectedText) { |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run("detected text"); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| // Copy text button should have been created. |
| const ActionButtonView* copy_text_button = |
| session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kCopyTextButton); |
| ASSERT_TRUE(copy_text_button); |
| // Clipboard should currently be empty. |
| std::u16string clipboard_data; |
| ui::Clipboard::GetForCurrentThread()->ReadText( |
| ui::ClipboardBuffer::kCopyPaste, /*data_dst=*/nullptr, &clipboard_data); |
| EXPECT_EQ(clipboard_data, u""); |
| // Clicking on the button should copy text to clipboard and show a toast. |
| LeftClickOn(copy_text_button); |
| ui::Clipboard::GetForCurrentThread()->ReadText( |
| ui::ClipboardBuffer::kCopyPaste, /*data_dst=*/nullptr, &clipboard_data); |
| EXPECT_EQ(clipboard_data, u"detected text"); |
| EXPECT_TRUE(ToastManager::Get()->IsToastShown(kCaptureModeTextCopiedToastId)); |
| } |
| |
| // Tests that the copy text button is shown in a sunfish session if text is |
| // detected in the selected region. |
| TEST_F(SunfishTest, CopyTextButtonShownForLensDetectedText) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| test_delegate->set_lens_detected_text("lens\ndetected text"); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| |
| // Copy text button should have been created. |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| const ActionButtonView* copy_text_button = |
| session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kCopyTextButton); |
| ASSERT_TRUE(copy_text_button); |
| |
| // The clipboard should currently be empty. |
| std::u16string clipboard_data; |
| ui::Clipboard::GetForCurrentThread()->ReadText( |
| ui::ClipboardBuffer::kCopyPaste, /*data_dst=*/nullptr, &clipboard_data); |
| EXPECT_EQ(clipboard_data, u""); |
| |
| // Clicking on the button should copy text to the clipboard and show a toast. |
| LeftClickOn(copy_text_button); |
| ui::Clipboard::GetForCurrentThread()->ReadText( |
| ui::ClipboardBuffer::kCopyPaste, /*data_dst=*/nullptr, &clipboard_data); |
| EXPECT_EQ(clipboard_data, u"lens\ndetected text"); |
| EXPECT_TRUE(ToastManager::Get()->IsToastShown(kCaptureModeTextCopiedToastId)); |
| |
| // Clear the clipboard for other tests that may need it. |
| ui::Clipboard::GetForCurrentThread()->Clear(ui::ClipboardBuffer::kCopyPaste); |
| } |
| |
| // Tests that the Sunfish region nudge is dismissed forever when an action |
| // button is shown to the user. |
| TEST_F(SunfishTest, SunfishRegionNudgeDismissedForever) { |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| auto* capture_session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| ASSERT_EQ(capture_session->session_type(), SessionType::kReal); |
| |
| // Starting a regular capture session should show the sunfish region nudge. |
| auto* capture_toast_controller = capture_session->capture_toast_controller(); |
| auto* nudge_controller = GetUserNudgeController(); |
| ASSERT_TRUE(nudge_controller); |
| EXPECT_TRUE(nudge_controller->is_visible()); |
| EXPECT_TRUE(capture_toast_controller->capture_toast_widget()); |
| ASSERT_TRUE(capture_toast_controller->current_toast_type()); |
| EXPECT_EQ(*(capture_toast_controller->current_toast_type()), |
| CaptureToastType::kUserNudge); |
| |
| // Select a valid region. There should be at least one action button, and the |
| // sunfish nudge should be dismissed. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 100, 100), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| ASSERT_TRUE(capture_session->action_container_widget()); |
| ASSERT_GE(CaptureModeSessionTestApi(controller->capture_mode_session()) |
| .GetActionButtons() |
| .size(), |
| 1u); |
| EXPECT_FALSE(GetUserNudgeController()); |
| |
| // Stop and restart the session. The nudge should not show again. |
| controller->Stop(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| EXPECT_FALSE(GetUserNudgeController()); |
| } |
| |
| // Tests that the search button is not shown when the network connection is |
| // offline. |
| TEST_F(SunfishTest, SearchButtonNotShownWhenOffline) { |
| // Start default capture mode. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| ON_CALL(*test_delegate, IsNetworkConnectionOffline) |
| .WillByDefault(Return(true)); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForCaptureModeWidgetsVisible(); |
| |
| // The search button should not be shown, and an error should be shown |
| // instead. |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| CaptureModeSessionTestApi session_test_api(session); |
| EXPECT_FALSE(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton)); |
| ActionButtonContainerView::ErrorView* error_view = |
| session_test_api.GetActionContainerErrorView(); |
| ASSERT_TRUE(error_view); |
| EXPECT_TRUE(error_view->GetVisible()); |
| } |
| |
| // Tests that selecting a region in Sunfish mode shows an error when the network |
| // connection is offline. |
| TEST_F(SunfishTest, SelectingRegionInSunfishModeShowsErrorIfOffline) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| ON_CALL(*test_delegate, IsNetworkConnectionOffline) |
| .WillByDefault(Return(true)); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForCaptureModeWidgetsVisible(); |
| |
| // An error should be shown. |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| CaptureModeSessionTestApi session_test_api(session); |
| ActionButtonContainerView::ErrorView* error_view = |
| session_test_api.GetActionContainerErrorView(); |
| ASSERT_TRUE(error_view); |
| EXPECT_TRUE(error_view->GetVisible()); |
| } |
| |
| // Tests that pressing the search button shows an error when the network |
| // connection is offline. |
| TEST_F(SunfishTest, PressingSearchButtonShowsErrorIfOffline) { |
| // Start default capture mode. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| // Simulate the network being online initially, so that the search button |
| // will appear when a region is selected. |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| ON_CALL(*test_delegate, IsNetworkConnectionOffline) |
| .WillByDefault(Return(false)); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForCaptureModeWidgetsVisible(); |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| CaptureModeSessionTestApi session_test_api(session); |
| ActionButtonView* search_button = session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton); |
| ASSERT_TRUE(search_button); |
| |
| // Simulate the network disconnecting before clicking on the search button. |
| ON_CALL(*test_delegate, IsNetworkConnectionOffline) |
| .WillByDefault(Return(true)); |
| LeftClickOn(search_button); |
| |
| // The session should still be active and an error should be shown. |
| ASSERT_TRUE(controller->IsActive()); |
| ActionButtonContainerView::ErrorView* error_view = |
| session_test_api.GetActionContainerErrorView(); |
| ASSERT_TRUE(error_view); |
| EXPECT_TRUE(error_view->GetVisible()); |
| } |
| |
| // Tests that if there is a lens web error when pressing the search button, we |
| // exit capture mode. |
| TEST_F(SunfishTest, PressingSearchButtonExitsIfLensError) { |
| // Start default capture mode. |
| CaptureModeController* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| |
| // Simulate a lens web error when pressing the search button. |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| test_delegate->set_force_lens_web_error(true); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForCaptureModeWidgetsVisible(); |
| |
| // Press the search button. |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| CaptureModeSessionTestApi session_test_api(session); |
| ActionButtonView* search_button = session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton); |
| LeftClickOn(search_button); |
| |
| // The session should no longer be active. |
| base::RunLoop run_loop; |
| test_delegate->set_on_session_state_changed_callback(run_loop.QuitClosure()); |
| run_loop.Run(); |
| ASSERT_FALSE(controller->IsActive()); |
| } |
| |
| TEST_F(SunfishTest, PinnedWindowExitSession) { |
| auto* controller = CaptureModeController::Get(); |
| |
| std::unique_ptr<aura::Window> pinned_window = CreateAppWindow(); |
| wm::ActivateWindow(pinned_window.get()); |
| window_util::PinWindow(pinned_window.get(), /*trusted=*/false); |
| EXPECT_FALSE(ash::CanShowSunfishUi()); |
| |
| Shell::Get()->accelerator_controller()->PerformActionIfEnabled( |
| AcceleratorAction::kUnpin, {}); |
| EXPECT_TRUE(ash::CanShowSunfishUi()); |
| |
| controller->StartSunfishSession(); |
| EXPECT_TRUE(controller->IsActive()); |
| window_util::PinWindow(pinned_window.get(), /*trusted=*/false); |
| EXPECT_FALSE(controller->IsActive()); |
| } |
| |
| TEST_F(SunfishTest, NudgeChangesRootWithBar) { |
| UpdateDisplay("800x700,801+0-800x700"); |
| |
| auto* event_generator = GetEventGenerator(); |
| event_generator->MoveMouseTo(gfx::Point(100, 500)); |
| |
| auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen, |
| CaptureModeType::kImage); |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| ASSERT_EQ(session->session_type(), SessionType::kReal); |
| auto* capture_toast_controller = session->capture_toast_controller(); |
| |
| EXPECT_EQ(Shell::GetAllRootWindows()[0], session->current_root()); |
| ASSERT_TRUE(capture_toast_controller->capture_toast_widget()); |
| EXPECT_EQ(capture_toast_controller->capture_toast_widget() |
| ->GetNativeWindow() |
| ->GetRootWindow(), |
| session->current_root()); |
| |
| event_generator->MoveMouseTo(gfx::Point(1000, 500)); |
| EXPECT_EQ(Shell::GetAllRootWindows()[1], session->current_root()); |
| ASSERT_TRUE(capture_toast_controller->capture_toast_widget()); |
| EXPECT_EQ(capture_toast_controller->capture_toast_widget() |
| ->GetNativeWindow() |
| ->GetRootWindow(), |
| session->current_root()); |
| } |
| |
| TEST_F(SunfishTest, NudgeBehaviorWhenSelectingRegion) { |
| UpdateDisplay("800x700,801+0-800x700"); |
| |
| auto* event_generator = GetEventGenerator(); |
| event_generator->MoveMouseTo(gfx::Point(100, 500)); |
| |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| auto* session = |
| static_cast<CaptureModeSession*>(controller->capture_mode_session()); |
| ASSERT_EQ(session->session_type(), SessionType::kReal); |
| EXPECT_EQ(Shell::GetAllRootWindows()[0], session->current_root()); |
| |
| // Nudge hides while selecting a region, but doesn't change roots until the |
| // region change is committed. |
| auto* nudge_controller = GetUserNudgeController(); |
| event_generator->MoveMouseTo(gfx::Point(1000, 500)); |
| event_generator->PressLeftButton(); |
| EXPECT_FALSE(nudge_controller->is_visible()); |
| |
| // If we release without selecting a valid region (i.e., an empty region), the |
| // nudge should be visible again. |
| event_generator->ReleaseLeftButton(); |
| EXPECT_EQ(Shell::GetAllRootWindows()[1], session->current_root()); |
| |
| // The nudge shows again, and is on the second display. |
| EXPECT_TRUE(nudge_controller->is_visible()); |
| ASSERT_TRUE(session->capture_toast_controller()->capture_toast_widget()); |
| EXPECT_EQ(session->capture_toast_controller() |
| ->capture_toast_widget() |
| ->GetNativeWindow() |
| ->GetRootWindow(), |
| session->current_root()); |
| } |
| |
| TEST_F(SunfishTest, NudgeDoesNotShowForAllUserTypes) { |
| struct { |
| std::string trace; |
| user_manager::UserType user_type; |
| bool can_see_nudge; |
| } kUserTypeTestCases[] = { |
| {"regular user", user_manager::UserType::kRegular, true}, |
| {"child", user_manager::UserType::kChild, true}, |
| {"guest", user_manager::UserType::kGuest, false}, |
| {"public account", user_manager::UserType::kPublicAccount, false}, |
| {"kiosk app", user_manager::UserType::kKioskChromeApp, false}, |
| {"web kiosk app", user_manager::UserType::kKioskWebApp, false}, |
| }; |
| |
| for (const auto& test_case : kUserTypeTestCases) { |
| SCOPED_TRACE(test_case.trace); |
| ClearLogin(); |
| SimulateUserLogin({"example@gmail.com", test_case.user_type}); |
| |
| auto* controller = StartCaptureSession(CaptureModeSource::kRegion, |
| CaptureModeType::kImage); |
| ASSERT_EQ(test_case.can_see_nudge, controller->CanShowSunfishRegionNudge()); |
| |
| auto* nudge_controller = GetUserNudgeController(); |
| ASSERT_EQ(test_case.can_see_nudge, !!nudge_controller); |
| |
| controller->Stop(); |
| } |
| } |
| |
| using SunfishMultiDisplayTest = SunfishTest; |
| |
| TEST_F(SunfishMultiDisplayTest, SelectNewRegionAndPanelRoot) { |
| UpdateDisplay("800x600,1000x900"); |
| aura::Window::Windows roots = Shell::GetAllRootWindows(); |
| EXPECT_THAT(roots, SizeIs(2)); |
| |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| |
| // Select a region on display 1. |
| auto* generator = GetEventGenerator(); |
| SelectCaptureModeRegion(generator, gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_EQ(roots[0], controller->capture_mode_session()->current_root()); |
| auto* panel_window = |
| controller->search_results_panel_widget()->GetNativeWindow(); |
| EXPECT_EQ(roots[0], controller->search_results_panel_widget() |
| ->GetNativeWindow() |
| ->GetRootWindow()); |
| |
| // Start a drag on display 2. |
| generator->MoveMouseTo(1010, 10); |
| generator->PressLeftButton(); |
| generator->MoveMouseBy(100, 100); |
| |
| // The panel will be hidden during the drag. |
| ASSERT_TRUE(controller->capture_mode_session()->is_drag_in_progress()); |
| EXPECT_FALSE(controller->search_results_panel_widget()->IsVisible()); |
| |
| // Release the drag. Test the region and panel are on display 2. |
| generator->ReleaseLeftButton(); |
| ASSERT_EQ(roots[1], controller->capture_mode_session()->current_root()); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| EXPECT_EQ(roots[1], controller->search_results_panel_widget() |
| ->GetNativeWindow() |
| ->GetRootWindow()); |
| EXPECT_EQ(Shell::GetContainer(roots[1], |
| kShellWindowId_CaptureModeSearchResultsPanel), |
| panel_window->parent()); |
| |
| // Start a drag on display 1 again. |
| generator->MoveMouseTo(10, 10); |
| generator->PressLeftButton(); |
| generator->MoveMouseBy(100, 100); |
| ASSERT_TRUE(controller->capture_mode_session()->is_drag_in_progress()); |
| EXPECT_FALSE(controller->search_results_panel_widget()->IsVisible()); |
| |
| // Release the drag. Test the region and panel are on display 1. |
| generator->ReleaseLeftButton(); |
| ASSERT_EQ(roots[0], controller->capture_mode_session()->current_root()); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| EXPECT_EQ(roots[0], controller->search_results_panel_widget() |
| ->GetNativeWindow() |
| ->GetRootWindow()); |
| EXPECT_EQ(Shell::GetContainer(roots[0], |
| kShellWindowId_CaptureModeSearchResultsPanel), |
| panel_window->parent()); |
| |
| // Stop the session. Test the panel stays on display 1. |
| controller->Stop(); |
| ASSERT_EQ(gfx::Rect(10, 10, 100, 100), controller->user_capture_region()); |
| EXPECT_EQ(roots[0], controller->search_results_panel_widget() |
| ->GetNativeWindow() |
| ->GetRootWindow()); |
| |
| // Restart the session. The region and panel will be cleared. |
| controller->StartSunfishSession(); |
| ASSERT_EQ(gfx::Rect(), controller->user_capture_region()); |
| EXPECT_FALSE(controller->search_results_panel_widget()); |
| } |
| |
| // Should not show scanner disclaimer since scanner is not enabled. |
| TEST_F(SunfishEnabledScannerDisabledTest, DoesNotShowScannerDisclaimer) { |
| UnackAllScannerDisclaimers(); |
| |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_FALSE(disclaimer); |
| } |
| |
| // Tests that the action buttons and the capture label button do not overlap |
| // when the capture label button cannot fit inside the region. |
| TEST_F(SunfishTest, ActionButtonsCaptureButtonNoOverlap) { |
| UpdateDisplay("2000x1000"); |
| |
| // Start a regular capture session. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| |
| // Select a small capture region in each corner. The action buttons and the |
| // capture button should not intersect (or overlap). |
| std::vector<std::pair<int, int>> corners = { |
| {0, 0}, {1950, 0}, {0, 950}, {1950, 950}}; |
| for (auto corner : corners) { |
| SelectCaptureModeRegion( |
| GetEventGenerator(), |
| gfx::Rect(corner.first, corner.second, kSmallRegionEdgeLength, |
| kSmallRegionEdgeLength), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| const views::Widget* action_container_widget = |
| session_test_api.GetActionContainerWidget(); |
| EXPECT_FALSE(action_container_widget->GetWindowBoundsInScreen().Intersects( |
| session_test_api.GetCaptureLabelWidget()->GetWindowBoundsInScreen())); |
| } |
| } |
| |
| // Tests that opening the search results panel while a settings menu is open and |
| // observed by the focus cycler does not result in a crash. |
| TEST_F(SunfishTest, PanelCreationWithMenuObserved) { |
| using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup; |
| |
| // Start a regular capture session and select a region. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(200, 200, 100, 100), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| |
| // Shift-Tab two times to focus on the settings button. |
| auto* event_generator = GetEventGenerator(); |
| SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN, /*count=*/2); |
| EXPECT_EQ(FocusGroup::kSettingsClose, |
| session_test_api.GetCurrentFocusGroup()); |
| EXPECT_EQ(0u, session_test_api.GetCurrentFocusIndex()); |
| |
| // Press the enter key to open the settings menu. The current focus group |
| // should be `kPendingSettings`. |
| SendKey(ui::VKEY_RETURN, event_generator); |
| ASSERT_TRUE(session_test_api.GetCaptureModeSettingsView()); |
| EXPECT_EQ(FocusGroup::kPendingSettings, |
| session_test_api.GetCurrentFocusGroup()); |
| |
| // Tab once to enter focus into the settings menu. |
| SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE); |
| ASSERT_EQ(FocusGroup::kSettingsMenu, session_test_api.GetCurrentFocusGroup()); |
| |
| // Click on the search button with the menu open to end the session and open |
| // the search results panel. |
| auto* search_button = session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton); |
| ASSERT_TRUE(search_button); |
| LeftClickOn(search_button); |
| } |
| |
| using SunfishDisplayMetricsTest = SunfishTest; |
| |
| TEST_F(SunfishDisplayMetricsTest, RefreshPanelBoundsInDefaultMode) { |
| // Start default mode, select a region and press "Search" to show the panel. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForCaptureModeWidgetsVisible(); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| auto* search_button = session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSearchButton); |
| ASSERT_TRUE(search_button); |
| LeftClickOn(search_button); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSearch); |
| ASSERT_FALSE(controller->IsActive()); |
| auto* panel = controller->GetSearchResultsPanel(); |
| ASSERT_TRUE(panel); |
| const gfx::Rect initial_panel_bounds(panel->GetBoundsInScreen()); |
| |
| // Zoom in until the panel is cropped to fit within the work area. |
| display::Screen* screen = display::Screen::Get(); |
| do { |
| PressAndReleaseKey(ui::VKEY_OEM_PLUS, |
| ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| // Test the panel stays within the display work area bounds. |
| const gfx::Rect display_bounds(screen->GetPrimaryDisplay().work_area()); |
| EXPECT_TRUE(display_bounds.Contains(panel->GetBoundsInScreen())); |
| } while (initial_panel_bounds.size() == panel->GetBoundsInScreen().size()); |
| |
| // Zoom out. Test the panel returns to its preferred size instead of being |
| // cropped to fit. |
| PressAndReleaseKey(ui::VKEY_OEM_MINUS, |
| ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| EXPECT_EQ(initial_panel_bounds.size(), panel->GetBoundsInScreen().size()); |
| } |
| |
| TEST_F(SunfishDisplayMetricsTest, RefreshPanelBoundsInSunfishMode) { |
| // Start sunfish mode, select a region and wait to show the panel. |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_TRUE(controller->IsActive()); |
| auto* panel = controller->GetSearchResultsPanel(); |
| ASSERT_TRUE(panel); |
| const gfx::Rect initial_panel_bounds(panel->GetBoundsInScreen()); |
| |
| // Zoom in until the panel is cropped to fit within the work area. |
| display::Screen* screen = display::Screen::Get(); |
| do { |
| PressAndReleaseKey(ui::VKEY_OEM_PLUS, |
| ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| // Test the panel stays within the display work area bounds. |
| const gfx::Rect display_bounds(screen->GetPrimaryDisplay().work_area()); |
| EXPECT_TRUE(display_bounds.Contains(panel->GetBoundsInScreen())); |
| } while (initial_panel_bounds.size() == panel->GetBoundsInScreen().size()); |
| |
| // Zoom out. Test the panel returns to its preferred size instead of being |
| // cropped to fit. |
| PressAndReleaseKey(ui::VKEY_OEM_MINUS, |
| ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| EXPECT_EQ(initial_panel_bounds.size(), panel->GetBoundsInScreen().size()); |
| } |
| |
| class ScannerTest : public AshTestBase { |
| public: |
| ScannerTest() |
| : AshTestBase( |
| base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME) { |
| scoped_feature_list_.InitWithFeatures({features::kScannerUpdate}, |
| {features::kSunfishFeature}); |
| } |
| ScannerTest(const ScannerTest&) = delete; |
| ScannerTest& operator=(const ScannerTest&) = delete; |
| ~ScannerTest() override = default; |
| |
| // Starts a default capture session, and mocks selecting a user region with |
| // text to show the "Smart Actions" button. |
| ActionButtonView* GetSmartActionsButton() { |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = static_cast<TestCaptureModeDelegate*>( |
| controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| // Reset the user-selected region to ensure the region passed to |
| // `SelectCaptureModeRegion()` is verified without interference from any |
| // previously selected region. This guarantees consistent and predictable |
| // behavior during tests. |
| CaptureModeTestApi().SetUserSelectedRegion(gfx::Rect()); |
| |
| // Select a region to trigger text detection. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kTextDetection); |
| detect_text_future.Take().Run("detected text"); |
| |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| ActionButtonView* action_button = session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSmartActionsButton); |
| EXPECT_TRUE(action_button); |
| return action_button; |
| } |
| |
| // testing::Test: |
| void SetUp() override { |
| AshTestBase::SetUp(); |
| |
| AckScannerDisclaimer(ScannerEntryPoint::kSmartActionsButton); |
| AckScannerDisclaimer(ScannerEntryPoint::kSunfishSession); |
| } |
| |
| MockNewWindowDelegate& new_window_delegate() { return new_window_delegate_; } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| MockNewWindowDelegate new_window_delegate_; |
| }; |
| |
| // Tests that a Scanner session is created when a Sunfish session begins. |
| TEST_F(ScannerTest, CreatesScannerSession) { |
| CaptureModeController::Get()->StartSunfishSession(); |
| |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| EXPECT_TRUE(scanner_controller->HasActiveSessionForTesting()); |
| } |
| |
| // Tests that action buttons are created when a Scanner response includes |
| // suggested actions. |
| TEST_F(ScannerTest, CreatesScannerActionButtons) { |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(WithArg<1>(InvokeFuture(fetch_actions_future))); |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| |
| auto output = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects = *output->add_objects(); |
| objects.add_actions()->mutable_new_event()->set_title("Event 1"); |
| objects.add_actions()->mutable_new_event()->set_title("Event 2"); |
| fetch_actions_future.Take().Run(std::move(output), manta::MantaStatus()); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| capture_mode_controller->capture_mode_session()); |
| EXPECT_THAT(session_test_api.GetActionButtons(), SizeIs(2)); |
| } |
| |
| // Tests that the "smart actions button not shown due to feature checks" metrics |
| // is not recorded when a region is selected in a Sunfish-session after |
| // `CanShowSunfishOrScannerUi` turns false. Removing the default session check |
| // guarding the metric will cause this test to fail. |
| TEST_F(ScannerTest, SmartActionsButtonNotShownDueToFeatureChecksNotRecorded) { |
| base::HistogramTester histogram_tester; |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSmartActionsButtonNotShownDueToFeatureChecks, |
| 0); |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| |
| // Forcibly turn `CanShowSunfishOrScannerUi` off by disabling the Search |
| // policy and the Scanner policy. |
| auto* test_delegate = static_cast<TestCaptureModeDelegate*>( |
| capture_mode_controller->delegate_for_testing()); |
| test_delegate->set_is_search_allowed_by_policy(false); |
| Shell::Get()->session_controller()->GetActivePrefService()->SetInteger( |
| prefs::kScannerEnterprisePolicyAllowed, |
| static_cast<int>(ScannerEnterprisePolicy::kDisallowed)); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSmartActionsButtonNotShownDueToFeatureChecks, |
| 0); |
| } |
| |
| TEST_F(ScannerTest, SunfishSessionImageCapturedAndActionsFetchedRecorded) { |
| base::HistogramTester histogram_tester; |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(WithArg<1>(InvokeFuture(fetch_actions_future))); |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState:: |
| kSunfishSessionImageCapturedAndActionsFetchStarted, |
| 0); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState:: |
| kSunfishSessionImageCapturedAndActionsFetchStarted, |
| 1); |
| } |
| |
| // Tests that action buttons are created when the Scanner response returns as |
| // fast as possible. |
| TEST_F(ScannerTest, FetchActionsImmediately) { |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| auto output = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects = *output->add_objects(); |
| objects.add_actions()->mutable_new_event()->set_title("Event 1"); |
| objects.add_actions()->mutable_new_event()->set_title("Event 2"); |
| // Synchronously send the fake actions response after the image is captured. |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(RunOnceCallback<1>(std::move(output), manta::MantaStatus())); |
| |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| |
| // We should have successfully created two action buttons. |
| const CaptureModeSessionTestApi session_test_api( |
| capture_mode_controller->capture_mode_session()); |
| EXPECT_THAT(session_test_api.GetActionButtons(), SizeIs(2)); |
| } |
| |
| // Tests that the action container shows an error if an error occurs while |
| // trying to fetch Scanner actions. |
| TEST_F(ScannerTest, ShowsErrorWhenScannerResponseContainsError) { |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(WithArg<1>(InvokeFuture(fetch_actions_future))); |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| |
| fetch_actions_future.Take().Run( |
| nullptr, |
| manta::MantaStatus{.status_code = manta::MantaStatusCode::kInvalidInput}); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| capture_mode_controller->capture_mode_session()); |
| EXPECT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| const ActionButtonContainerView::ErrorView* error_view = |
| session_test_api.GetActionContainerErrorView(); |
| ASSERT_TRUE(error_view); |
| EXPECT_TRUE(error_view->GetVisible()); |
| } |
| |
| // Tests that the user can click try again to try fetching Scanner actions again |
| // if their initial attempt failed. |
| TEST_F(ScannerTest, CanTryAgainWhenScannerResponseContainsError) { |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillRepeatedly(WithArg<1>(InvokeFuture(fetch_actions_future))); |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| |
| // Simulate an error so that the try again link appears. |
| fetch_actions_future.Take().Run( |
| nullptr, |
| manta::MantaStatus{.status_code = manta::MantaStatusCode::kInvalidInput}); |
| const CaptureModeSessionTestApi session_test_api( |
| capture_mode_controller->capture_mode_session()); |
| ActionButtonContainerView::ErrorView* error_view = |
| session_test_api.GetActionContainerErrorView(); |
| views::View* try_again_link = error_view->try_again_link(); |
| ASSERT_TRUE(try_again_link); |
| EXPECT_TRUE(try_again_link->GetVisible()); |
| |
| // Click the try again link. |
| LeftClickOn(try_again_link); |
| |
| // Now simulate a successful response. |
| auto output = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects = *output->add_objects(); |
| objects.add_actions()->mutable_new_event()->set_title("Event 1"); |
| objects.add_actions()->mutable_new_event()->set_title("Event 2"); |
| fetch_actions_future.Take().Run(std::move(output), manta::MantaStatus()); |
| |
| // Action buttons should be shown. |
| EXPECT_FALSE(error_view->GetVisible()); |
| EXPECT_THAT(session_test_api.GetActionButtons(), SizeIs(2)); |
| } |
| |
| // Tests that the user can use keyboard navigation to try fetching Scanner |
| // actions again if their initial attempt failed. |
| TEST_F(ScannerTest, |
| KeyboardNavigationTryAgainWhenScannerResponseContainsError) { |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillRepeatedly(WithArg<1>(InvokeFuture(fetch_actions_future))); |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| |
| // Simulate an error so that the try again link appears. |
| fetch_actions_future.Take().Run( |
| nullptr, |
| manta::MantaStatus{.status_code = manta::MantaStatusCode::kInvalidInput}); |
| CaptureModeSessionTestApi session_test_api( |
| capture_mode_controller->capture_mode_session()); |
| ActionButtonContainerView::ErrorView* error_view = |
| session_test_api.GetActionContainerErrorView(); |
| EXPECT_TRUE(error_view->GetVisible()); |
| views::View* try_again_link = error_view->try_again_link(); |
| ASSERT_TRUE(try_again_link); |
| EXPECT_TRUE(try_again_link->GetVisible()); |
| |
| // Use tab to navigate to the try again link. |
| auto* event_generator = GetEventGenerator(); |
| SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN, /*count=*/2); |
| EXPECT_EQ(session_test_api.GetCurrentFocusGroup(), |
| CaptureModeSessionFocusCycler::FocusGroup::kActionButtons); |
| EXPECT_TRUE( |
| CaptureModeSessionFocusCycler::HighlightHelper::Get(try_again_link) |
| ->has_focus()); |
| // Press enter to activate the try again link. |
| SendKey(ui::VKEY_RETURN, event_generator); |
| |
| // Now simulate a successful response. |
| auto output = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects = *output->add_objects(); |
| objects.add_actions()->mutable_new_event()->set_title("Event 1"); |
| objects.add_actions()->mutable_new_event()->set_title("Event 2"); |
| fetch_actions_future.Take().Run(std::move(output), manta::MantaStatus()); |
| |
| // Action buttons should be shown. |
| EXPECT_FALSE(error_view->GetVisible()); |
| EXPECT_THAT(session_test_api.GetActionButtons(), SizeIs(2)); |
| } |
| |
| // Tests that the try again button is not shown when an unsupported language |
| // error occurs. |
| TEST_F(ScannerTest, CannotTryAgainIfUnsupportedLanguageDetected) { |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillRepeatedly(WithArg<1>(InvokeFuture(fetch_actions_future))); |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| |
| // Simulate an unsupported language error. |
| fetch_actions_future.Take().Run( |
| nullptr, |
| manta::MantaStatus{.status_code = |
| manta::MantaStatusCode::kUnsupportedLanguage}); |
| |
| // An error view should be shown without a try again link. |
| const CaptureModeSessionTestApi session_test_api( |
| capture_mode_controller->capture_mode_session()); |
| ActionButtonContainerView::ErrorView* error_view = |
| session_test_api.GetActionContainerErrorView(); |
| ASSERT_TRUE(error_view); |
| EXPECT_TRUE(error_view->GetVisible()); |
| EXPECT_FALSE(error_view->try_again_link()->GetVisible()); |
| } |
| |
| // Tests that action buttons for a stale Scanner request are not added to a new |
| // and different region the same session. |
| TEST_F(ScannerTest, DoesNotCreateActionButtonsForNewerDifferentRegion) { |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| stale_fetch_actions_future; |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(WithArg<1>(InvokeFuture(stale_fetch_actions_future))) |
| .WillOnce(DoDefault()); |
| |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| const CaptureModeSessionTestApi session_test_api( |
| capture_mode_controller->capture_mode_session()); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(50, 50, 500, 400), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| auto output = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects = *output->add_objects(); |
| objects.add_actions()->mutable_new_event()->set_title("Event 1"); |
| objects.add_actions()->mutable_new_event()->set_title("Event 2"); |
| stale_fetch_actions_future.Take().Run(std::move(output), |
| manta::MantaStatus()); |
| |
| // The callback for the stale actions should not affect the current actions. |
| EXPECT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| } |
| |
| // Tests that action buttons for a stale Scanner request are not added to a new |
| // but identical region in the same session. |
| TEST_F(ScannerTest, DoesNotCreateActionButtonsForNewerSameRegion) { |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| stale_fetch_actions_future; |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(WithArg<1>(InvokeFuture(stale_fetch_actions_future))) |
| .WillOnce(DoDefault()); |
| |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| const CaptureModeSessionTestApi session_test_api( |
| capture_mode_controller->capture_mode_session()); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| // Reselect the same region. Trying to drag the exact same bounds will instead |
| // modify the existing region, so we need to clear the region first. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| auto output = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects = *output->add_objects(); |
| objects.add_actions()->mutable_new_event()->set_title("Event 1"); |
| objects.add_actions()->mutable_new_event()->set_title("Event 2"); |
| stale_fetch_actions_future.Take().Run(std::move(output), |
| manta::MantaStatus()); |
| |
| // The callback for the stale actions should not affect the current actions. |
| EXPECT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| } |
| |
| // Tests that action buttons for a stale Scanner request are not added to a new |
| // empty region in the same session. |
| TEST_F(ScannerTest, DoesNotCreateActionButtonsForNewerEmptyRegion) { |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| stale_fetch_actions_future; |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(WithArg<1>(InvokeFuture(stale_fetch_actions_future))); |
| |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| const CaptureModeSessionTestApi session_test_api( |
| capture_mode_controller->capture_mode_session()); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| ASSERT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| auto output = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects = *output->add_objects(); |
| objects.add_actions()->mutable_new_event()->set_title("Event 1"); |
| objects.add_actions()->mutable_new_event()->set_title("Event 2"); |
| stale_fetch_actions_future.Take().Run(std::move(output), |
| manta::MantaStatus()); |
| |
| // The callback for the stale actions should not affect the current actions. |
| EXPECT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| } |
| |
| // Tests that action buttons for a stale Scanner request are not added to region |
| // that is currently being selected, that is identical to the region that was |
| // selected for the stale Scanner request. |
| TEST_F(ScannerTest, DoesNotCreateActionButtonsForNewerSameRegionBeingSelected) { |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| stale_fetch_actions_future; |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(WithArg<1>(InvokeFuture(stale_fetch_actions_future))); |
| |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| const CaptureModeSessionTestApi session_test_api( |
| capture_mode_controller->capture_mode_session()); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| // Reselect the same region without releasing the mouse. Trying to drag the |
| // exact same bounds will instead modify the existing region, so we need to |
| // clear the region first. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/false, /*verify_region=*/true); |
| ASSERT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| auto output = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects = *output->add_objects(); |
| objects.add_actions()->mutable_new_event()->set_title("Event 1"); |
| objects.add_actions()->mutable_new_event()->set_title("Event 2"); |
| stale_fetch_actions_future.Take().Run(std::move(output), |
| manta::MantaStatus()); |
| |
| // The callback for the stale actions should not affect the current actions. |
| EXPECT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| } |
| |
| // Tests that action buttons for a stale Scanner request are not added to region |
| // that was fine-tuned, but did not change. |
| TEST_F(ScannerTest, DoesNotCreateActionButtonsForFineTuneNoop) { |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .Times(2) |
| .WillRepeatedly(WithArg<1>(InvokeFuture(fetch_actions_future))); |
| constexpr auto kCaptureRegion = gfx::Rect(100, 100, 600, 500); |
| |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| const CaptureModeSessionTestApi session_test_api( |
| capture_mode_controller->capture_mode_session()); |
| SelectCaptureModeRegion(GetEventGenerator(), kCaptureRegion, |
| /*release_mouse=*/true, /*verify_region=*/true); |
| manta::ScannerProvider::ScannerProtoResponseCallback stale_fetch_actions = |
| fetch_actions_future.Take(); |
| ASSERT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| const gfx::Point drag_affordance_location = |
| capture_mode_util::GetLocationForFineTunePosition( |
| kCaptureRegion, FineTunePosition::kBottomRightVertex); |
| GetEventGenerator()->MoveMouseTo(drag_affordance_location); |
| GetEventGenerator()->ClickLeftButton(); |
| ASSERT_TRUE(fetch_actions_future.Wait()) |
| << "Fetch actions was not called again after fine-tune"; |
| ASSERT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| auto output = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects = *output->add_objects(); |
| objects.add_actions()->mutable_new_event()->set_title("Event 1"); |
| objects.add_actions()->mutable_new_event()->set_title("Event 2"); |
| std::move(stale_fetch_actions).Run(std::move(output), manta::MantaStatus()); |
| |
| // The callback for the stale actions should not affect the current actions. |
| EXPECT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| } |
| |
| // Tests that action buttons for a stale Scanner request are not added to region |
| // that is currently being selected, that is identical to the region that was |
| // selected for the stale Scanner request. |
| TEST_F(ScannerTest, |
| DoesNotCreateActionButtonsForNewerEmptyRegionBeingSelected) { |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| stale_fetch_actions_future; |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(WithArg<1>(InvokeFuture(stale_fetch_actions_future))); |
| |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| const CaptureModeSessionTestApi session_test_api( |
| capture_mode_controller->capture_mode_session()); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| ASSERT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(), |
| /*release_mouse=*/false, /*verify_region=*/true); |
| ASSERT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| auto output = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects = *output->add_objects(); |
| objects.add_actions()->mutable_new_event()->set_title("Event 1"); |
| objects.add_actions()->mutable_new_event()->set_title("Event 2"); |
| stale_fetch_actions_future.Take().Run(std::move(output), |
| manta::MantaStatus()); |
| |
| // The callback for the stale actions should not affect the current actions. |
| EXPECT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| } |
| |
| TEST_F(ScannerTest, PressingActionButtonsEndSession) { |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| manta::proto::ScannerOutput output; |
| output.add_objects()->add_actions()->mutable_new_event()->set_title( |
| "Event 1"); |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(RunOnceCallback<1>( |
| std::make_unique<manta::proto::ScannerOutput>(output), |
| manta::MantaStatus())); |
| |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| const CaptureModeSessionTestApi session_test_api( |
| capture_mode_controller->capture_mode_session()); |
| std::vector<ActionButtonView*> action_buttons = |
| session_test_api.GetActionButtons(); |
| ASSERT_THAT( |
| action_buttons, |
| ElementsAre(ActionButtonIdIs(ActionButtonViewID::kScannerButton))); |
| LeftClickOn(action_buttons[0]); |
| |
| EXPECT_FALSE(capture_mode_controller->GetSearchResultsPanel()); |
| EXPECT_FALSE(capture_mode_controller->IsActive()); |
| } |
| |
| // Tests that no text detection request is ever made in default capture mode if |
| // the Scanner enabled pref is false. |
| TEST_F(ScannerTest, NoTextDetectionInDefaultModeIfEnabledPrefIsFalse) { |
| Shell::Get()->session_controller()->GetActivePrefService()->SetBoolean( |
| prefs::kScannerEnabled, false); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| |
| // No text detection request should have been made, so there should not be a |
| // pending DLP check. |
| EXPECT_FALSE(CaptureModeTestApi().IsPendingDlpCheck()); |
| } |
| |
| // Tests that the copy text button is shown in default capture mode if text is |
| // detected in the selected region. |
| TEST_F(ScannerTest, CopyTextButtonShownForDetectedText) { |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run("detected text"); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| // Copy text button should have been created. |
| const ActionButtonView* copy_text_button = |
| session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kCopyTextButton); |
| ASSERT_TRUE(copy_text_button); |
| // Clipboard should currently be empty. |
| std::u16string clipboard_data; |
| ui::Clipboard::GetForCurrentThread()->ReadText( |
| ui::ClipboardBuffer::kCopyPaste, /*data_dst=*/nullptr, &clipboard_data); |
| EXPECT_EQ(clipboard_data, u""); |
| // Clicking on the button should copy text to clipboard and show a toast. |
| LeftClickOn(copy_text_button); |
| ui::Clipboard::GetForCurrentThread()->ReadText( |
| ui::ClipboardBuffer::kCopyPaste, /*data_dst=*/nullptr, &clipboard_data); |
| EXPECT_EQ(clipboard_data, u"detected text"); |
| EXPECT_TRUE(ToastManager::Get()->IsToastShown(kCaptureModeTextCopiedToastId)); |
| } |
| |
| // Tests that restarting default mode within a short time will re-show the |
| // Copy Text button. |
| TEST_F(ScannerTest, RestartDefaultModeReshowsCopyTextButton) { |
| // Select a capture region with text. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future_1; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future_1))); |
| |
| const gfx::Rect capture_region(0, 0, 50, 200); |
| SelectCaptureModeRegion(GetEventGenerator(), capture_region, |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future_1.Take().Run("detected text"); |
| |
| // Copy text button should have been created. |
| ActionButtonView* copy_text_button = |
| CaptureModeSessionTestApi(controller->capture_mode_session()) |
| .GetActionButtonByViewId(ActionButtonViewID::kCopyTextButton); |
| ASSERT_TRUE(copy_text_button); |
| |
| // Exit then re-enter capture mode session. |
| controller->Stop(); |
| ASSERT_FALSE(controller->IsActive()); |
| |
| // Re-enter capture mode session. Test it runs text detection. |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future_2; |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future_2))); |
| |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| VerifyActiveBehavior(BehaviorType::kDefault); |
| ASSERT_EQ(capture_region, controller->user_capture_region()); |
| detect_text_future_2.Take().Run("detected text"); |
| |
| // Test the Capture button and Copy Text button are both created. |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| auto* capture_button = |
| session_test_api.GetCaptureLabelView()->capture_button_container(); |
| ASSERT_TRUE(capture_button->GetVisible()); |
| copy_text_button = session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kCopyTextButton); |
| ASSERT_TRUE(copy_text_button); |
| } |
| |
| // Tests that the copy text button is not shown in default capture mode if no |
| // text is detected in the selected region. |
| TEST_F(ScannerTest, NoCopyTextButtonIfNoDetectedText) { |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run(""); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| EXPECT_FALSE(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kCopyTextButton)); |
| } |
| |
| // Tests that the "smart actions button not shown due to no text detected" |
| // metric is emitted if no text is detected in the selected region. |
| TEST_F(ScannerTest, NoDetectedTextMetrics) { |
| base::HistogramTester histogram_tester; |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSmartActionsButtonNotShownDueToNoTextDetected, |
| 0); |
| |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run(""); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSmartActionsButtonNotShownDueToNoTextDetected, |
| 1); |
| } |
| |
| // Tests that the copy text button is not shown in default capture mode if the |
| // OCR request for the selected region fails. |
| TEST_F(ScannerTest, NoCopyTextButtonIfOcrRequestFailed) { |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run(std::nullopt); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| EXPECT_FALSE(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kCopyTextButton)); |
| } |
| |
| // Tests that the "smart actions button not shown due to text detection |
| // cancelled" metric is emitted if the OCR reports as cancelled. |
| TEST_F(ScannerTest, OcrRequestFailedMetrics) { |
| base::HistogramTester histogram_tester; |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState:: |
| kSmartActionsButtonNotShownDueToTextDetectionCancelled, |
| 0); |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run(std::nullopt); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState:: |
| kSmartActionsButtonNotShownDueToTextDetectionCancelled, |
| 1); |
| } |
| |
| // Tests that the copy text button is not shown if the selected region changes |
| // before text detection completes. |
| TEST_F(ScannerTest, NoCopyTextButtonIfSelectedRegionChanges) { |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 50), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| OnTextDetectionComplete callback = detect_text_future.Take(); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 50, 50), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| std::move(callback).Run("detected text"); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| EXPECT_FALSE(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kCopyTextButton)); |
| } |
| |
| // Tests that the "smart actions button not shown due to text detection |
| // cancelled" metric is emitted if the selected region changes before text |
| // detection completes. |
| TEST_F(ScannerTest, SelectedRegionChangesMetrics) { |
| base::HistogramTester histogram_tester; |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState:: |
| kSmartActionsButtonNotShownDueToTextDetectionCancelled, |
| 0); |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 50), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| OnTextDetectionComplete callback = detect_text_future.Take(); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 50, 50), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| std::move(callback).Run("detected text"); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState:: |
| kSmartActionsButtonNotShownDueToTextDetectionCancelled, |
| 1); |
| } |
| |
| // Tests that the copy text button is not shown if the selected region is |
| // fine-tuned, but does not change, before text detection completes. |
| TEST_F(ScannerTest, NoCopyTextButtonIfSelectedRegionChangesByFineTuneNoop) { |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .Times(2) |
| .WillRepeatedly(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| constexpr auto kCaptureRegion = gfx::Rect(0, 0, 50, 50); |
| SelectCaptureModeRegion(GetEventGenerator(), kCaptureRegion, |
| /*release_mouse=*/true, /*verify_region=*/true); |
| OnTextDetectionComplete callback = detect_text_future.Take(); |
| const gfx::Point drag_affordance_location = |
| capture_mode_util::GetLocationForFineTunePosition( |
| kCaptureRegion, FineTunePosition::kBottomRightVertex); |
| GetEventGenerator()->MoveMouseTo(drag_affordance_location); |
| GetEventGenerator()->ClickLeftButton(); |
| ASSERT_TRUE(detect_text_future.Wait()) |
| << "Detect text was not called again after fine-tune"; |
| std::move(callback).Run("detected text"); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| EXPECT_FALSE(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kCopyTextButton)); |
| } |
| |
| // Tests that the "smart actions button not shown due to text detection |
| // cancelled" metric is emitted if the selected region is fine-tuned, but does |
| // not change, before text detection completes. |
| TEST_F(ScannerTest, SelectedRegionChangesByFineTuneNoopMetrics) { |
| base::HistogramTester histogram_tester; |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState:: |
| kSmartActionsButtonNotShownDueToTextDetectionCancelled, |
| 0); |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .Times(2) |
| .WillRepeatedly(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| constexpr auto kCaptureRegion = gfx::Rect(0, 0, 50, 50); |
| SelectCaptureModeRegion(GetEventGenerator(), kCaptureRegion, |
| /*release_mouse=*/true, /*verify_region=*/true); |
| OnTextDetectionComplete callback = detect_text_future.Take(); |
| const gfx::Point drag_affordance_location = |
| capture_mode_util::GetLocationForFineTunePosition( |
| kCaptureRegion, FineTunePosition::kBottomRightVertex); |
| GetEventGenerator()->MoveMouseTo(drag_affordance_location); |
| GetEventGenerator()->ClickLeftButton(); |
| ASSERT_TRUE(detect_text_future.Wait()) |
| << "Detect text was not called again after fine-tune"; |
| std::move(callback).Run("detected text"); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState:: |
| kSmartActionsButtonNotShownDueToTextDetectionCancelled, |
| 1); |
| } |
| |
| // Records the time taken for the on device OCR. |
| TEST_F(ScannerTest, OnSelectCaptureRegionRecordTextDetectionTimer) { |
| base::HistogramTester histogram_tester; |
| |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| |
| ASSERT_TRUE(detect_text_future.Wait()); |
| task_environment()->FastForwardBy(base::Milliseconds(500)); |
| detect_text_future.Take().Run("detected text"); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.Timer.OnDeviceTextDetection", 500, 1); |
| } |
| |
| TEST_F(ScannerTest, |
| SmartActionsButtonShownForDetectedTextWhenConsentNotAccepted) { |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| CheckFeatureAccess) |
| .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{ |
| FeatureAccessFailure::kConsentNotAccepted, |
| })); |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run("detected text"); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| |
| EXPECT_TRUE(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSmartActionsButton)); |
| } |
| |
| TEST_F( |
| ScannerTest, |
| SmartActionsButtonNotShownForDetectedTextButWithAccessCheckFailureWithSunfishEnabled) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature(features::kSunfishFeature); |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| CheckFeatureAccess) |
| .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{ |
| FeatureAccessFailure::kDisabledInSettings, |
| })); |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run("detected text"); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| |
| EXPECT_FALSE(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSmartActionsButton)); |
| } |
| |
| // Tests that the "smart actions button not shown due to CanShowUi returning |
| // false" metric is emitted when Scanner is enabled, but is disabled between |
| // sending the OCR request and receiving a response. |
| TEST_F(ScannerTest, SmartActionsButtonNotShownDueToCanShowUiFalseRecorded) { |
| base::HistogramTester histogram_tester; |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSmartActionsButtonNotShownDueToCanShowUiFalse, |
| 0); |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| OnTextDetectionComplete text_detection_complete = detect_text_future.Take(); |
| // Disable Scanner by enterprise policy. |
| Shell::Get()->session_controller()->GetActivePrefService()->SetInteger( |
| prefs::kScannerEnterprisePolicyAllowed, |
| static_cast<int>(ScannerEnterprisePolicy::kDisallowed)); |
| std::move(text_detection_complete).Run("detected text"); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSmartActionsButtonNotShownDueToCanShowUiFalse, |
| 1); |
| } |
| |
| // Tests that the smart actions button is shown in default capture mode if text |
| // is detected in the selected region. |
| TEST_F(ScannerTest, SmartActionsButtonShownForDetectedText) { |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run("detected text"); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| // Smart actions button should have been created. |
| std::vector<ActionButtonView*> action_buttons = |
| session_test_api.GetActionButtons(); |
| ASSERT_THAT( |
| action_buttons, |
| ElementsAre(ActionButtonIdIs(ActionButtonViewID::kSmartActionsButton), |
| AllOf(ActionButtonIdIs(ActionButtonViewID::kCopyTextButton), |
| Not(ActionButtonIsCollapsed())))); |
| |
| // Click the smart actions button. |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(WithArg<1>(InvokeFuture(fetch_actions_future))); |
| LeftClickOn(action_buttons[0]); |
| WaitForImageCapturedForSearch(PerformCaptureType::kScanner); |
| |
| // Smart actions button should have been removed, and the copy text button |
| // should be collapsed. |
| EXPECT_THAT( |
| session_test_api.GetActionButtons(), |
| ElementsAre(AllOf(ActionButtonIdIs(ActionButtonViewID::kCopyTextButton), |
| ActionButtonIsCollapsed()))); |
| |
| // Simulate a single fetched Scanner action. |
| auto output = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects = *output->add_objects(); |
| objects.add_actions()->mutable_new_event()->set_title("Event"); |
| fetch_actions_future.Take().Run(std::move(output), manta::MantaStatus()); |
| |
| // Check for a Scanner action button and a collapsed copy text button. |
| EXPECT_THAT( |
| session_test_api.GetActionButtons(), |
| ElementsAre(AllOf(ActionButtonIdIs(ActionButtonViewID::kScannerButton), |
| Not(ActionButtonIsCollapsed())), |
| AllOf(ActionButtonIdIs(ActionButtonViewID::kCopyTextButton), |
| ActionButtonIsCollapsed()))); |
| } |
| |
| // Tests that the smart actions button is shown after region selection if |
| // on-device OCR is disabled. In this scenario, the smart actions button is |
| // shown regardless of whether the selected area contains text or not. |
| TEST_F(ScannerTest, SmartActionsButtonShownWhenOnDeviceOcrDisabled) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndDisableFeature(features::kCaptureModeOnDeviceOcr); |
| |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| ASSERT_THAT( |
| session_test_api.GetActionButtons(), |
| ElementsAre(ActionButtonIdIs(ActionButtonViewID::kSmartActionsButton))); |
| |
| // Click the smart actions button. |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(WithArg<1>(InvokeFuture(fetch_actions_future))); |
| LeftClickOn(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSmartActionsButton)); |
| WaitForImageCapturedForSearch(PerformCaptureType::kScanner); |
| |
| // Smart actions button should have been removed. |
| EXPECT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| |
| // Simulate a single fetched Scanner action. |
| auto output = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects = *output->add_objects(); |
| objects.add_actions()->mutable_new_event()->set_title("Event"); |
| fetch_actions_future.Take().Run(std::move(output), manta::MantaStatus()); |
| |
| // Check for a Scanner action button. |
| EXPECT_THAT( |
| session_test_api.GetActionButtons(), |
| ElementsAre(ActionButtonIdIs(ActionButtonViewID::kScannerButton))); |
| } |
| |
| // Tests that the smart actions button is not shown when the network connection |
| // is offline. |
| TEST_F(ScannerTest, SmartActionsButtonNotShownWhenOffline) { |
| // Start default capture mode. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| ON_CALL(*test_delegate, IsNetworkConnectionOffline) |
| .WillByDefault(Return(true)); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run("detected text"); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| // Only the copy text button should be shown, no smart actions button. |
| EXPECT_THAT( |
| session_test_api.GetActionButtons(), |
| ElementsAre(ActionButtonIdIs(ActionButtonViewID::kCopyTextButton))); |
| } |
| |
| // Tests that the "smart actions button not shown due to device being offline" |
| // metric is emitted when the network connection is offline. |
| TEST_F(ScannerTest, SmartActionsButtonNotShownDueToOfflineRecorded) { |
| base::HistogramTester histogram_tester; |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSmartActionsButtonNotShownDueToOffline, 0); |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| ON_CALL(*test_delegate, IsNetworkConnectionOffline) |
| .WillByDefault(Return(true)); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run("detected text"); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kSmartActionsButtonNotShownDueToOffline, 1); |
| } |
| |
| // Tests that pressing the smart actions button shows an error when the network |
| // connection is offline. |
| TEST_F(ScannerTest, PressingSmartActionsButtonShowsErrorIfOffline) { |
| // Start default capture mode. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| // Simulate the network being online initially, so that the search button |
| // will appear when a region is selected. |
| ON_CALL(*test_delegate, IsNetworkConnectionOffline) |
| .WillByDefault(Return(false)); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run("detected text"); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| const ActionButtonView* smart_actions_button = |
| session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSmartActionsButton); |
| ASSERT_TRUE(smart_actions_button); |
| |
| // Simulate the network disconnecting before clicking the smart actions |
| // button. |
| ON_CALL(*test_delegate, IsNetworkConnectionOffline) |
| .WillByDefault(Return(true)); |
| LeftClickOn(smart_actions_button); |
| |
| // An error should be shown. |
| ActionButtonContainerView::ErrorView* error_view = |
| session_test_api.GetActionContainerErrorView(); |
| ASSERT_TRUE(error_view); |
| EXPECT_TRUE(error_view->GetVisible()); |
| } |
| |
| TEST_F(ScannerTest, SmartActionsButtonShownForDetectedTextRecordsHistogram) { |
| base::HistogramTester histogram_tester; |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run("detected text"); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| // Smart actions button should have been created. |
| EXPECT_TRUE(session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSmartActionsButton)); |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kScreenCaptureModeScannerButtonShown, 1); |
| } |
| |
| TEST_F(ScannerTest, SmartActionsButtonShouldRecordMetricWhenActionsFetched) { |
| base::HistogramTester histogram_tester; |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run("detected text"); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| // Smart actions button should have been created. |
| const ActionButtonView* smart_actions_button = |
| session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSmartActionsButton); |
| ASSERT_TRUE(smart_actions_button); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState:: |
| kSmartActionsButtonImageCapturedAndActionsFetchStarted, |
| 0); |
| |
| // Click the smart actions button. |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(WithArg<1>(InvokeFuture(fetch_actions_future))); |
| LeftClickOn(smart_actions_button); |
| WaitForImageCapturedForSearch(PerformCaptureType::kScanner); |
| |
| // Simulate a single fetched Scanner action. |
| auto output = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects = *output->add_objects(); |
| objects.add_actions()->mutable_new_event()->set_title("Event"); |
| fetch_actions_future.Take().Run(std::move(output), manta::MantaStatus()); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState:: |
| kSmartActionsButtonImageCapturedAndActionsFetchStarted, |
| 1); |
| } |
| |
| TEST_F(ScannerTest, SmartActionsButtonShouldRecordMetricWhenActionsNotFetched) { |
| base::HistogramTester histogram_tester; |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run("detected text"); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| // Smart actions button should have been created. |
| const ActionButtonView* smart_actions_button = |
| session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSmartActionsButton); |
| ASSERT_TRUE(smart_actions_button); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState:: |
| kSmartActionsButtonImageCapturedAndActionsNotFetched, |
| 0); |
| |
| // Click the smart actions button when it is now disabled by enterprise. |
| Shell::Get()->session_controller()->GetActivePrefService()->SetInteger( |
| prefs::kScannerEnterprisePolicyAllowed, |
| static_cast<int>(ScannerEnterprisePolicy::kDisallowed)); |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .Times(0); |
| |
| LeftClickOn(smart_actions_button); |
| WaitForImageCapturedForSearch(PerformCaptureType::kScanner); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState:: |
| kSmartActionsButtonImageCapturedAndActionsNotFetched, |
| 1); |
| } |
| |
| // Tests that the copy text and smart actions buttons are correctly shown and |
| // hidden when the user selects or adjusts a capture region with their keyboard. |
| TEST_F(ScannerTest, ActionButtonsUpdatedWhenRegionAdjustedWithKeyboard) { |
| // Start default capture mode. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillRepeatedly(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| // Hit space to select a default region. |
| ui::test::EventGenerator* event_generator = GetEventGenerator(); |
| SendKey(ui::VKEY_SPACE, event_generator); |
| task_environment()->FastForwardBy(kImageSearchRequestStartDelay); |
| detect_text_future.Take().Run("detected text"); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| // Action buttons should be shown since there was detected text. |
| EXPECT_THAT( |
| session_test_api.GetActionButtons(), |
| ElementsAre(ActionButtonIdIs(ActionButtonViewID::kSmartActionsButton), |
| ActionButtonIdIs(ActionButtonViewID::kCopyTextButton))); |
| |
| // Hit tab until the whole region is focused, then shift the region using an |
| // arrow key. |
| SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6); |
| SendKey(ui::VKEY_RIGHT, event_generator); |
| task_environment()->FastForwardBy(kImageSearchRequestStartDelay); |
| detect_text_future.Take().Run(""); |
| |
| // No action buttons should be shown since there was no detected text. |
| EXPECT_THAT(session_test_api.GetActionButtons(), IsEmpty()); |
| |
| // Shift the region again. |
| SendKey(ui::VKEY_RIGHT, event_generator); |
| task_environment()->FastForwardBy(kImageSearchRequestStartDelay); |
| detect_text_future.Take().Run("detected text again"); |
| |
| // Action buttons should be shown again since there was detected text. |
| EXPECT_THAT( |
| session_test_api.GetActionButtons(), |
| ElementsAre(ActionButtonIdIs(ActionButtonViewID::kSmartActionsButton), |
| ActionButtonIdIs(ActionButtonViewID::kCopyTextButton))); |
| } |
| |
| // Tests that the user can use keyboard navigation to use the smart actions |
| // button. |
| TEST_F(ScannerTest, KeyboardNavigationSmartActionsButton) { |
| // Start default capture mode. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| // Set up text detection expectations. |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillRepeatedly(WithArg<1>(InvokeFuture(detect_text_future))); |
| // Set up Scanner action fetching expectations. |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_action_details_future; |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| FakeScannerProfileScopedDelegate& fake_profile_scoped_delegate = |
| *GetFakeScannerProfileScopedDelegate(*scanner_controller); |
| EXPECT_CALL(fake_profile_scoped_delegate, FetchActionsForImage) |
| .WillOnce(WithArg<1>(InvokeFuture(fetch_actions_future))); |
| EXPECT_CALL(fake_profile_scoped_delegate, FetchActionDetailsForImage) |
| .WillOnce(WithArg<2>(InvokeFuture(fetch_action_details_future))); |
| |
| // Hit space to select a default region. |
| ui::test::EventGenerator* event_generator = GetEventGenerator(); |
| SendKey(ui::VKEY_SPACE, event_generator); |
| detect_text_future.Take().Run("detected text"); |
| |
| // Check that smart actions button has appeared. |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| const ActionButtonView* smart_actions_button = |
| session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSmartActionsButton); |
| ASSERT_TRUE(smart_actions_button); |
| ASSERT_EQ(session_test_api.GetActionButtons()[0], smart_actions_button); |
| |
| // Use tab and enter to navigate to and select the smart actions button. |
| SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN, /*count=*/4); |
| SendKey(ui::VKEY_RETURN, event_generator); |
| |
| // Simulate a single fetched Scanner action. |
| auto output = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects = *output->add_objects(); |
| objects.add_actions()->mutable_new_event()->set_title("Event"); |
| fetch_actions_future.Take().Run(std::move(output), manta::MantaStatus()); |
| |
| // Check for a Scanner action button and a copy text button. |
| EXPECT_THAT( |
| session_test_api.GetActionButtons(), |
| ElementsAre(ActionButtonIdIs(ActionButtonViewID::kScannerButton), |
| ActionButtonIdIs(ActionButtonViewID::kCopyTextButton))); |
| |
| // Press enter to select the Scanner action button. |
| SendKey(ui::VKEY_RETURN, event_generator); |
| |
| EXPECT_TRUE(fetch_action_details_future.Wait()); |
| } |
| |
| // Tests that Scanner actions are updated when the user selects or adjusts a |
| // capture region with their keyboard in Sunfish mode. |
| TEST_F(ScannerTest, |
| ActionButtonsUpdatedWhenRegionAdjustedWithKeyboardInSunfishMode) { |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillRepeatedly(WithArg<1>(InvokeFuture(fetch_actions_future))); |
| |
| // Hit space to select a default region. |
| ui::test::EventGenerator* event_generator = GetEventGenerator(); |
| SendKey(ui::VKEY_SPACE, event_generator); |
| task_environment()->FastForwardBy(kImageSearchRequestStartDelay); |
| // Simulate two fetched actions. |
| auto output1 = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects1 = *output1->add_objects(); |
| objects1.add_actions()->mutable_new_event()->set_title("Event 1"); |
| objects1.add_actions()->mutable_new_event()->set_title("Event 2"); |
| fetch_actions_future.Take().Run(std::move(output1), manta::MantaStatus()); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| EXPECT_THAT(session_test_api.GetActionButtons(), SizeIs(2)); |
| |
| // Hit tab to focus the whole region, then shift the region using an arrow |
| // key. |
| SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE); |
| SendKey(ui::VKEY_RIGHT, event_generator); |
| task_environment()->FastForwardBy(kImageSearchRequestStartDelay); |
| // Simulate one fetched action. |
| auto output2 = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects2 = *output2->add_objects(); |
| objects2.add_actions()->mutable_new_event()->set_title("Event 3"); |
| fetch_actions_future.Take().Run(std::move(output2), manta::MantaStatus()); |
| |
| EXPECT_THAT(session_test_api.GetActionButtons(), SizeIs(1)); |
| } |
| |
| // Tests that there is a delay when requesting actions after the user adjusts a |
| // capture region with their keyboard. This is to prevent too many requests if |
| // the user repeatedly adjusts the capture region with arrow keys. |
| TEST_F(ScannerTest, |
| ActionButtonsUpdatedWithDelayAfterRegionAdjustedWithKeyboard) { |
| // Start default capture mode. |
| auto* controller = |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| // Expect OCR to be triggered exactly once, after the user has finished |
| // adjusting the capture region. |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .Times(1) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| // Hit space to select a default region, tab until the whole region is |
| // focused, then shift the region using arrow keys. |
| ui::test::EventGenerator* event_generator = GetEventGenerator(); |
| SendKey(ui::VKEY_SPACE, event_generator); |
| SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6); |
| SendKey(ui::VKEY_RIGHT, event_generator, /*count=*/3); |
| task_environment()->FastForwardBy(kImageSearchRequestStartDelay); |
| detect_text_future.Take().Run("detected text"); |
| |
| const CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| // Action buttons should be shown since there was detected text. |
| EXPECT_THAT( |
| session_test_api.GetActionButtons(), |
| ElementsAre(ActionButtonIdIs(ActionButtonViewID::kSmartActionsButton), |
| ActionButtonIdIs(ActionButtonViewID::kCopyTextButton))); |
| } |
| |
| // Tests that the capture label is hidden while capturing an image to send to |
| // the Scanner backend. |
| TEST_F(ScannerTest, CaptureLabelHiddenWhilePerformingCaptureForScanner) { |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| // Select a region to trigger text detection. The region is large enough that |
| // the capture label should appear inside the capture region. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 250, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| // The capture label should be hidden so that it does not interfere with text |
| // detection. |
| EXPECT_FALSE(session_test_api.GetCaptureLabelWidget()->IsVisible()); |
| |
| // The capture label should be reshown once capture completes. |
| WaitForImageCapturedForSearch(PerformCaptureType::kTextDetection); |
| EXPECT_TRUE(session_test_api.GetCaptureLabelWidget()->IsVisible()); |
| |
| detect_text_future.Take().Run("detected text"); |
| // Smart actions button should have been created. |
| const ActionButtonView* smart_actions_button = |
| session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSmartActionsButton); |
| ASSERT_TRUE(smart_actions_button); |
| |
| // Click the smart actions button. The capture label should be hidden so that |
| // it does not interfere with detecting Scanner actions. |
| LeftClickOn(smart_actions_button); |
| EXPECT_FALSE(session_test_api.GetCaptureLabelWidget()->IsVisible()); |
| |
| // The capture label should be reshown once capture completes. |
| WaitForImageCapturedForSearch(PerformCaptureType::kScanner); |
| EXPECT_TRUE(session_test_api.GetCaptureLabelWidget()->IsVisible()); |
| } |
| |
| // If the action container does not overlap the capture region, it should remain |
| // visible while performing capture for Scanner. |
| TEST_F(ScannerTest, |
| ActionContainerWidgetVisibleWhilePerformingCaptureIfNoOverlap) { |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| // Select a small region and simulate detected text so that the smart actions |
| // button appears. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kTextDetection); |
| detect_text_future.Take().Run("detected text"); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| const ActionButtonView* smart_actions_button = |
| session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSmartActionsButton); |
| ASSERT_TRUE(smart_actions_button); |
| |
| // Click the smart actions button to trigger performing capture for Scanner. |
| LeftClickOn(smart_actions_button); |
| |
| // Since the action container widget doesn't overlap the small capture region, |
| // it should remain visible and animations should remain enabled. |
| const views::Widget* action_container_widget = |
| session_test_api.GetActionContainerWidget(); |
| EXPECT_FALSE(action_container_widget->GetWindowBoundsInScreen().Intersects( |
| controller->user_capture_region())); |
| EXPECT_TRUE(action_container_widget->IsVisible()); |
| EXPECT_FALSE(action_container_widget->GetNativeWindow()->GetProperty( |
| aura::client::kAnimationsDisabledKey)); |
| } |
| |
| // If the action container overlaps the capture region, it should be hidden |
| // while performing capture for Scanner. |
| TEST_F(ScannerTest, |
| ActionContainerWidgetVisibleWhilePerformingCaptureIfOverlap) { |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| // Select a large region and simulate detected text so that the smart actions |
| // button appears. The region covers the whole root window, so it will |
| // intersect the action container. |
| SelectCaptureModeRegion(GetEventGenerator(), |
| Shell::GetPrimaryRootWindow()->bounds(), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kTextDetection); |
| detect_text_future.Take().Run("detected text"); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| const ActionButtonView* smart_actions_button = |
| session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSmartActionsButton); |
| ASSERT_TRUE(smart_actions_button); |
| |
| // Click the smart actions button to trigger performing capture for Scanner. |
| LeftClickOn(smart_actions_button); |
| |
| // The action container widget should be hidden with animations disabled while |
| // performing capture. |
| const views::Widget* action_container_widget = |
| session_test_api.GetActionContainerWidget(); |
| EXPECT_FALSE(action_container_widget->IsVisible()); |
| EXPECT_TRUE(action_container_widget->GetNativeWindow()->GetProperty( |
| aura::client::kAnimationsDisabledKey)); |
| |
| // The action container widget should be reshown and animations re-enabled |
| // after capture completes. |
| WaitForImageCapturedForSearch(PerformCaptureType::kScanner); |
| |
| EXPECT_TRUE(action_container_widget->IsVisible()); |
| EXPECT_FALSE(action_container_widget->GetNativeWindow()->GetProperty( |
| aura::client::kAnimationsDisabledKey)); |
| } |
| |
| // Tests that a glow animation is shown when Scanner actions are being fetched |
| // during a Sunfish session. |
| TEST_F(ScannerTest, GlowAnimationWhenFetchingActionsDuringSunfishSession) { |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| capture_mode_controller->StartSunfishSession(); |
| CaptureModeSessionTestApi session_test_api( |
| capture_mode_controller->capture_mode_session()); |
| CaptureRegionOverlayController* region_overlay_controller = |
| session_test_api.GetCaptureRegionOverlayController(); |
| ASSERT_TRUE(region_overlay_controller); |
| EXPECT_FALSE(region_overlay_controller->HasGlowAnimation()); |
| |
| // Select a region to start fetching Scanner actions. |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(WithArg<1>(InvokeFuture(fetch_actions_future))); |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 100, 600, 500), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| WaitForImageCapturedForSearch(PerformCaptureType::kSunfish); |
| |
| // Glow should be animating. |
| EXPECT_TRUE(region_overlay_controller->HasGlowAnimation()); |
| const gfx::ThrobAnimation* glow_animation = |
| region_overlay_controller->glow_animation_for_testing(); |
| ASSERT_TRUE(glow_animation); |
| EXPECT_TRUE(glow_animation->is_animating()); |
| |
| // Finish fetching Scanner actions. |
| fetch_actions_future.Take().Run( |
| std::make_unique<manta::proto::ScannerOutput>(), manta::MantaStatus()); |
| |
| // Glow should be pausing. |
| EXPECT_TRUE(region_overlay_controller->HasGlowAnimation()); |
| EXPECT_EQ(glow_animation->cycles_remaining(), 0); |
| } |
| |
| // Tests that a glow animation is shown when Scanner actions are being fetched |
| // after the smart actions button is pressed. |
| TEST_F(ScannerTest, GlowAnimationAfterPressingSmartActionsButton) { |
| ActionButtonView* smart_actions_button = GetSmartActionsButton(); |
| ASSERT_TRUE(smart_actions_button); |
| CaptureModeSessionTestApi session_test_api( |
| CaptureModeController::Get()->capture_mode_session()); |
| CaptureRegionOverlayController* region_overlay_controller = |
| session_test_api.GetCaptureRegionOverlayController(); |
| ASSERT_TRUE(region_overlay_controller); |
| EXPECT_FALSE(region_overlay_controller->HasGlowAnimation()); |
| |
| // Click on the smart actions button to start fetching Scanner actions. |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(WithArg<1>(InvokeFuture(fetch_actions_future))); |
| LeftClickOn(smart_actions_button); |
| WaitForImageCapturedForSearch(PerformCaptureType::kScanner); |
| |
| // Glow should be animating. |
| EXPECT_TRUE(region_overlay_controller->HasGlowAnimation()); |
| const gfx::ThrobAnimation* glow_animation = |
| region_overlay_controller->glow_animation_for_testing(); |
| ASSERT_TRUE(glow_animation); |
| EXPECT_TRUE(glow_animation->is_animating()); |
| |
| // Finish fetching Scanner actions. |
| fetch_actions_future.Take().Run( |
| std::make_unique<manta::proto::ScannerOutput>(), manta::MantaStatus()); |
| |
| // Glow should be pausing. |
| EXPECT_TRUE(region_overlay_controller->HasGlowAnimation()); |
| EXPECT_EQ(glow_animation->cycles_remaining(), 0); |
| |
| // Reselect a capture region. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(100, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| |
| // Glow should be removed. |
| EXPECT_FALSE(region_overlay_controller->HasGlowAnimation()); |
| } |
| |
| // Tests that a glow animation is removed if the capture source changes. |
| TEST_F(ScannerTest, GlowAnimationRemovedOnCaptureSourceChange) { |
| ActionButtonView* smart_actions_button = GetSmartActionsButton(); |
| ASSERT_TRUE(smart_actions_button); |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| CaptureModeSessionTestApi session_test_api( |
| capture_mode_controller->capture_mode_session()); |
| CaptureRegionOverlayController* region_overlay_controller = |
| session_test_api.GetCaptureRegionOverlayController(); |
| ASSERT_TRUE(region_overlay_controller); |
| EXPECT_FALSE(region_overlay_controller->HasGlowAnimation()); |
| |
| // Click on the smart actions button. Glow should be shown. |
| LeftClickOn(smart_actions_button); |
| WaitForImageCapturedForSearch(PerformCaptureType::kScanner); |
| |
| EXPECT_TRUE(region_overlay_controller->HasGlowAnimation()); |
| |
| // Switch to video. Glow should be removed. |
| capture_mode_controller->SetType(CaptureModeType::kVideo); |
| |
| EXPECT_FALSE(region_overlay_controller->HasGlowAnimation()); |
| } |
| |
| TEST_F(ScannerTest, DisclaimerAcceptContinuesScreenshotSession) { |
| base::HistogramTester histogram_tester; |
| UnackAllScannerDisclaimers(); |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| // Ensure that `CheckFeatureAccess` succeeds iff the consent disclaimer has |
| // been accepted. |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| CheckFeatureAccess) |
| .WillRepeatedly([]() { |
| // Explicitly use the pref here as that is the source of truth for |
| // feature access checking. |
| return Shell::Get() |
| ->session_controller() |
| ->GetActivePrefService() |
| ->GetBoolean(prefs::kScannerConsentDisclaimerAccepted) |
| ? specialized_features::FeatureAccessFailureSet{} |
| : specialized_features::FeatureAccessFailureSet{ |
| specialized_features::FeatureAccessFailure:: |
| kConsentNotAccepted}; |
| }); |
| |
| auto* controller = CaptureModeController::Get(); |
| StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kImage); |
| base::test::TestFuture<OnTextDetectionComplete> detect_text_future; |
| auto* test_delegate = |
| static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing()); |
| EXPECT_CALL(*test_delegate, DetectTextInImage) |
| .WillOnce(WithArg<1>(InvokeFuture(detect_text_future))); |
| |
| base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback> |
| fetch_actions_future; |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| FetchActionsForImage) |
| .WillOnce(WithArg<1>(InvokeFuture(fetch_actions_future))); |
| |
| // Select a region and simulate detected text so that the smart actions button |
| // appears. |
| SelectCaptureModeRegion(GetEventGenerator(), gfx::Rect(0, 0, 50, 200), |
| /*release_mouse=*/true, /*verify_region=*/true); |
| detect_text_future.Take().Run("detected text"); |
| // Smart actions button should have been created. |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| const ActionButtonView* smart_actions_button = |
| session_test_api.GetActionButtonByViewId( |
| ActionButtonViewID::kSmartActionsButton); |
| ASSERT_TRUE(smart_actions_button); |
| |
| // Click the smart actions button. |
| LeftClickOn(smart_actions_button); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| |
| views::View* accept_button = |
| disclaimer->GetContentsView()->GetViewByID(kDisclaimerViewAcceptButtonId); |
| LeftClickOn(accept_button); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kConsentDisclaimerAccepted, 1); |
| EXPECT_EQ(session_test_api.GetDisclaimerWidget(), nullptr); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kNone); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kReminder); |
| EXPECT_TRUE(controller->IsActive()); |
| WaitForImageCapturedForSearch(PerformCaptureType::kScanner); |
| |
| // Smart actions button should have been removed, and the copy text button |
| // should be collapsed. |
| EXPECT_THAT( |
| session_test_api.GetActionButtons(), |
| ElementsAre(AllOf(ActionButtonIdIs(ActionButtonViewID::kCopyTextButton), |
| ActionButtonIsCollapsed()))); |
| |
| // Simulate a single fetched Scanner action. |
| auto output = std::make_unique<manta::proto::ScannerOutput>(); |
| manta::proto::ScannerObject& objects = *output->add_objects(); |
| objects.add_actions()->mutable_new_event()->set_title("Event"); |
| fetch_actions_future.Take().Run(std::move(output), manta::MantaStatus()); |
| |
| // Check for a Scanner action button and a collapsed copy text button. |
| EXPECT_THAT( |
| session_test_api.GetActionButtons(), |
| ElementsAre(AllOf(ActionButtonIdIs(ActionButtonViewID::kScannerButton), |
| Not(ActionButtonIsCollapsed())), |
| AllOf(ActionButtonIdIs(ActionButtonViewID::kCopyTextButton), |
| ActionButtonIsCollapsed()))); |
| } |
| |
| TEST_F(ScannerTest, DisclaimerAcceptInSunfishSessionStartsScannerSession) { |
| base::HistogramTester histogram_tester; |
| UnackAllScannerDisclaimers(); |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| // Ensure that `CheckFeatureAccess` succeeds iff the consent disclaimer has |
| // been accepted. |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| CheckFeatureAccess) |
| .WillRepeatedly([]() { |
| // Explicitly use the pref here as that is the source of truth for |
| // feature access checking. |
| return Shell::Get() |
| ->session_controller() |
| ->GetActivePrefService() |
| ->GetBoolean(prefs::kScannerConsentDisclaimerAccepted) |
| ? specialized_features::FeatureAccessFailureSet{} |
| : specialized_features::FeatureAccessFailureSet{ |
| specialized_features::FeatureAccessFailure:: |
| kConsentNotAccepted}; |
| }); |
| |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| views::View* accept_button = |
| disclaimer->GetContentsView()->GetViewByID(kDisclaimerViewAcceptButtonId); |
| LeftClickOn(accept_button); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kConsentDisclaimerAccepted, 1); |
| EXPECT_TRUE(scanner_controller->HasActiveSessionForTesting()); |
| } |
| |
| TEST_F(ScannerTest, DisclaimerAcceptedInSunfishSessionStartsScannerSession) { |
| AckScannerDisclaimer(ScannerEntryPoint::kSunfishSession); |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| |
| EXPECT_FALSE(session_test_api.GetDisclaimerWidget()); |
| EXPECT_TRUE(scanner_controller->HasActiveSessionForTesting()); |
| } |
| |
| TEST_F(ScannerTest, DisclaimerDeclineGoesBackToScreenshotMode) { |
| base::HistogramTester histogram_tester; |
| UnackAllScannerDisclaimers(); |
| ScannerController* scanner_controller = Shell::Get()->scanner_controller(); |
| ASSERT_TRUE(scanner_controller); |
| // `CheckFeatureAccess` should reflect the enabled and disclaimer pref. |
| EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller), |
| CheckFeatureAccess) |
| .WillRepeatedly([]() { |
| specialized_features::FeatureAccessFailureSet failures; |
| PrefService* prefs = |
| Shell::Get()->session_controller()->GetActivePrefService(); |
| if (!prefs->GetBoolean(prefs::kScannerEnabled)) { |
| failures.Put( |
| specialized_features::FeatureAccessFailure::kDisabledInSettings); |
| } |
| // Explicitly use the pref here as that is the source of truth for |
| // feature access checking. |
| if (!prefs->GetBoolean(prefs::kScannerConsentDisclaimerAccepted)) { |
| failures.Put( |
| specialized_features::FeatureAccessFailure::kConsentNotAccepted); |
| } |
| |
| return failures; |
| }); |
| |
| ActionButtonView* smart_actions_button = GetSmartActionsButton(); |
| auto* controller = CaptureModeController::Get(); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| ASSERT_TRUE(smart_actions_button); |
| EXPECT_THAT(session_test_api.GetActionButtons(), |
| ElementsAre(smart_actions_button, _)); |
| |
| // Click the smart actions button. |
| LeftClickOn(smart_actions_button); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| |
| views::View* decline_button = disclaimer->GetContentsView()->GetViewByID( |
| kDisclaimerViewDeclineButtonId); |
| LeftClickOn(decline_button); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kConsentDisclaimerRejected, 1); |
| EXPECT_EQ(session_test_api.GetDisclaimerWidget(), nullptr); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kFull); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kFull); |
| EXPECT_FALSE( |
| Shell::Get()->session_controller()->GetActivePrefService()->GetBoolean( |
| prefs::kScannerEnabled)); |
| EXPECT_FALSE(ScannerController::CanShowUiForShell()); |
| EXPECT_TRUE(controller->IsActive()); |
| // The smart actions button is now removed. |
| EXPECT_THAT(session_test_api.GetActionButtons(), SizeIs(1)); |
| } |
| |
| // Tests that the full disclaimer is shown when the smart actions button is |
| // clicked for the first time. |
| TEST_F(ScannerTest, FullDisclaimerFromSmartActionsButton) { |
| PrefService& prefs = |
| *Shell::Get()->session_controller()->GetActivePrefService(); |
| SetAllScannerDisclaimersUnackedForTest(prefs); |
| ASSERT_EQ( |
| GetScannerDisclaimerType(prefs, ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kFull); |
| |
| ActionButtonView* smart_actions_button = GetSmartActionsButton(); |
| LeftClickOn(smart_actions_button); |
| |
| views::Widget* disclaimer = CaptureModeSessionTestApi().GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| EXPECT_FALSE(DisclaimerIsReminder(*disclaimer->GetContentsView())); |
| } |
| |
| // Tests that the full disclaimer is shown on first Sunfish-session start. |
| TEST_F(ScannerTest, FullDisclaimerFromSunfishSession) { |
| PrefService& prefs = |
| *Shell::Get()->session_controller()->GetActivePrefService(); |
| SetAllScannerDisclaimersUnackedForTest(prefs); |
| ASSERT_EQ(GetScannerDisclaimerType(prefs, ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kFull); |
| |
| CaptureModeController::Get()->StartSunfishSession(); |
| |
| views::Widget* disclaimer = CaptureModeSessionTestApi().GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| EXPECT_FALSE(DisclaimerIsReminder(*disclaimer->GetContentsView())); |
| } |
| |
| // Tests that the reminder disclaimer is shown for the smart actions button when |
| // the smart actions button is clicked after the Sunfish-session disclaimer is |
| // acknowledged. |
| TEST_F(ScannerTest, ReminderDisclaimerFromSmartActionsButton) { |
| PrefService& prefs = |
| *Shell::Get()->session_controller()->GetActivePrefService(); |
| SetAllScannerDisclaimersUnackedForTest(prefs); |
| // Acknowledge the Sunfish-session disclaimer. |
| SetScannerDisclaimerAcked(prefs, ScannerEntryPoint::kSunfishSession); |
| ASSERT_EQ( |
| GetScannerDisclaimerType(prefs, ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kReminder); |
| |
| ActionButtonView* smart_actions_button = GetSmartActionsButton(); |
| LeftClickOn(smart_actions_button); |
| |
| views::Widget* disclaimer = CaptureModeSessionTestApi().GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| EXPECT_TRUE(DisclaimerIsReminder(*disclaimer->GetContentsView())); |
| } |
| |
| // Tests that the reminder disclaimer is shown at Sunfish-session start if the |
| // smart actions button disclaimer is acknowledged. |
| TEST_F(ScannerTest, ReminderDisclaimerFromSunfishSession) { |
| PrefService& prefs = |
| *Shell::Get()->session_controller()->GetActivePrefService(); |
| SetAllScannerDisclaimersUnackedForTest(prefs); |
| // Acknowledge the smart actions button disclaimer. |
| SetScannerDisclaimerAcked(prefs, ScannerEntryPoint::kSmartActionsButton); |
| ASSERT_EQ(GetScannerDisclaimerType(prefs, ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kReminder); |
| |
| CaptureModeController::Get()->StartSunfishSession(); |
| |
| views::Widget* disclaimer = CaptureModeSessionTestApi().GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| EXPECT_TRUE(DisclaimerIsReminder(*disclaimer->GetContentsView())); |
| } |
| |
| // Tests that the consent disclaimer can be properly navigated from the smart |
| // actions button using the keyboard. |
| TEST_F(ScannerTest, KeyboardNavigationDisclaimerFromSmartActionsButton) { |
| base::HistogramTester histogram_tester; |
| UnackAllScannerDisclaimers(); |
| |
| ActionButtonView* smart_actions_button = GetSmartActionsButton(); |
| ASSERT_TRUE(smart_actions_button); |
| auto* controller = CaptureModeController::Get(); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| ASSERT_EQ(session_test_api.GetActionButtons().size(), 2u); |
| |
| // Use tab to navigate to the smart actions button. |
| auto* event_generator = GetEventGenerator(); |
| SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN, /*count=*/4); |
| ASSERT_EQ(CaptureModeSessionFocusCycler::FocusGroup::kActionButtons, |
| session_test_api.GetCurrentFocusGroup()); |
| ASSERT_EQ(0u, session_test_api.GetCurrentFocusIndex()); |
| ASSERT_EQ(session_test_api.GetActionButtons()[0], smart_actions_button); |
| |
| // Press enter to open the consent disclaimer. |
| SendKey(ui::VKEY_RETURN, event_generator); |
| auto* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| |
| // The accept button should already be focused. |
| views::View* accept_button = |
| disclaimer->GetContentsView()->GetViewByID(kDisclaimerViewAcceptButtonId); |
| EXPECT_TRUE(accept_button->HasFocus()); |
| |
| // Press tab once. The focus should stay inside the disclaimer, and loop back |
| // around to the link in paragraph one. |
| SendKey(ui::VKEY_TAB, event_generator); |
| auto* paragraph_one = views::AsViewClass<views::StyledLabel>( |
| disclaimer->GetContentsView()->GetViewByID( |
| kDisclaimerViewParagraphOneId)); |
| ASSERT_TRUE(paragraph_one); |
| views::Link* paragraph_one_first_link = |
| paragraph_one->GetFirstLinkForTesting(); |
| ASSERT_TRUE(paragraph_one_first_link); |
| EXPECT_TRUE(paragraph_one_first_link->HasFocus()); |
| |
| // Unfortunately, if a link spans across more than one line, each line has its |
| // own focus target: https://crbug.com/391154477 |
| // Assume that each link spans at most two lines, so press the tab button |
| // up to two times to focus the link in paragraph two. |
| SendKey(ui::VKEY_TAB, event_generator); |
| auto* paragraph_three = views::AsViewClass<views::StyledLabel>( |
| disclaimer->GetContentsView()->GetViewByID( |
| kDisclaimerViewParagraphThreeId)); |
| ASSERT_TRUE(paragraph_three); |
| views::Link* paragraph_three_first_link = |
| paragraph_three->GetFirstLinkForTesting(); |
| ASSERT_TRUE(paragraph_three_first_link); |
| if (!paragraph_three_first_link->HasFocus()) { |
| SendKey(ui::VKEY_TAB, event_generator); |
| } |
| EXPECT_TRUE(paragraph_three_first_link->HasFocus()); |
| |
| // Press tab up to two times to focus the decline button. |
| SendKey(ui::VKEY_TAB, event_generator); |
| views::View* decline_button = disclaimer->GetContentsView()->GetViewByID( |
| kDisclaimerViewDeclineButtonId); |
| if (!decline_button->HasFocus()) { |
| SendKey(ui::VKEY_TAB, event_generator); |
| } |
| EXPECT_TRUE(decline_button->HasFocus()); |
| |
| // Press tab again. The accept button should now be focused. |
| SendKey(ui::VKEY_TAB, event_generator); |
| EXPECT_TRUE(accept_button->HasFocus()); |
| |
| // Press tab one more time. The focus should loop back around to the link in |
| // paragraph one again. |
| SendKey(ui::VKEY_TAB, event_generator); |
| EXPECT_TRUE(paragraph_one_first_link->HasFocus()); |
| } |
| |
| TEST_F(ScannerTest, DisclaimerTosLinkFromScreenshotMode) { |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL(chrome::kGooglePrivacyPolicyUrl), _, _)); |
| |
| UnackAllScannerDisclaimers(); |
| ActionButtonView* smart_actions_button = GetSmartActionsButton(); |
| ASSERT_TRUE(smart_actions_button); |
| auto* controller = CaptureModeController::Get(); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| LeftClickOn(smart_actions_button); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| auto* paragraph_one = views::AsViewClass<views::StyledLabel>( |
| disclaimer->GetContentsView()->GetViewByID( |
| kDisclaimerViewParagraphOneId)); |
| ASSERT_TRUE(paragraph_one); |
| |
| paragraph_one->ClickFirstLinkForTesting(); |
| |
| EXPECT_FALSE(controller->IsActive()); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kFull); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kFull); |
| EXPECT_TRUE( |
| Shell::Get()->session_controller()->GetActivePrefService()->GetBoolean( |
| prefs::kScannerEnabled)); |
| } |
| |
| TEST_F(ScannerTest, DisclaimerLearnMoreLinkFromScreenshotMode) { |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL(chrome::kScannerLearnMoreUrl), _, _)); |
| |
| UnackAllScannerDisclaimers(); |
| ActionButtonView* smart_actions_button = GetSmartActionsButton(); |
| ASSERT_TRUE(smart_actions_button); |
| auto* controller = CaptureModeController::Get(); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| LeftClickOn(smart_actions_button); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| auto* paragraph_three = views::AsViewClass<views::StyledLabel>( |
| disclaimer->GetContentsView()->GetViewByID( |
| kDisclaimerViewParagraphThreeId)); |
| ASSERT_TRUE(paragraph_three); |
| |
| paragraph_three->ClickFirstLinkForTesting(); |
| |
| EXPECT_FALSE(controller->IsActive()); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kFull); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kFull); |
| EXPECT_TRUE( |
| Shell::Get()->session_controller()->GetActivePrefService()->GetBoolean( |
| prefs::kScannerEnabled)); |
| } |
| |
| TEST_F(ScannerTest, ReminderDisclaimerTosLinkFromScreenshotMode) { |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL(chrome::kGooglePrivacyPolicyUrl), _, _)); |
| |
| PrefService& prefs = |
| *Shell::Get()->session_controller()->GetActivePrefService(); |
| SetAllScannerDisclaimersUnackedForTest(prefs); |
| // Acknowledge the Sunfish-session disclaimer. |
| SetScannerDisclaimerAcked(prefs, ScannerEntryPoint::kSunfishSession); |
| ASSERT_EQ( |
| GetScannerDisclaimerType(prefs, ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kReminder); |
| |
| ActionButtonView* smart_actions_button = GetSmartActionsButton(); |
| LeftClickOn(smart_actions_button); |
| views::Widget* disclaimer = CaptureModeSessionTestApi().GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| ASSERT_TRUE(DisclaimerIsReminder(*disclaimer->GetContentsView())); |
| auto* paragraph_one = views::AsViewClass<views::StyledLabel>( |
| disclaimer->GetContentsView()->GetViewByID( |
| kDisclaimerViewParagraphOneId)); |
| ASSERT_TRUE(paragraph_one); |
| |
| paragraph_one->ClickFirstLinkForTesting(); |
| |
| EXPECT_FALSE(capture_mode_util::IsCaptureModeActive()); |
| // Clicking the smart actions button should still show a reminder next time. |
| EXPECT_EQ( |
| GetScannerDisclaimerType(prefs, ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kReminder); |
| } |
| |
| TEST_F(ScannerTest, ReminderDisclaimerLearnMoreLinkFromScreenshotMode) { |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL(chrome::kScannerLearnMoreUrl), _, _)); |
| |
| PrefService& prefs = |
| *Shell::Get()->session_controller()->GetActivePrefService(); |
| SetAllScannerDisclaimersUnackedForTest(prefs); |
| // Acknowledge the Sunfish-session disclaimer. |
| SetScannerDisclaimerAcked(prefs, ScannerEntryPoint::kSunfishSession); |
| ASSERT_EQ( |
| GetScannerDisclaimerType(prefs, ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kReminder); |
| |
| ActionButtonView* smart_actions_button = GetSmartActionsButton(); |
| LeftClickOn(smart_actions_button); |
| views::Widget* disclaimer = CaptureModeSessionTestApi().GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| ASSERT_TRUE(DisclaimerIsReminder(*disclaimer->GetContentsView())); |
| auto* paragraph_three = views::AsViewClass<views::StyledLabel>( |
| disclaimer->GetContentsView()->GetViewByID( |
| kDisclaimerViewParagraphThreeId)); |
| ASSERT_TRUE(paragraph_three); |
| |
| paragraph_three->ClickFirstLinkForTesting(); |
| |
| EXPECT_FALSE(capture_mode_util::IsCaptureModeActive()); |
| // Clicking the smart actions button should still show a reminder next time. |
| EXPECT_EQ( |
| GetScannerDisclaimerType(prefs, ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kReminder); |
| } |
| |
| TEST_F(ScannerTest, DisclaimerTosLinkFromSunfishMode) { |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL(chrome::kGooglePrivacyPolicyUrl), _, _)); |
| |
| UnackAllScannerDisclaimers(); |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| auto* paragraph_one = views::AsViewClass<views::StyledLabel>( |
| disclaimer->GetContentsView()->GetViewByID( |
| kDisclaimerViewParagraphOneId)); |
| ASSERT_TRUE(paragraph_one); |
| |
| paragraph_one->ClickFirstLinkForTesting(); |
| |
| EXPECT_FALSE(controller->IsActive()); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kFull); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kFull); |
| EXPECT_TRUE( |
| Shell::Get()->session_controller()->GetActivePrefService()->GetBoolean( |
| prefs::kScannerEnabled)); |
| } |
| |
| TEST_F(ScannerTest, DisclaimerLearnMoreLinkFromSunfishMode) { |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL(chrome::kScannerLearnMoreUrl), _, _)); |
| |
| UnackAllScannerDisclaimers(); |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| auto* paragraph_three = views::AsViewClass<views::StyledLabel>( |
| disclaimer->GetContentsView()->GetViewByID( |
| kDisclaimerViewParagraphThreeId)); |
| ASSERT_TRUE(paragraph_three); |
| |
| paragraph_three->ClickFirstLinkForTesting(); |
| |
| EXPECT_FALSE(controller->IsActive()); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kFull); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kFull); |
| EXPECT_TRUE( |
| Shell::Get()->session_controller()->GetActivePrefService()->GetBoolean( |
| prefs::kScannerEnabled)); |
| } |
| |
| TEST_F(ScannerTest, ReminderDisclaimerTosLinkFromSunfishMode) { |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL(chrome::kGooglePrivacyPolicyUrl), _, _)); |
| |
| PrefService& prefs = |
| *Shell::Get()->session_controller()->GetActivePrefService(); |
| SetAllScannerDisclaimersUnackedForTest(prefs); |
| // Acknowledge the smart actions button disclaimer. |
| SetScannerDisclaimerAcked(prefs, ScannerEntryPoint::kSmartActionsButton); |
| ASSERT_EQ(GetScannerDisclaimerType(prefs, ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kReminder); |
| |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| ASSERT_TRUE(DisclaimerIsReminder(*disclaimer->GetContentsView())); |
| auto* paragraph_one = views::AsViewClass<views::StyledLabel>( |
| disclaimer->GetContentsView()->GetViewByID( |
| kDisclaimerViewParagraphOneId)); |
| ASSERT_TRUE(paragraph_one); |
| |
| paragraph_one->ClickFirstLinkForTesting(); |
| |
| EXPECT_FALSE(capture_mode_util::IsCaptureModeActive()); |
| // Starting a Sunfish-session should still show a reminder next time. |
| EXPECT_EQ(GetScannerDisclaimerType(prefs, ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kReminder); |
| } |
| |
| TEST_F(ScannerTest, ReminderDisclaimerLearnMoreLinkFromSunfishMode) { |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL(chrome::kScannerLearnMoreUrl), _, _)); |
| |
| PrefService& prefs = |
| *Shell::Get()->session_controller()->GetActivePrefService(); |
| SetAllScannerDisclaimersUnackedForTest(prefs); |
| // Acknowledge the smart actions button disclaimer. |
| SetScannerDisclaimerAcked(prefs, ScannerEntryPoint::kSmartActionsButton); |
| ASSERT_EQ(GetScannerDisclaimerType(prefs, ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kReminder); |
| |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| ASSERT_TRUE(DisclaimerIsReminder(*disclaimer->GetContentsView())); |
| auto* paragraph_three = views::AsViewClass<views::StyledLabel>( |
| disclaimer->GetContentsView()->GetViewByID( |
| kDisclaimerViewParagraphThreeId)); |
| ASSERT_TRUE(paragraph_three); |
| |
| paragraph_three->ClickFirstLinkForTesting(); |
| |
| EXPECT_FALSE(capture_mode_util::IsCaptureModeActive()); |
| // Starting a Sunfish-session should still show a reminder next time. |
| EXPECT_EQ(GetScannerDisclaimerType(prefs, ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kReminder); |
| } |
| |
| TEST_F(ScannerTest, DisclaimerAcceptRecordsHistogramOnce) { |
| base::HistogramTester histogram_tester; |
| UnackAllScannerDisclaimers(); |
| |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| |
| views::View* accept_button = |
| disclaimer->GetContentsView()->GetViewByID(kDisclaimerViewAcceptButtonId); |
| LeftClickOn(accept_button); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kConsentDisclaimerAccepted, 1); |
| } |
| |
| TEST_F(ScannerTest, DisclaimerDeclineRecordsHistogramOnce) { |
| base::HistogramTester histogram_tester; |
| UnackAllScannerDisclaimers(); |
| |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| |
| views::View* decline_button = disclaimer->GetContentsView()->GetViewByID( |
| kDisclaimerViewDeclineButtonId); |
| LeftClickOn(decline_button); |
| |
| histogram_tester.ExpectBucketCount( |
| "Ash.ScannerFeature.UserState", |
| ScannerFeatureUserState::kConsentDisclaimerRejected, 1); |
| } |
| |
| TEST_F(ScannerTest, |
| DisclaimerAcceptHidesDisclaimerSetPrefsAndContinuesSession) { |
| UnackAllScannerDisclaimers(); |
| |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| |
| views::View* accept_button = |
| disclaimer->GetContentsView()->GetViewByID(kDisclaimerViewAcceptButtonId); |
| LeftClickOn(accept_button); |
| |
| EXPECT_EQ(session_test_api.GetDisclaimerWidget(), nullptr); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kReminder); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kNone); |
| EXPECT_TRUE(controller->IsActive()); |
| } |
| |
| TEST_F(ScannerTest, |
| DisclaimerDeclineInSunfishSessionHidesDisclaimerSetPrefsAndEndsSession) { |
| UnackAllScannerDisclaimers(); |
| |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| |
| views::View* decline_button = disclaimer->GetContentsView()->GetViewByID( |
| kDisclaimerViewDeclineButtonId); |
| LeftClickOn(decline_button); |
| |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kFull); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kFull); |
| EXPECT_FALSE( |
| Shell::Get()->session_controller()->GetActivePrefService()->GetBoolean( |
| prefs::kScannerEnabled)); |
| EXPECT_FALSE(controller->capture_mode_session()); |
| EXPECT_FALSE(controller->IsActive()); |
| } |
| |
| TEST_F( |
| ScannerTest, |
| DisclaimerDeclineHidesDisclaimerSetPrefsAndDoesNotEndIfSunfishFlagEnabled) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature(features::kSunfishFeature); |
| UnackAllScannerDisclaimers(); |
| |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| ASSERT_TRUE(controller->IsActive()); |
| |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| |
| views::View* decline_button = disclaimer->GetContentsView()->GetViewByID( |
| kDisclaimerViewDeclineButtonId); |
| LeftClickOn(decline_button); |
| |
| EXPECT_EQ(session_test_api.GetDisclaimerWidget(), nullptr); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kFull); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kFull); |
| EXPECT_FALSE( |
| Shell::Get()->session_controller()->GetActivePrefService()->GetBoolean( |
| prefs::kScannerEnabled)); |
| EXPECT_TRUE(controller->IsActive()); |
| } |
| |
| TEST_F(ScannerTest, KeyboardNavigationDisclaimerAcceptedFromSunfishMode) { |
| UnackAllScannerDisclaimers(); |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| |
| // Press enter to accept the disclaimer. |
| SendKey(ui::VKEY_RETURN, GetEventGenerator()); |
| |
| EXPECT_EQ(session_test_api.GetDisclaimerWidget(), nullptr); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kReminder); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kNone); |
| } |
| |
| TEST_F(ScannerTest, KeyboardNavigationDisclaimerDeclinedFromSunfishMode) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature(features::kSunfishFeature); |
| UnackAllScannerDisclaimers(); |
| auto* controller = CaptureModeController::Get(); |
| controller->StartSunfishSession(); |
| |
| CaptureModeSessionTestApi session_test_api( |
| controller->capture_mode_session()); |
| views::Widget* disclaimer = session_test_api.GetDisclaimerWidget(); |
| ASSERT_TRUE(disclaimer); |
| |
| // Press shift tab then enter to select the decline button in the disclaimer. |
| ui::test::EventGenerator* event_generator = GetEventGenerator(); |
| SendKey(ui::VKEY_TAB, event_generator, ui::EF_SHIFT_DOWN); |
| SendKey(ui::VKEY_RETURN, event_generator); |
| |
| EXPECT_EQ(session_test_api.GetDisclaimerWidget(), nullptr); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSmartActionsButton), |
| ScannerDisclaimerType::kFull); |
| EXPECT_EQ(GetScannerDisclaimerType(ScannerEntryPoint::kSunfishSession), |
| ScannerDisclaimerType::kFull); |
| } |
| |
| } // namespace |
| |
| } // namespace ash |