| // Copyright 2020 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/shelf/drag_handle.h" |
| |
| #include "ash/accessibility/accessibility_controller.h" |
| #include "ash/shelf/shelf_layout_manager.h" |
| #include "ash/shelf/shelf_navigation_widget.h" |
| #include "ash/shelf/shelf_widget.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/system/status_area_widget.h" |
| #include "ash/test/ash_test_base.h" |
| #include "ash/test/test_widget_delegates.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/views/accessibility/view_accessibility.h" |
| #include "ui/views/test/test_widget_builder.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| enum class TestAccessibilityFeature { |
| kTabletModeShelfNavigationButtons, |
| kSpokenFeedback, |
| kAutoclick, |
| kSwitchAccess |
| }; |
| |
| // Tests drag handle functionalities with number of accessibility setting |
| // enabled. |
| class DragHandleTest |
| : public AshTestBase, |
| public ::testing::WithParamInterface<TestAccessibilityFeature> { |
| public: |
| DragHandleTest() = default; |
| ~DragHandleTest() override = default; |
| |
| const DragHandle* drag_handle() const { |
| return GetPrimaryShelf()->shelf_widget()->GetDragHandle(); |
| } |
| |
| void ClickDragHandle() { |
| gfx::Point center = drag_handle()->GetBoundsInScreen().CenterPoint(); |
| GetEventGenerator()->MoveMouseTo(center); |
| GetEventGenerator()->ClickLeftButton(); |
| } |
| |
| void SetTestA11yFeatureEnabled(bool enabled) { |
| switch (GetParam()) { |
| case TestAccessibilityFeature::kTabletModeShelfNavigationButtons: |
| Shell::Get() |
| ->accessibility_controller() |
| ->SetTabletModeShelfNavigationButtonsEnabled(enabled); |
| break; |
| case TestAccessibilityFeature::kSpokenFeedback: |
| Shell::Get()->accessibility_controller()->SetSpokenFeedbackEnabled( |
| enabled, A11Y_NOTIFICATION_NONE); |
| break; |
| case TestAccessibilityFeature::kAutoclick: |
| Shell::Get()->accessibility_controller()->autoclick().SetEnabled( |
| enabled); |
| break; |
| case TestAccessibilityFeature::kSwitchAccess: |
| Shell::Get()->accessibility_controller()->switch_access().SetEnabled( |
| enabled); |
| Shell::Get() |
| ->accessibility_controller() |
| ->DisableSwitchAccessDisableConfirmationDialogTesting(); |
| break; |
| } |
| } |
| }; |
| |
| class DragHandleFocusTest : public AshTestBase { |
| public: |
| DragHandleFocusTest() = default; |
| ~DragHandleFocusTest() override = default; |
| |
| const DragHandle* drag_handle() const { |
| return GetPrimaryShelf()->shelf_widget()->GetDragHandle(); |
| } |
| |
| void CheckFocusOrder(views::Widget* expected_previous, |
| views::Widget* expected_next) { |
| auto* drag_handle = GetPrimaryShelf()->shelf_widget()->GetDragHandle(); |
| views::ViewAccessibility& view_accessibility = |
| drag_handle->GetViewAccessibility(); |
| EXPECT_EQ(expected_previous, view_accessibility.GetPreviousWindowFocus()); |
| EXPECT_EQ(expected_next, view_accessibility.GetNextWindowFocus()); |
| } |
| |
| void ClickDragHandle() { |
| gfx::Point center = drag_handle()->GetBoundsInScreen().CenterPoint(); |
| GetEventGenerator()->MoveMouseTo(center); |
| GetEventGenerator()->ClickLeftButton(); |
| } |
| }; |
| |
| } // namespace |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| DragHandleTest, |
| ::testing::Values( |
| TestAccessibilityFeature::kTabletModeShelfNavigationButtons, |
| TestAccessibilityFeature::kSpokenFeedback, |
| TestAccessibilityFeature::kAutoclick, |
| TestAccessibilityFeature::kSwitchAccess)); |
| |
| TEST_P(DragHandleTest, AccessibleName) { |
| Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true); |
| UpdateDisplay("800x700"); |
| // Create a widget to transition to the in-app shelf. |
| CreateWidgetBuilderWithDelegate() |
| .SetBounds(gfx::Rect(0, 0, 800, 800)) |
| .BuildOwnedByNativeWidget(); |
| |
| // If a11y feature is enabled, the drag handle button should behave like a |
| // button. |
| SetTestA11yFeatureEnabled(true /*enabled*/); |
| EXPECT_TRUE(drag_handle()->GetEnabled()); |
| |
| ui::AXNodeData data; |
| drag_handle()->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(HotseatState::kHidden, |
| GetPrimaryShelf()->shelf_layout_manager()->hotseat_state()); |
| EXPECT_EQ( |
| data.GetString16Attribute(ax::mojom::StringAttribute::kName), |
| l10n_util::GetStringUTF16(IDS_ASH_DRAG_HANDLE_HOTSEAT_ACCESSIBLE_NAME)); |
| |
| // Click on the drag handle should extend the hotseat. |
| ClickDragHandle(); |
| data = ui::AXNodeData(); |
| drag_handle()->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(HotseatState::kExtended, |
| GetPrimaryShelf()->shelf_layout_manager()->hotseat_state()); |
| EXPECT_EQ( |
| data.GetString16Attribute(ax::mojom::StringAttribute::kName), |
| l10n_util::GetStringUTF16(IDS_ASH_DRAG_HANDLE_HOTSEAT_ACCESSIBLE_NAME)); |
| |
| // Click again should hide the hotseat. |
| ClickDragHandle(); |
| data = ui::AXNodeData(); |
| drag_handle()->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(HotseatState::kHidden, |
| GetPrimaryShelf()->shelf_layout_manager()->hotseat_state()); |
| EXPECT_EQ( |
| data.GetString16Attribute(ax::mojom::StringAttribute::kName), |
| l10n_util::GetStringUTF16(IDS_ASH_DRAG_HANDLE_HOTSEAT_ACCESSIBLE_NAME)); |
| |
| // Exit a11y feature should disable drag handle. |
| SetTestA11yFeatureEnabled(false /*enabled*/); |
| EXPECT_FALSE(drag_handle()->GetEnabled()); |
| } |
| |
| TEST_P(DragHandleTest, AccessiblePreviousAndNextFocus) { |
| Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true); |
| UpdateDisplay("800x700"); |
| // Create a widget to transition to the in-app shelf. |
| CreateWidgetBuilderWithDelegate() |
| .SetBounds(gfx::Rect(0, 0, 800, 800)) |
| .BuildOwnedByNativeWidget(); |
| |
| // If a11y feature is enabled, the drag handle button should behave like a |
| // button. |
| SetTestA11yFeatureEnabled(true /*enabled*/); |
| EXPECT_TRUE(drag_handle()->GetEnabled()); |
| |
| ui::AXNodeData data; |
| EXPECT_EQ(HotseatState::kHidden, |
| GetPrimaryShelf()->shelf_layout_manager()->hotseat_state()); |
| EXPECT_EQ(drag_handle()->GetViewAccessibility().GetNextWindowFocus(), |
| GetPrimaryShelf()->GetStatusAreaWidget()); |
| EXPECT_EQ(drag_handle()->GetViewAccessibility().GetPreviousWindowFocus(), |
| GetPrimaryShelf()->shelf_widget()->navigation_widget()); |
| |
| // Click on the drag handle should extend the hotseat. |
| ClickDragHandle(); |
| data = ui::AXNodeData(); |
| drag_handle()->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(HotseatState::kExtended, |
| GetPrimaryShelf()->shelf_layout_manager()->hotseat_state()); |
| EXPECT_EQ(drag_handle()->GetViewAccessibility().GetNextWindowFocus(), |
| GetPrimaryShelf()->hotseat_widget()); |
| EXPECT_EQ(drag_handle()->GetViewAccessibility().GetPreviousWindowFocus(), |
| GetPrimaryShelf()->hotseat_widget()); |
| |
| // Click again should hide the hotseat. |
| ClickDragHandle(); |
| data = ui::AXNodeData(); |
| drag_handle()->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(HotseatState::kHidden, |
| GetPrimaryShelf()->shelf_layout_manager()->hotseat_state()); |
| EXPECT_EQ(drag_handle()->GetViewAccessibility().GetNextWindowFocus(), |
| GetPrimaryShelf()->GetStatusAreaWidget()); |
| EXPECT_EQ(drag_handle()->GetViewAccessibility().GetPreviousWindowFocus(), |
| GetPrimaryShelf()->shelf_widget()->navigation_widget()); |
| |
| // Exit a11y feature should disable drag handle. |
| SetTestA11yFeatureEnabled(false /*enabled*/); |
| EXPECT_FALSE(drag_handle()->GetEnabled()); |
| } |
| |
| TEST_P(DragHandleTest, AccessibilityFeaturesEnabled) { |
| Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true); |
| UpdateDisplay("800x700"); |
| // Create a widget to transition to the in-app shelf. |
| CreateWidgetBuilderWithDelegate() |
| .SetBounds(gfx::Rect(0, 0, 800, 800)) |
| .BuildOwnedByNativeWidget(); |
| |
| EXPECT_TRUE(drag_handle()->GetVisible()); |
| |
| // By default, drag handle should not function as a button. |
| EXPECT_FALSE(drag_handle()->GetEnabled()); |
| |
| // If a11y feature is enabled, the drag handle button should behave like a |
| // button. |
| SetTestA11yFeatureEnabled(true /*enabled*/); |
| EXPECT_TRUE(drag_handle()->GetEnabled()); |
| |
| ui::AXNodeData data; |
| drag_handle()->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_FALSE(data.HasState(ax::mojom::State::kExpanded)); |
| EXPECT_TRUE(data.HasState(ax::mojom::State::kCollapsed)); |
| EXPECT_EQ(HotseatState::kHidden, |
| GetPrimaryShelf()->shelf_layout_manager()->hotseat_state()); |
| |
| // Click on the drag handle should extend the hotseat. |
| ClickDragHandle(); |
| data = ui::AXNodeData(); |
| drag_handle()->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_TRUE(data.HasState(ax::mojom::State::kExpanded)); |
| EXPECT_FALSE(data.HasState(ax::mojom::State::kCollapsed)); |
| EXPECT_EQ(HotseatState::kExtended, |
| GetPrimaryShelf()->shelf_layout_manager()->hotseat_state()); |
| |
| // Click again should hide the hotseat. |
| ClickDragHandle(); |
| data = ui::AXNodeData(); |
| drag_handle()->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_FALSE(data.HasState(ax::mojom::State::kExpanded)); |
| EXPECT_TRUE(data.HasState(ax::mojom::State::kCollapsed)); |
| EXPECT_EQ(HotseatState::kHidden, |
| GetPrimaryShelf()->shelf_layout_manager()->hotseat_state()); |
| |
| // Exit a11y feature should disable drag handle. |
| SetTestA11yFeatureEnabled(false /*enabled*/); |
| EXPECT_FALSE(drag_handle()->GetEnabled()); |
| } |
| |
| TEST_F(DragHandleFocusTest, AccessibilityFocusOrder) { |
| Shell::Get()->accessibility_controller()->SetSpokenFeedbackEnabled( |
| true, A11Y_NOTIFICATION_NONE); |
| Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true); |
| UpdateDisplay("800x700"); |
| // Create a widget to transition to the in-app shelf. |
| CreateWidgetBuilderWithDelegate() |
| .SetBounds(gfx::Rect(0, 0, 800, 800)) |
| .BuildOwnedByNativeWidget(); |
| EXPECT_TRUE(drag_handle()->GetVisible()); |
| |
| // When the hotseat is hidden the drag handle's next focus should be the |
| // status area and its previous focus should be the navigation area. |
| auto* shelf = GetPrimaryShelf(); |
| ASSERT_EQ(shelf->hotseat_widget()->state(), HotseatState::kHidden); |
| ui::AXNodeData data; |
| shelf->shelf_widget() |
| ->GetDragHandle() |
| ->GetViewAccessibility() |
| .GetAccessibleNodeData(&data); |
| EXPECT_FALSE(data.HasState(ax::mojom::State::kExpanded)); |
| EXPECT_TRUE(data.HasState(ax::mojom::State::kCollapsed)); |
| CheckFocusOrder(shelf->shelf_widget()->navigation_widget(), |
| shelf->status_area_widget()); |
| |
| // When the hotseat is extended the drag handle's next and previous focus |
| // should be the hotseat. |
| ClickDragHandle(); |
| ASSERT_EQ(shelf->hotseat_widget()->state(), HotseatState::kExtended); |
| data = ui::AXNodeData(); |
| shelf->shelf_widget() |
| ->GetDragHandle() |
| ->GetViewAccessibility() |
| .GetAccessibleNodeData(&data); |
| EXPECT_TRUE(data.HasState(ax::mojom::State::kExpanded)); |
| EXPECT_FALSE(data.HasState(ax::mojom::State::kCollapsed)); |
| CheckFocusOrder(shelf->hotseat_widget(), shelf->hotseat_widget()); |
| } |
| |
| } // namespace ash |