// Copyright 2018 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/accessibility/accessibility_feature_pod_controller.h"
#include "ash/accessibility/a11y_feature_type.h"
#include "ash/accessibility/accessibility_controller.h"
#include "ash/constants/quick_settings_catalogs.h"
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/shell.h"
#include "ash/system/tray/tray_bubble_view.h"
#include "ash/system/unified/feature_tile.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/system/unified/unified_system_tray_bubble.h"
#include "ash/system/unified/unified_system_tray_controller.h"
#include "ash/test/ash_test_base.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "ui/base/metadata/metadata_utils.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/view.h"
#include "ui/views/view_utils.h"
namespace ash {
// Tests manually control their session state.
class AccessibilityFeaturePodControllerTest : public NoSessionAshTestBase {
public:
AccessibilityFeaturePodControllerTest() = default;
AccessibilityFeaturePodControllerTest(
const AccessibilityFeaturePodControllerTest&) = delete;
AccessibilityFeaturePodControllerTest& operator=(
const AccessibilityFeaturePodControllerTest&) = delete;
~AccessibilityFeaturePodControllerTest() override = default;
void TearDown() override {
GetPrimaryUnifiedSystemTray()->CloseBubble();
NoSessionAshTestBase::TearDown();
}
protected:
AccessibilityController* GetAccessibilityController() {
return Shell::Get()->accessibility_controller();
}
UnifiedSystemTrayController* tray_controller() {
return GetPrimaryUnifiedSystemTray()
->bubble()
->unified_system_tray_controller();
}
void PressIcon() {
for (auto& controller : tray_controller()->feature_pod_controllers_) {
if (controller->GetCatalogName() ==
QsFeatureCatalogName::kAccessibility) {
controller->OnIconPressed();
return;
}
}
}
void PressLabel() {
for (auto& controller : tray_controller()->feature_pod_controllers_) {
if (controller->GetCatalogName() ==
QsFeatureCatalogName::kAccessibility) {
controller->OnLabelPressed();
return;
}
}
}
const char* GetToggledOnHistogramName() {
return "Ash.QuickSettings.FeaturePod.ToggledOn";
}
const char* GetToggledOffHistogramName() {
return "Ash.QuickSettings.FeaturePod.ToggledOff";
}
const char* GetDiveInHistogramName() {
return "Ash.QuickSettings.FeaturePod.DiveIn";
}
};
TEST_F(AccessibilityFeaturePodControllerTest, ButtonVisibilityNotLoggedIn) {
GetPrimaryUnifiedSystemTray()->ShowBubble();
auto* tile = views::AsViewClass<FeatureTile>(
GetPrimaryUnifiedSystemTray()->bubble()->GetBubbleView()->GetViewByID(
VIEW_ID_FEATURE_TILE_ACCESSIBILITY));
// If not logged in, it should be always visible.
EXPECT_TRUE(tile->GetVisible());
}
TEST_F(AccessibilityFeaturePodControllerTest, ButtonVisibilityLoggedIn) {
CreateUserSessions(1);
GetPrimaryUnifiedSystemTray()->ShowBubble();
auto* tile = views::AsViewClass<FeatureTile>(
GetPrimaryUnifiedSystemTray()->bubble()->GetBubbleView()->GetViewByID(
VIEW_ID_FEATURE_TILE_ACCESSIBILITY));
// If logged in, it's not visible by default.
EXPECT_FALSE(tile->GetVisible());
}
TEST_F(AccessibilityFeaturePodControllerTest, IconUMATracking) {
GetPrimaryUnifiedSystemTray()->ShowBubble();
// No metrics logged before clicking on any views.
auto histogram_tester = std::make_unique<base::HistogramTester>();
histogram_tester->ExpectTotalCount(GetToggledOnHistogramName(),
/*count=*/0);
histogram_tester->ExpectTotalCount(GetToggledOffHistogramName(),
/*count=*/0);
histogram_tester->ExpectTotalCount(GetDiveInHistogramName(),
/*count=*/0);
// Show a11y detailed view when pressing on the icon.
PressIcon();
histogram_tester->ExpectTotalCount(GetToggledOnHistogramName(),
/*count=*/0);
histogram_tester->ExpectTotalCount(GetToggledOffHistogramName(),
/*count=*/0);
histogram_tester->ExpectTotalCount(GetDiveInHistogramName(),
/*count=*/1);
histogram_tester->ExpectBucketCount(GetDiveInHistogramName(),
QsFeatureCatalogName::kAccessibility,
/*expected_count=*/1);
}
TEST_F(AccessibilityFeaturePodControllerTest, LabelUMATracking) {
GetPrimaryUnifiedSystemTray()->ShowBubble();
// No metrics logged before clicking on any views.
auto histogram_tester = std::make_unique<base::HistogramTester>();
histogram_tester->ExpectTotalCount(GetToggledOnHistogramName(),
/*count=*/0);
histogram_tester->ExpectTotalCount(GetToggledOffHistogramName(),
/*count=*/0);
histogram_tester->ExpectTotalCount(GetDiveInHistogramName(),
/*count=*/0);
// Show a11y detailed view when pressing on the label.
PressLabel();
histogram_tester->ExpectTotalCount(GetToggledOnHistogramName(),
/*count=*/0);
histogram_tester->ExpectTotalCount(GetToggledOffHistogramName(),
/*count=*/0);
histogram_tester->ExpectTotalCount(GetDiveInHistogramName(),
/*count=*/1);
histogram_tester->ExpectBucketCount(GetDiveInHistogramName(),
QsFeatureCatalogName::kAccessibility,
/*expected_count=*/1);
}
TEST_F(AccessibilityFeaturePodControllerTest, FeatureTileBasicToggleBehavior) {
GetPrimaryUnifiedSystemTray()->ShowBubble();
auto* tile = views::AsViewClass<FeatureTile>(
GetPrimaryUnifiedSystemTray()->bubble()->GetBubbleView()->GetViewByID(
VIEW_ID_FEATURE_TILE_ACCESSIBILITY));
EXPECT_FALSE(tile->IsToggled());
// Enable an accessibility feature and expect the feature tile to be toggled
// and the sublabel to be visible.
GetAccessibilityController()
->GetFeature(A11yFeatureType::kHighContrast)
.SetEnabled(true);
EXPECT_TRUE(tile->IsToggled());
EXPECT_TRUE(tile->sub_label()->GetVisible());
// Disable an accessibility feature and expect the feature tile to be
// untoggled and the sublabel to be invisible.
GetAccessibilityController()
->GetFeature(A11yFeatureType::kHighContrast)
.SetEnabled(false);
EXPECT_FALSE(tile->IsToggled());
EXPECT_FALSE(tile->sub_label()->GetVisible());
}
TEST_F(AccessibilityFeaturePodControllerTest, WithMultipleFeatureToggled) {
GetPrimaryUnifiedSystemTray()->ShowBubble();
auto* tile = views::AsViewClass<FeatureTile>(
GetPrimaryUnifiedSystemTray()->bubble()->GetBubbleView()->GetViewByID(
VIEW_ID_FEATURE_TILE_ACCESSIBILITY));
EXPECT_FALSE(tile->IsToggled());
// Enable an accessibility feature and expect the feature tile to be toggled
// and the sublabel to be visible.
GetAccessibilityController()
->GetFeature(A11yFeatureType::kSpokenFeedback)
.SetEnabled(true);
EXPECT_TRUE(tile->IsToggled());
EXPECT_TRUE(tile->sub_label()->GetVisible());
EXPECT_EQ(tile->sub_label()->GetText(), u"ChromeVox (spoken feedback)");
GetAccessibilityController()
->GetFeature(A11yFeatureType::kDockedMagnifier)
.SetEnabled(true);
EXPECT_TRUE(tile->IsToggled());
EXPECT_TRUE(tile->sub_label()->GetVisible());
// Here we only check the "...", which is u"\x2026", and the trailing count
// are created. We don't check the first specific a11y name, since the a11y
// names are stored in a map so the order of them is not fixed.
EXPECT_TRUE(base::Contains(tile->sub_label()->GetText(), u"\x2026, +1"));
GetAccessibilityController()
->GetFeature(A11yFeatureType::kVirtualKeyboard)
.SetEnabled(true);
EXPECT_TRUE(tile->IsToggled());
EXPECT_TRUE(tile->sub_label()->GetVisible());
EXPECT_TRUE(base::Contains(tile->sub_label()->GetText(), u"\x2026, +2"));
}
// Toggle all accessibility features one by one and make sure the feature tile
// is updated appropriately.
TEST_F(AccessibilityFeaturePodControllerTest, FeatureTileAllFeaturesToggled) {
// Disables the features that have confirmation dialog when enabling/disabling
// them. Otherwise they will block the other tests after them.
GetAccessibilityController()->DisableAutoClickConfirmationDialogForTest();
GetAccessibilityController()
->DisableSwitchAccessDisableConfirmationDialogTesting();
GetPrimaryUnifiedSystemTray()->ShowBubble();
auto* tile = views::AsViewClass<FeatureTile>(
GetPrimaryUnifiedSystemTray()->bubble()->GetBubbleView()->GetViewByID(
VIEW_ID_FEATURE_TILE_ACCESSIBILITY));
EXPECT_FALSE(tile->IsToggled());
for (int type = 0; type != static_cast<int>(A11yFeatureType::kFeatureCount);
type++) {
auto& feature = GetAccessibilityController()->GetFeature(
static_cast<A11yFeatureType>(type));
feature.SetEnabled(true);
if (!feature.enabled()) {
continue;
}
if (feature.toggleable_in_quicksettings()) {
EXPECT_TRUE(tile->IsToggled());
} else {
EXPECT_FALSE(tile->IsToggled());
}
feature.SetEnabled(false);
}
}
// Enable accessibility features one by one until we have double digits in the
// count shown in the `sub_label`.
TEST_F(AccessibilityFeaturePodControllerTest,
FeatureTileSubLabelCounterBehavior) {
GetPrimaryUnifiedSystemTray()->ShowBubble();
auto* tile = views::AsViewClass<FeatureTile>(
GetPrimaryUnifiedSystemTray()->bubble()->GetBubbleView()->GetViewByID(
VIEW_ID_FEATURE_TILE_ACCESSIBILITY));
GetAccessibilityController()
->GetFeature(A11yFeatureType::kLargeCursor)
.SetEnabled(true);
int expected_count = 0;
auto feature_types = {
A11yFeatureType::kCaretHighlight, A11yFeatureType::kCursorHighlight,
A11yFeatureType::kDictation, A11yFeatureType::kFocusHighlight,
A11yFeatureType::kHighContrast, A11yFeatureType::kMonoAudio,
A11yFeatureType::kLiveCaption, A11yFeatureType::kFullscreenMagnifier,
A11yFeatureType::kStickyKeys, A11yFeatureType::kSwitchAccess};
for (A11yFeatureType type : feature_types) {
auto& feature = GetAccessibilityController()->GetFeature(
static_cast<A11yFeatureType>(type));
feature.SetEnabled(true);
expected_count++;
EXPECT_TRUE(base::EndsWith(tile->sub_label()->GetText(),
base::NumberToString16(expected_count)));
}
for (A11yFeatureType type : feature_types) {
EXPECT_TRUE(base::EndsWith(tile->sub_label()->GetText(),
base::NumberToString16(expected_count)));
auto& feature = GetAccessibilityController()->GetFeature(
static_cast<A11yFeatureType>(type));
expected_count--;
feature.SetEnabled(false);
}
}
} // namespace ash