| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/user_education/user_education_util.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <set> |
| #include <string> |
| |
| #include "ash/display/window_tree_host_manager.h" |
| #include "ash/public/cpp/session/session_types.h" |
| #include "ash/public/cpp/session/user_info.h" |
| #include "ash/session/test_session_controller_client.h" |
| #include "ash/shell.h" |
| #include "ash/test/ash_test_base.h" |
| #include "ash/test/test_widget_builder.h" |
| #include "ash/user_education/user_education_types.h" |
| #include "components/account_id/account_id.h" |
| #include "components/user_education/common/help_bubble_params.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/interaction/element_tracker.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/views/metadata/view_factory.h" |
| #include "ui/views/view.h" |
| #include "ui/views/view_class_properties.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace ash::user_education_util { |
| namespace { |
| |
| // Aliases. |
| using ::session_manager::SessionState; |
| using ::testing::AnyOf; |
| using ::testing::Eq; |
| using ::user_education::HelpBubbleParams; |
| |
| // Helpers --------------------------------------------------------------------- |
| |
| std::unique_ptr<views::Widget> ShowFramelessTestWidgetOnDisplay( |
| int64_t display_id, |
| std::unique_ptr<views::View> contents_view) { |
| auto* manager = Shell::Get()->window_tree_host_manager(); |
| auto widget = |
| TestWidgetBuilder() |
| .SetWidgetType(views::Widget::InitParams::TYPE_WINDOW_FRAMELESS) |
| .SetParent(manager->GetRootWindowForDisplayId(display_id)) |
| .BuildOwnsNativeWidget(); |
| widget->SetContentsView(std::move(contents_view)); |
| widget->Show(); |
| return widget; |
| } |
| |
| } // namespace |
| |
| // UserEducationUtilTest ------------------------------------------------------- |
| |
| // Base class for tests of user education utilities. |
| using UserEducationUtilTest = ::testing::Test; |
| |
| // Tests ----------------------------------------------------------------------- |
| |
| TEST_F(UserEducationUtilTest, CreateExtendedProperties) { |
| const user_education::HelpBubbleParams::ExtendedProperties |
| extended_properties = CreateExtendedProperties( |
| CreateExtendedProperties(HelpBubbleId::kTest), |
| CreateExtendedProperties(HelpBubbleStyle::kNudge)); |
| |
| EXPECT_EQ(GetHelpBubbleId(extended_properties), HelpBubbleId::kTest); |
| EXPECT_EQ(GetHelpBubbleStyle(extended_properties), HelpBubbleStyle::kNudge); |
| } |
| |
| // Verifies that `CreateExtendedProperties()` can be used to create extended |
| // properties for a help bubble having set body icon, and that |
| // `GetHelpBubbleBodyIcon()` can be used to retrieve help bubble body icon from |
| // extended properties. |
| TEST_F(UserEducationUtilTest, CreateExtendedPropertiesWithBodyIcon) { |
| EXPECT_EQ( |
| &GetHelpBubbleBodyIcon(CreateExtendedProperties(gfx::kNoneIcon))->get(), |
| &gfx::kNoneIcon); |
| |
| // It is permissible to query help bubble body icon even when absent. |
| EXPECT_EQ(GetHelpBubbleBodyIcon(HelpBubbleParams::ExtendedProperties()), |
| std::nullopt); |
| } |
| |
| // Verifies that `CreateExtendedProperties()` can be used to create extended |
| // properties for a help bubble having set ID, and that `GetHelpBubbleId()` can |
| // be used to retrieve help bubble ID from extended properties. |
| TEST_F(UserEducationUtilTest, ExtendedPropertiesWithId) { |
| EXPECT_EQ(GetHelpBubbleId(CreateExtendedProperties(HelpBubbleId::kTest)), |
| HelpBubbleId::kTest); |
| } |
| |
| // Verifies that `CreateExtendedProperties()` can be used to create extended |
| // properties for a help bubble having set modal type, and that |
| // `GetHelpBubbleModalType()` can be used to retrieve help bubble modal type |
| // from extended properties. |
| TEST_F(UserEducationUtilTest, CreateExtendedPropertiesWithModalType) { |
| EXPECT_EQ( |
| GetHelpBubbleModalType(CreateExtendedProperties(ui::MODAL_TYPE_SYSTEM)), |
| ui::MODAL_TYPE_SYSTEM); |
| |
| // It is permissible to query help bubble modal type even when absent. |
| EXPECT_EQ(GetHelpBubbleModalType(HelpBubbleParams::ExtendedProperties()), |
| ui::MODAL_TYPE_NONE); |
| } |
| |
| // Verifies that `CreateExtendedProperties()` can be used to create extended |
| // properties for a help bubble having set style, and `GetHelpBubbleStyle()` can |
| // be used to retrieve help bubble style from extended properties. |
| TEST_F(UserEducationUtilTest, ExtendedPropertiesWithStyle) { |
| EXPECT_EQ( |
| GetHelpBubbleStyle(CreateExtendedProperties(HelpBubbleStyle::kNudge)), |
| HelpBubbleStyle::kNudge); |
| |
| // It is permissible to query help bubble style even when absent. |
| EXPECT_EQ(GetHelpBubbleStyle(HelpBubbleParams::ExtendedProperties()), |
| std::nullopt); |
| } |
| |
| // Verifies that `ToString()` is working as intended. |
| TEST_F(UserEducationUtilTest, ToString) { |
| std::set<std::string> tutorial_id_strs; |
| for (size_t i = static_cast<size_t>(TutorialId::kMinValue); |
| i <= static_cast<size_t>(TutorialId::kMaxValue); ++i) { |
| // Currently the only constraint on `ToString()` is that it returns a unique |
| // value for each distinct tutorial ID. |
| auto tutorial_id_str = ToString(static_cast<TutorialId>(i)); |
| EXPECT_TRUE(tutorial_id_strs.emplace(std::move(tutorial_id_str)).second); |
| } |
| } |
| |
| // UserEducationUtilAshTest ---------------------------------------------------- |
| |
| // Base class for tests of user education utilities which require Ash. |
| using UserEducationUtilAshTest = NoSessionAshTestBase; |
| |
| // Tests ----------------------------------------------------------------------- |
| |
| // Verifies that `GetAccountId()` is working as intended. |
| TEST_F(UserEducationUtilAshTest, GetAccountId) { |
| // Case: null `UserSession`. |
| AccountId account_id; |
| EXPECT_EQ(GetAccountId(/*user_session=*/nullptr), account_id); |
| |
| // Case: non-null `UserSession`. |
| account_id = AccountId::FromUserEmail("user@test"); |
| UserSession user_session; |
| user_session.user_info.account_id = account_id; |
| EXPECT_EQ(GetAccountId(&user_session), account_id); |
| } |
| |
| // Verifies that `GetMatchingViewInRootWindow()` is working as intended. |
| TEST_F(UserEducationUtilAshTest, GetMatchingViewInRootWindow) { |
| // Set up a primary and secondary display. |
| UpdateDisplay("1024x768,1024x768"); |
| |
| // Cache display IDs. |
| const int64_t primary_display_id = GetPrimaryDisplay().id(); |
| const int64_t secondary_display_id = GetSecondaryDisplay().id(); |
| |
| // Show a widget with a tracked element on the primary display. |
| views::View* primary_display_view = nullptr; |
| auto primary_display_widget = ShowFramelessTestWidgetOnDisplay( |
| primary_display_id, |
| views::Builder<views::View>() |
| .CopyAddressTo(&primary_display_view) |
| .SetProperty(views::kElementIdentifierKey, |
| ui::ElementTracker::kTemporaryIdentifier) |
| .Build()); |
| |
| // The tracked element *should* match for the primary root window. |
| EXPECT_EQ(GetMatchingViewInRootWindow( |
| primary_display_id, ui::ElementTracker::kTemporaryIdentifier), |
| primary_display_view); |
| |
| // The tracked element should *not* match for the secondary root window. |
| EXPECT_FALSE(GetMatchingViewInRootWindow( |
| secondary_display_id, ui::ElementTracker::kTemporaryIdentifier)); |
| |
| // Show a widget with a tracked element on the secondary display. |
| views::View* secondary_display_view = nullptr; |
| auto secondary_display_widget = ShowFramelessTestWidgetOnDisplay( |
| secondary_display_id, |
| views::Builder<views::View>() |
| .CopyAddressTo(&secondary_display_view) |
| .SetProperty(views::kElementIdentifierKey, |
| ui::ElementTracker::kTemporaryIdentifier) |
| .Build()); |
| |
| // The tracked element on the secondary display should *not* match for the |
| // primary root window. |
| EXPECT_EQ(GetMatchingViewInRootWindow( |
| primary_display_id, ui::ElementTracker::kTemporaryIdentifier), |
| primary_display_view); |
| |
| // The tracked element on the secondary display *should* match for the |
| // secondary root window. |
| EXPECT_EQ(GetMatchingViewInRootWindow( |
| secondary_display_id, ui::ElementTracker::kTemporaryIdentifier), |
| secondary_display_view); |
| |
| // Show another widget with a tracked element on the secondary display. |
| views::View* another_secondary_display_view = nullptr; |
| auto another_secondary_display_widget = ShowFramelessTestWidgetOnDisplay( |
| secondary_display_id, |
| views::Builder<views::View>() |
| .CopyAddressTo(&another_secondary_display_view) |
| .SetProperty(views::kElementIdentifierKey, |
| ui::ElementTracker::kTemporaryIdentifier) |
| .Build()); |
| |
| // Both tracked elements on the secondary display should *not* match for the |
| // primary root window when there *is* a match on the primary display. |
| EXPECT_EQ(GetMatchingViewInRootWindow( |
| primary_display_id, ui::ElementTracker::kTemporaryIdentifier), |
| primary_display_view); |
| |
| // Both tracked elements on the secondary display should *not* match for the |
| // primary root window when there is *no* match on the primary display. |
| primary_display_view = nullptr; |
| primary_display_widget.reset(); |
| EXPECT_FALSE(GetMatchingViewInRootWindow( |
| primary_display_id, ui::ElementTracker::kTemporaryIdentifier)); |
| |
| // Either of the tracked elements shown in a widget on the secondary display |
| // *should* match for the secondary root window. The utility method does *not* |
| // guarantee which match will be returned. |
| EXPECT_THAT( |
| GetMatchingViewInRootWindow(secondary_display_id, |
| ui::ElementTracker::kTemporaryIdentifier), |
| AnyOf(Eq(secondary_display_view), Eq(another_secondary_display_view))); |
| } |
| |
| // Verifies that `GetUserType()` is working as intended. |
| TEST_F(UserEducationUtilAshTest, GetUserType) { |
| AccountId guest_account_id = AccountId::FromUserEmail("guest@test"); |
| AccountId regular_account_id = AccountId::FromUserEmail("regular@test"); |
| |
| // Case: no user sessions added. |
| EXPECT_FALSE(GetUserType(AccountId())); |
| EXPECT_FALSE(GetUserType(guest_account_id)); |
| EXPECT_FALSE(GetUserType(regular_account_id)); |
| |
| auto* session_controller = GetSessionControllerClient(); |
| session_controller->AddUserSession(guest_account_id.GetUserEmail(), |
| user_manager::UserType::kGuest); |
| session_controller->AddUserSession(regular_account_id.GetUserEmail(), |
| user_manager::UserType::kRegular); |
| |
| // Case: multiple user sessions added. |
| EXPECT_FALSE(GetUserType(AccountId())); |
| EXPECT_EQ(GetUserType(guest_account_id), user_manager::UserType::kGuest); |
| EXPECT_EQ(GetUserType(regular_account_id), user_manager::UserType::kRegular); |
| } |
| |
| // Verifies that `IsPrimaryAccountActive()` is working as intended. |
| TEST_F(UserEducationUtilAshTest, IsPrimaryAccountActive) { |
| AccountId primary_account_id = AccountId::FromUserEmail("primary@test"); |
| AccountId secondary_account_id = AccountId::FromUserEmail("secondary@test"); |
| |
| // Case: no user sessions added. |
| EXPECT_FALSE(IsPrimaryAccountActive()); |
| |
| // Case: primary user session added but inactive. |
| auto* session_controller_client = GetSessionControllerClient(); |
| session_controller_client->AddUserSession(primary_account_id.GetUserEmail()); |
| EXPECT_FALSE(IsPrimaryAccountActive()); |
| |
| // Case: primary user session activated. |
| session_controller_client->SetSessionState(SessionState::ACTIVE); |
| EXPECT_TRUE(IsPrimaryAccountActive()); |
| |
| // Case: primary user session locked and then unlocked. |
| session_controller_client->SetSessionState(SessionState::LOCKED); |
| EXPECT_FALSE(IsPrimaryAccountActive()); |
| session_controller_client->SetSessionState(SessionState::ACTIVE); |
| EXPECT_TRUE(IsPrimaryAccountActive()); |
| |
| // Case: secondary user session added but inactive. |
| session_controller_client->AddUserSession( |
| secondary_account_id.GetUserEmail()); |
| EXPECT_TRUE(IsPrimaryAccountActive()); |
| |
| // Case: secondary user activated and then deactivated. |
| session_controller_client->SwitchActiveUser(secondary_account_id); |
| EXPECT_FALSE(IsPrimaryAccountActive()); |
| session_controller_client->SwitchActiveUser(primary_account_id); |
| EXPECT_TRUE(IsPrimaryAccountActive()); |
| } |
| |
| // Verifies that `IsPrimaryAccountId()` is working as intended. |
| TEST_F(UserEducationUtilAshTest, IsPrimaryAccountId) { |
| AccountId primary_account_id = AccountId::FromUserEmail("primary@test"); |
| AccountId secondary_account_id = AccountId::FromUserEmail("secondary@test"); |
| |
| // Case: no user sessions added. |
| EXPECT_FALSE(IsPrimaryAccountId(AccountId())); |
| EXPECT_FALSE(IsPrimaryAccountId(primary_account_id)); |
| EXPECT_FALSE(IsPrimaryAccountId(secondary_account_id)); |
| |
| auto* session_controller_client = GetSessionControllerClient(); |
| session_controller_client->AddUserSession(primary_account_id.GetUserEmail()); |
| session_controller_client->AddUserSession( |
| secondary_account_id.GetUserEmail()); |
| |
| // Case: multiple user sessions added. |
| EXPECT_FALSE(IsPrimaryAccountId(AccountId())); |
| EXPECT_TRUE(IsPrimaryAccountId(primary_account_id)); |
| EXPECT_FALSE(IsPrimaryAccountId(secondary_account_id)); |
| } |
| |
| } // namespace ash::user_education_util |