| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/system/unified/feature_tile.h" |
| |
| #include "ash/constants/quick_settings_catalogs.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/system/tray/tray_constants.h" |
| #include "ash/system/unified/feature_pod_button.h" |
| #include "ash/system/unified/feature_pod_controller_base.h" |
| #include "ash/system/unified/feature_tile.h" |
| #include "ash/system/video_conference/fake_video_conference_tray_controller.h" |
| #include "ash/test/ash_test_base.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "components/vector_icons/vector_icons.h" |
| #include "ui/accessibility/ax_enums.mojom-shared.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/chromeos/styles/cros_tokens_color_mappings.h" |
| #include "ui/gfx/geometry/rrect_f.h" |
| #include "ui/views/accessibility/view_accessibility.h" |
| #include "ui/views/animation/ink_drop.h" |
| #include "ui/views/animation/ink_drop_state.h" |
| #include "ui/views/controls/highlight_path_generator.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/test/views_test_utils.h" |
| #include "ui/views/view_class_properties.h" |
| |
| namespace ash { |
| |
| constexpr int kDefaultButtonRadius = 16; |
| |
| namespace { |
| |
| class MockFeaturePodController : public FeaturePodControllerBase { |
| public: |
| MockFeaturePodController() = default; |
| explicit MockFeaturePodController(bool togglable) : togglable_(togglable) {} |
| |
| MockFeaturePodController(const MockFeaturePodController&) = delete; |
| MockFeaturePodController& operator=(const MockFeaturePodController&) = delete; |
| |
| ~MockFeaturePodController() override = default; |
| |
| std::unique_ptr<FeatureTile> CreateTile(bool compact = false) override { |
| auto tile = std::make_unique<FeatureTile>( |
| base::BindRepeating(&FeaturePodControllerBase::OnLabelPressed, |
| weak_ptr_factory_.GetWeakPtr()), |
| togglable_, |
| compact ? FeatureTile::TileType::kCompact |
| : FeatureTile::TileType::kPrimary); |
| tile->SetVectorIcon(vector_icons::kDogfoodIcon); |
| tile->SetIconClickCallback( |
| base::BindRepeating(&MockFeaturePodController::OnIconPressed, |
| weak_ptr_factory_.GetWeakPtr())); |
| tile_ = tile.get(); |
| return tile; |
| } |
| |
| QsFeatureCatalogName GetCatalogName() override { |
| return QsFeatureCatalogName::kUnknown; |
| } |
| |
| void OnIconPressed() override { |
| was_icon_pressed_ = true; |
| // FeaturePodController elements in production know if they are togglable, |
| // but in this mock we need to check before changing the toggled state. This |
| // attempts to match UX specs: tiles with clickable icons should toggle when |
| // their icon is clicked rather than their label. |
| if (togglable_ && tile_->is_icon_clickable()) { |
| toggled_ = !toggled_; |
| tile_->SetToggled(toggled_); |
| } |
| } |
| |
| void OnLabelPressed() override { |
| was_label_pressed_ = true; |
| // FeaturePodController elements in production know if they are togglable, |
| // but in this mock we need to check before changing the toggled state. This |
| // attempts to match UX specs: tiles with clickable icons should toggle when |
| // their icon is clicked rather than their label. |
| if (togglable_ && !tile_->is_icon_clickable()) { |
| toggled_ = !toggled_; |
| tile_->SetToggled(toggled_); |
| } |
| } |
| |
| bool WasIconPressed() { return was_icon_pressed_; } |
| bool WasLabelPressed() { return was_label_pressed_; } |
| |
| private: |
| raw_ptr<FeatureTile> tile_ = nullptr; |
| bool was_icon_pressed_ = false; |
| bool was_label_pressed_ = false; |
| bool togglable_ = false; |
| bool toggled_ = false; |
| |
| base::WeakPtrFactory<MockFeaturePodController> weak_ptr_factory_{this}; |
| }; |
| |
| } // namespace |
| |
| class FeatureTileTest |
| : public AshTestBase, |
| public testing::WithParamInterface</*IsVcDlcUiEnabled*/ bool> { |
| public: |
| FeatureTileTest() = default; |
| FeatureTileTest(const FeatureTileTest&) = delete; |
| FeatureTileTest& operator=(const FeatureTileTest&) = delete; |
| ~FeatureTileTest() override = default; |
| |
| // AshTestBase: |
| void SetUp() override { |
| if (IsVcDlcUiEnabled()) { |
| scoped_feature_list_ |
| .InitWithFeatures(/*enabled_features=*/ |
| {features::kFeatureManagementVideoConference, |
| features::kVcDlcUi}, |
| /*disabled_features=*/{}); |
| // Need to create a fake VC tray controller if VcDlcUi is enabled because |
| // this implies `features::IsVideoConferenceEnabled()` is true, and when |
| // that is true the VC tray is created (and the VC tray depends on the |
| // VC tray controller being available). |
| tray_controller_ = std::make_unique<FakeVideoConferenceTrayController>(); |
| } |
| AshTestBase::SetUp(); |
| widget_ = CreateFramelessTestWidget(); |
| widget_->SetFullscreen(true); |
| } |
| |
| void TearDown() override { |
| widget_.reset(); |
| AshTestBase::TearDown(); |
| if (IsVcDlcUiEnabled()) { |
| tray_controller_.reset(); |
| } |
| } |
| |
| void PressTab() { |
| ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow()); |
| generator.PressKey(ui::KeyboardCode::VKEY_TAB, ui::EF_NONE); |
| } |
| |
| bool IsVcDlcUiEnabled() { return GetParam(); } |
| |
| std::u16string GetExpectedDownloadPendingLabel() { |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_FEATURE_TILE_DOWNLOAD_PENDING_TITLE); |
| } |
| |
| std::u16string GetExpectedDownloadInProgressLabel(int progress) { |
| return l10n_util::GetStringFUTF16( |
| IDS_ASH_FEATURE_TILE_DOWNLOAD_IN_PROGRESS_TITLE, |
| base::NumberToString16(progress)); |
| } |
| |
| std::unique_ptr<views::Widget> widget_; |
| std::unique_ptr<FakeVideoConferenceTrayController> tray_controller_ = nullptr; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(IsVcDlcUiEnabled, FeatureTileTest, testing::Bool()); |
| |
| TEST_P(FeatureTileTest, PrimaryTile_LaunchSurface) { |
| auto mock_controller = |
| std::make_unique<MockFeaturePodController>(/*togglable=*/false); |
| auto* tile = widget_->SetContentsView(mock_controller->CreateTile()); |
| |
| EXPECT_FALSE(tile->is_icon_clickable()); |
| EXPECT_FALSE(tile->drill_in_arrow()); |
| |
| // Ensure label hasn't been pressed. |
| EXPECT_FALSE(tile->IsToggled()); |
| EXPECT_FALSE(mock_controller->WasLabelPressed()); |
| |
| LeftClickOn(tile); |
| |
| // Ensure label was pressed and button does not toggle after clicking it. |
| EXPECT_TRUE(mock_controller->WasLabelPressed()); |
| EXPECT_FALSE(tile->IsToggled()); |
| } |
| |
| TEST_P(FeatureTileTest, PrimaryTile_Toggle) { |
| auto mock_controller = |
| std::make_unique<MockFeaturePodController>(/*togglable=*/true); |
| auto* tile = widget_->SetContentsView(mock_controller->CreateTile()); |
| |
| EXPECT_FALSE(tile->is_icon_clickable()); |
| EXPECT_FALSE(tile->drill_in_arrow()); |
| |
| // Ensure label hasn't been pressed. |
| EXPECT_FALSE(tile->IsToggled()); |
| EXPECT_FALSE(mock_controller->WasLabelPressed()); |
| |
| LeftClickOn(tile); |
| |
| // Ensure label was pressed and button toggles after clicking it. |
| EXPECT_TRUE(mock_controller->WasLabelPressed()); |
| EXPECT_TRUE(tile->IsToggled()); |
| |
| LeftClickOn(tile); |
| |
| // Ensure button toggles after clicking it again. |
| EXPECT_FALSE(tile->IsToggled()); |
| } |
| |
| TEST_P(FeatureTileTest, PrimaryTile_ToggleWithDrillIn) { |
| auto mock_controller = |
| std::make_unique<MockFeaturePodController>(/*togglable=*/true); |
| auto* tile = widget_->SetContentsView(mock_controller->CreateTile()); |
| |
| tile->SetIconClickable(true); |
| views::test::RunScheduledLayout(tile); |
| EXPECT_TRUE(tile->is_icon_clickable()); |
| ASSERT_TRUE(tile->icon_button()); |
| EXPECT_TRUE(tile->icon_button()->GetEnabled()); |
| |
| // Ensure tile is not toggled and icon is not pressed. |
| EXPECT_FALSE(tile->IsToggled()); |
| EXPECT_FALSE(mock_controller->WasIconPressed()); |
| |
| LeftClickOn(tile); |
| |
| // Clicking the tile does not press the icon. |
| EXPECT_FALSE(mock_controller->WasIconPressed()); |
| EXPECT_TRUE(mock_controller->WasLabelPressed()); |
| EXPECT_FALSE(tile->IsToggled()); |
| |
| LeftClickOn(tile->icon_button()); |
| |
| // Clicking the icon presses it and toggles the tile. |
| EXPECT_TRUE(mock_controller->WasIconPressed()); |
| EXPECT_TRUE(tile->IsToggled()); |
| } |
| |
| TEST_P(FeatureTileTest, PrimaryTile_SetIconClickable) { |
| auto mock_controller = |
| std::make_unique<MockFeaturePodController>(/*togglable=*/true); |
| auto* tile = widget_->SetContentsView(mock_controller->CreateTile()); |
| |
| // Ensure clickable state is correct. |
| tile->SetIconClickable(true); |
| EXPECT_TRUE(tile->is_icon_clickable()); |
| EXPECT_TRUE(tile->icon_button()->GetEnabled()); |
| auto* ink_drop = views::InkDrop::Get(tile->icon_button()); |
| ASSERT_TRUE(ink_drop); |
| EXPECT_EQ(ink_drop->GetMode(), views::InkDropHost::InkDropMode::ON); |
| |
| // Ensure icon button can take focus. |
| auto* focus_manager = widget_->GetFocusManager(); |
| PressTab(); |
| EXPECT_EQ(tile, focus_manager->GetFocusedView()); |
| PressTab(); |
| EXPECT_EQ(tile->icon_button(), focus_manager->GetFocusedView()); |
| |
| // Ensure button state changes when set to not clickable. |
| tile->SetIconClickable(false); |
| EXPECT_FALSE(tile->is_icon_clickable()); |
| EXPECT_FALSE(tile->icon_button()->GetEnabled()); |
| EXPECT_EQ(ink_drop->GetMode(), views::InkDropHost::InkDropMode::OFF); |
| |
| // Ensure icon button doesn't focus. |
| PressTab(); |
| EXPECT_EQ(tile, focus_manager->GetFocusedView()); |
| PressTab(); |
| EXPECT_EQ(tile, focus_manager->GetFocusedView()); |
| } |
| |
| TEST_P(FeatureTileTest, PrimaryTile_DecorativeDrillIn) { |
| auto mock_controller = |
| std::make_unique<MockFeaturePodController>(/*togglable=*/false); |
| auto* tile = widget_->SetContentsView(mock_controller->CreateTile()); |
| |
| tile->CreateDecorativeDrillInArrow(); |
| views::test::RunScheduledLayout(tile); |
| EXPECT_FALSE(tile->is_icon_clickable()); |
| ASSERT_TRUE(tile->drill_in_arrow()); |
| EXPECT_TRUE(tile->drill_in_arrow()->GetVisible()); |
| |
| // Ensure label hasn't been pressed. |
| EXPECT_FALSE(tile->IsToggled()); |
| EXPECT_FALSE(mock_controller->WasLabelPressed()); |
| |
| LeftClickOn(tile->drill_in_arrow()); |
| |
| // Ensure label was pressed and button does not toggle after clicking it. |
| EXPECT_TRUE(mock_controller->WasLabelPressed()); |
| EXPECT_FALSE(tile->IsToggled()); |
| |
| // Ensure drill-in button doesn't focus. |
| auto* focus_manager = widget_->GetFocusManager(); |
| PressTab(); |
| EXPECT_EQ(tile, focus_manager->GetFocusedView()); |
| PressTab(); |
| EXPECT_EQ(tile, focus_manager->GetFocusedView()); |
| } |
| |
| // Togglable tiles with a decorative drill-in button do not toggle when |
| // clicked, but show a detailed view from where the user can trigger an action |
| // which toggles the button state (e.g. selecting a VPN network). |
| // Since this test uses mock feature tiles and cannot easily create detailed |
| // views, this toggle behavior will be omitted. |
| TEST_P(FeatureTileTest, PrimaryTile_ToggleWithDecorativeDrillIn) { |
| auto mock_controller = |
| std::make_unique<MockFeaturePodController>(/*togglable=*/true); |
| auto* tile = widget_->SetContentsView(mock_controller->CreateTile()); |
| |
| tile->CreateDecorativeDrillInArrow(); |
| views::test::RunScheduledLayout(tile); |
| EXPECT_FALSE(tile->is_icon_clickable()); |
| ASSERT_TRUE(tile->drill_in_arrow()); |
| EXPECT_TRUE(tile->drill_in_arrow()->GetVisible()); |
| |
| // Ensure label is not pressed. |
| EXPECT_FALSE(mock_controller->WasLabelPressed()); |
| |
| LeftClickOn(tile); |
| |
| // Ensure label was pressed after clicking it. |
| EXPECT_FALSE(mock_controller->WasIconPressed()); |
| EXPECT_TRUE(mock_controller->WasLabelPressed()); |
| |
| LeftClickOn(tile->drill_in_arrow()); |
| |
| // Ensure `WasIconPressed` not pressed after clicking drill-in button. |
| EXPECT_FALSE(mock_controller->WasIconPressed()); |
| |
| // Ensure drill-in button doesn't focus. |
| auto* focus_manager = widget_->GetFocusManager(); |
| PressTab(); |
| EXPECT_EQ(tile, focus_manager->GetFocusedView()); |
| PressTab(); |
| EXPECT_EQ(tile, focus_manager->GetFocusedView()); |
| } |
| |
| TEST_P(FeatureTileTest, PrimaryTile_WithSubLabel) { |
| FeatureTile primary_tile_with_sub_label(views::Button::PressedCallback(), |
| /*is_togglable=*/true, |
| FeatureTile::TileType::kPrimary); |
| primary_tile_with_sub_label.SetLabel(u"Button label"); |
| primary_tile_with_sub_label.SetSubLabel(u"Sub label"); |
| |
| EXPECT_EQ(primary_tile_with_sub_label.label()->GetHorizontalAlignment(), |
| gfx::ALIGN_LEFT); |
| EXPECT_EQ(primary_tile_with_sub_label.label()->GetMultiLine(), false); |
| EXPECT_EQ((int)primary_tile_with_sub_label.label()->GetMaxLines(), 0); |
| } |
| |
| TEST_P(FeatureTileTest, PrimaryTile_UpdatedCornerRadius) { |
| int updated_radius = 10; |
| auto mock_controller = |
| std::make_unique<MockFeaturePodController>(/*togglable=*/false); |
| auto* tile = widget_->SetContentsView(mock_controller->CreateTile()); |
| |
| // Verify the initial tile state utilizes the default radius. |
| views::RoundRectHighlightPathGenerator* path_generator = |
| static_cast<views::RoundRectHighlightPathGenerator*>( |
| tile->GetProperty(views::kHighlightPathGeneratorKey)); |
| gfx::RectF bounds = gfx::RectF(tile->GetLocalBounds()); |
| EXPECT_EQ(path_generator->GetRoundRect(bounds), |
| gfx::RRectF(bounds, kDefaultButtonRadius)); |
| |
| tile->SetButtonCornerRadius(updated_radius); |
| |
| // Verify the tile utilizes the updated radius. |
| path_generator = static_cast<views::RoundRectHighlightPathGenerator*>( |
| tile->GetProperty(views::kHighlightPathGeneratorKey)); |
| bounds = gfx::RectF(tile->GetLocalBounds()); |
| EXPECT_EQ(path_generator->GetRoundRect(bounds), |
| gfx::RRectF(bounds, updated_radius)); |
| } |
| |
| TEST_P(FeatureTileTest, CompactTile_AddedAndRemoveSubLabel) { |
| // Create initial compact `FeatureTile` without a sub-label and verify default |
| // parameters. |
| FeatureTile compact_tile_with_sub_label(views::Button::PressedCallback(), |
| /*is_togglable=*/true, |
| FeatureTile::TileType::kCompact); |
| compact_tile_with_sub_label.SetLabel(u"Button label"); |
| |
| EXPECT_FALSE(compact_tile_with_sub_label.sub_label()->GetVisible()); |
| EXPECT_EQ(compact_tile_with_sub_label.label()->GetHorizontalAlignment(), |
| gfx::ALIGN_CENTER); |
| EXPECT_EQ(compact_tile_with_sub_label.label()->GetMultiLine(), true); |
| EXPECT_EQ((int)compact_tile_with_sub_label.label()->GetMaxLines(), 2); |
| |
| // Add a sub-label, update visibility, and verify parameters are updated. |
| compact_tile_with_sub_label.SetSubLabel(u"Sub label"); |
| compact_tile_with_sub_label.SetSubLabelVisibility(true); |
| |
| EXPECT_EQ(compact_tile_with_sub_label.label()->GetText(), u"Button label"); |
| EXPECT_EQ(compact_tile_with_sub_label.label()->GetHorizontalAlignment(), |
| gfx::ALIGN_CENTER); |
| EXPECT_EQ(compact_tile_with_sub_label.label()->GetMultiLine(), false); |
| EXPECT_EQ((int)compact_tile_with_sub_label.label()->GetMaxLines(), 1); |
| EXPECT_TRUE(compact_tile_with_sub_label.sub_label()->GetVisible()); |
| |
| // Hide sub-label and verify parameters are back to defaults. |
| compact_tile_with_sub_label.SetSubLabelVisibility(false); |
| |
| EXPECT_FALSE(compact_tile_with_sub_label.sub_label()->GetVisible()); |
| EXPECT_EQ(compact_tile_with_sub_label.label()->GetHorizontalAlignment(), |
| gfx::ALIGN_CENTER); |
| EXPECT_EQ(compact_tile_with_sub_label.label()->GetMultiLine(), true); |
| EXPECT_EQ((int)compact_tile_with_sub_label.label()->GetMaxLines(), 2); |
| } |
| |
| TEST_P(FeatureTileTest, CompactTile_LaunchSurface) { |
| auto mock_controller = std::make_unique<MockFeaturePodController>( |
| /*togglable=*/false); |
| auto* tile = |
| widget_->SetContentsView(mock_controller->CreateTile(/*compact=*/true)); |
| EXPECT_FALSE(tile->is_icon_clickable()); |
| |
| // Ensure label hasn't been pressed. |
| EXPECT_FALSE(tile->IsToggled()); |
| EXPECT_FALSE(mock_controller->WasLabelPressed()); |
| |
| LeftClickOn(tile); |
| |
| // Ensure label was pressed and button does not toggle after clicking it. |
| EXPECT_TRUE(mock_controller->WasLabelPressed()); |
| EXPECT_FALSE(tile->IsToggled()); |
| } |
| |
| TEST_P(FeatureTileTest, CompactTile_Toggle) { |
| auto mock_controller = std::make_unique<MockFeaturePodController>( |
| /*togglable=*/true); |
| auto* tile = |
| widget_->SetContentsView(mock_controller->CreateTile(/*compact=*/true)); |
| EXPECT_FALSE(tile->is_icon_clickable()); |
| |
| // Ensure label hasn't been pressed. |
| EXPECT_FALSE(tile->IsToggled()); |
| EXPECT_FALSE(mock_controller->WasLabelPressed()); |
| |
| LeftClickOn(tile); |
| |
| // Ensure label was pressed and button toggles after clicking it. |
| EXPECT_TRUE(mock_controller->WasLabelPressed()); |
| EXPECT_TRUE(tile->IsToggled()); |
| |
| // Ensure button toggles after clicking it again. |
| LeftClickOn(tile); |
| EXPECT_FALSE(tile->IsToggled()); |
| } |
| |
| TEST_P(FeatureTileTest, TogglingTileUpdatesInkDropColor) { |
| auto* tile = widget_->SetContentsView( |
| std::make_unique<FeatureTile>(views::Button::PressedCallback())); |
| auto* color_provider = tile->GetColorProvider(); |
| |
| tile->SetToggled(true); |
| EXPECT_EQ(views::InkDrop::Get(tile)->GetBaseColor(), |
| color_provider->GetColor(cros_tokens::kCrosSysRipplePrimary)); |
| |
| tile->SetToggled(false); |
| EXPECT_EQ( |
| views::InkDrop::Get(tile)->GetBaseColor(), |
| color_provider->GetColor(cros_tokens::kCrosSysRippleNeutralOnSubtle)); |
| } |
| |
| // Regression test for http://b/284318391 |
| TEST_P(FeatureTileTest, TogglingTileHidesInkDrop) { |
| auto mock_controller = std::make_unique<MockFeaturePodController>( |
| /*togglable=*/true); |
| auto* tile = widget_->SetContentsView(mock_controller->CreateTile()); |
| |
| LeftClickOn(tile); |
| ASSERT_TRUE(tile->IsToggled()); |
| EXPECT_EQ(views::InkDrop::Get(tile)->GetInkDrop()->GetTargetInkDropState(), |
| views::InkDropState::HIDDEN); |
| } |
| |
| TEST_P(FeatureTileTest, AccessibilityRoles) { |
| // Togglable feature tiles (like Do Not Disturb) have role "toggle button". |
| FeatureTile togglable_tile(views::Button::PressedCallback(), |
| /*is_togglable=*/true); |
| togglable_tile.SetToggled(true); |
| ui::AXNodeData node_data; |
| togglable_tile.GetViewAccessibility().GetAccessibleNodeData(&node_data); |
| EXPECT_EQ(node_data.role, ax::mojom::Role::kToggleButton); |
| EXPECT_EQ(node_data.GetCheckedState(), ax::mojom::CheckedState::kTrue); |
| |
| togglable_tile.SetToggled(false); |
| ui::AXNodeData node_data2; |
| togglable_tile.GetViewAccessibility().GetAccessibleNodeData(&node_data2); |
| EXPECT_EQ(node_data2.role, ax::mojom::Role::kToggleButton); |
| EXPECT_EQ(node_data2.GetCheckedState(), ax::mojom::CheckedState::kFalse); |
| |
| // However, togglable feature tiles that have a clickable icon (like Network |
| // and Bluetooth) do not have role "toggle button", since clicking the main |
| // tile takes the user to a detail page. |
| togglable_tile.SetIconClickable(true); |
| ui::AXNodeData node_data3; |
| togglable_tile.GetViewAccessibility().GetAccessibleNodeData(&node_data3); |
| EXPECT_EQ(node_data3.role, ax::mojom::Role::kButton); |
| |
| // Non-togglable feature tiles are just buttons. |
| FeatureTile non_togglable_tile(views::Button::PressedCallback(), |
| /*is_togglable=*/false); |
| ui::AXNodeData node_data4; |
| non_togglable_tile.GetViewAccessibility().GetAccessibleNodeData(&node_data4); |
| EXPECT_EQ(node_data4.role, ax::mojom::Role::kButton); |
| } |
| |
| // Tests that the tile's label and tooltip are set according to the feature tile |
| // DLC's download state. |
| TEST_P(FeatureTileTest, DownloadLabelAndTooltip) { |
| // Download states are only supported when `VcDlcUi` is enabled. |
| if (!IsVcDlcUiEnabled()) { |
| return; |
| } |
| |
| // Create a tile and verify that it has its client-specified label by default. |
| std::u16string client_specified_label(u"Client Specified Label"); |
| std::u16string client_specified_tooltip(u"Client Specified Tooltip"); |
| FeatureTile tile(views::Button::PressedCallback(), |
| /*is_togglable=*/true); |
| tile.SetLabel(client_specified_label); |
| tile.SetTooltipText(client_specified_tooltip); |
| EXPECT_EQ(client_specified_label, tile.label()->GetText()); |
| EXPECT_EQ(client_specified_tooltip, tile.GetTooltipText()); |
| |
| // Set the tile to have a pending download and verify that the label and |
| // tooltip are updated. |
| tile.SetDownloadState(FeatureTile::DownloadState::kPending, /*progress=*/0); |
| |
| EXPECT_EQ(GetExpectedDownloadPendingLabel(), tile.label()->GetText()); |
| EXPECT_EQ(GetExpectedDownloadPendingLabel(), tile.GetTooltipText()); |
| |
| // Set the tile to have an in-progress download and verify that the label is |
| // updated accordingly. |
| tile.SetDownloadState(FeatureTile::DownloadState::kDownloading, |
| /*progress=*/7); |
| |
| EXPECT_EQ(GetExpectedDownloadInProgressLabel(/*progress=*/7), |
| tile.label()->GetText()); |
| EXPECT_EQ(GetExpectedDownloadInProgressLabel(/*progress=*/7), |
| tile.GetTooltipText()); |
| |
| // Set the tile to have a successfully-completed download and verify that the |
| // label is set to the client-specified label. |
| tile.SetDownloadState(FeatureTile::DownloadState::kDownloaded, |
| /*progress=*/0); |
| |
| EXPECT_EQ(client_specified_label, tile.label()->GetText()); |
| EXPECT_EQ(client_specified_tooltip, tile.GetTooltipText()); |
| |
| // Set the tile to have an error with its download and verify that the label |
| // is set to the client-specified label with additional info. |
| tile.SetDownloadState(FeatureTile::DownloadState::kError, /*progress=*/0); |
| EXPECT_EQ(client_specified_label, tile.label()->GetText()); |
| EXPECT_EQ(l10n_util::GetStringFUTF16(IDS_ASH_FEATURE_TILE_DOWNLOAD_ERROR, |
| client_specified_label), |
| tile.GetTooltipText()); |
| |
| // Set the tile to have no associated download and verify that the label is |
| // set to the client-specified label. |
| tile.SetDownloadState(FeatureTile::DownloadState::kNone, /*progress=*/0); |
| EXPECT_EQ(client_specified_label, tile.label()->GetText()); |
| EXPECT_EQ(client_specified_tooltip, tile.GetTooltipText()); |
| } |
| |
| // Tests that updates to the tile's client-specified label and tooltip are |
| // delayed until the current download is finished. |
| TEST_P(FeatureTileTest, LabelAndTooltipUpdatesDelayedDuringDownload) { |
| // Download states are only supported when `VcDlcUi` is enabled. |
| if (!IsVcDlcUiEnabled()) { |
| return; |
| } |
| |
| // Create a tile and set it to have an in-progress download. |
| std::u16string label_1(u"Client Specified Label"); |
| std::u16string tooltip_1(u"Client Specified Tooltip"); |
| FeatureTile tile(views::Button::PressedCallback(), |
| /*is_togglable=*/true); |
| tile.SetLabel(label_1); |
| tile.SetTooltipText(tooltip_1); |
| tile.SetDownloadState(FeatureTile::DownloadState::kDownloading, |
| /*progress=*/7); |
| ASSERT_EQ(GetExpectedDownloadInProgressLabel(/*progress=*/7), |
| tile.label()->GetText()); |
| ASSERT_EQ(GetExpectedDownloadInProgressLabel(/*progress=*/7), |
| tile.GetTooltipText()); |
| |
| // Change the tile's client-specified label and tooltip, and verify that the |
| // change is not yet reflected due to the on-going download. |
| std::u16string label_2(u"New Label"); |
| std::u16string tooltip_2(u"New Tooltip"); |
| tile.SetLabel(label_2); |
| tile.SetTooltipText(tooltip_2); |
| EXPECT_EQ(GetExpectedDownloadInProgressLabel(/*progress=*/7), |
| tile.label()->GetText()); |
| EXPECT_EQ(GetExpectedDownloadInProgressLabel(/*progress=*/7), |
| tile.GetTooltipText()); |
| |
| // Set the tile's download to be finished and verify that the tile now has the |
| // new client-specified label. |
| tile.SetDownloadState(FeatureTile::DownloadState::kDownloaded, |
| /*progress=*/0); |
| EXPECT_EQ(label_2, tile.label()->GetText()); |
| EXPECT_EQ(tooltip_2, tile.GetTooltipText()); |
| |
| // Set the tile to have a pending download. |
| tile.SetDownloadState(FeatureTile::DownloadState::kPending, /*progress=*/0); |
| ASSERT_EQ(GetExpectedDownloadPendingLabel(), tile.label()->GetText()); |
| |
| // Change the tile's client-specified label and verify that the change is not |
| // yet reflected due to the pending download. |
| std::u16string label_3(u"Another new label"); |
| std::u16string tooltip_3(u"Another new label"); |
| tile.SetLabel(label_3); |
| tile.SetTooltipText(tooltip_3); |
| EXPECT_EQ(GetExpectedDownloadPendingLabel(), tile.label()->GetText()); |
| EXPECT_EQ(GetExpectedDownloadPendingLabel(), tile.GetTooltipText()); |
| |
| // Set the tile's download to be finished (with an error this time, for |
| // for variety) and verify that the tile now has the new client-specified |
| // label, but the tooltip shows the error message. |
| tile.SetDownloadState(FeatureTile::DownloadState::kError, /*progress=*/0); |
| EXPECT_EQ(label_3, tile.label()->GetText()); |
| EXPECT_EQ( |
| l10n_util::GetStringFUTF16(IDS_ASH_FEATURE_TILE_DOWNLOAD_ERROR, label_3), |
| tile.GetTooltipText()); |
| } |
| |
| } // namespace ash |