| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| #include <string> |
| #include <unordered_set> |
| #include <utility> |
| |
| #include "ash/detachable_base/detachable_base_pairing_status.h" |
| #include "ash/login/mock_login_screen_client.h" |
| #include "ash/login/ui/arrow_button_view.h" |
| #include "ash/login/ui/fake_login_detachable_base_model.h" |
| #include "ash/login/ui/lock_contents_view.h" |
| #include "ash/login/ui/lock_screen.h" |
| #include "ash/login/ui/login_auth_user_view.h" |
| #include "ash/login/ui/login_big_user_view.h" |
| #include "ash/login/ui/login_display_style.h" |
| #include "ash/login/ui/login_expanded_public_account_view.h" |
| #include "ash/login/ui/login_keyboard_test_base.h" |
| #include "ash/login/ui/login_pin_view.h" |
| #include "ash/login/ui/login_public_account_user_view.h" |
| #include "ash/login/ui/login_test_base.h" |
| #include "ash/login/ui/login_test_utils.h" |
| #include "ash/login/ui/login_user_view.h" |
| #include "ash/login/ui/scrollable_users_list_view.h" |
| #include "ash/login/ui/views_utils.h" |
| #include "ash/public/interfaces/tray_action.mojom.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/shell.h" |
| #include "ash/system/power/backlights_forced_off_setter.h" |
| #include "ash/system/power/power_button_controller.h" |
| #include "ash/system/status_area_widget.h" |
| #include "ash/tray_action/test_tray_action_client.h" |
| #include "ash/tray_action/tray_action.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/fake_power_manager_client.h" |
| #include "chromeos/dbus/power_manager/suspend.pb.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/display/manager/display_manager.h" |
| #include "ui/display/test/display_manager_test_api.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/views/controls/textfield/textfield.h" |
| #include "ui/views/widget/widget.h" |
| |
| using ::testing::_; |
| using ::testing::Mock; |
| |
| namespace ash { |
| |
| namespace { |
| |
| void PressAndReleasePowerButton() { |
| base::SimpleTestTickClock tick_clock; |
| auto dispatch_power_button_event_after_delay = |
| [&](const base::TimeDelta& delta, bool down) { |
| tick_clock.Advance(delta + base::TimeDelta::FromMilliseconds(1)); |
| Shell::Get()->power_button_controller()->OnPowerButtonEvent( |
| down, tick_clock.NowTicks()); |
| base::RunLoop().RunUntilIdle(); |
| }; |
| |
| // Press and release the power button to force backlights off. |
| dispatch_power_button_event_after_delay( |
| PowerButtonController::kIgnorePowerButtonAfterResumeDelay, true /*down*/); |
| dispatch_power_button_event_after_delay( |
| PowerButtonController::kIgnoreRepeatedButtonUpDelay, false /*down*/); |
| } |
| |
| } // namespace |
| |
| using LockContentsViewUnitTest = LoginTestBase; |
| using LockContentsViewKeyboardUnitTest = LoginKeyboardTestBase; |
| |
| TEST_F(LockContentsViewUnitTest, DisplayMode) { |
| // Build lock screen with 1 user. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| |
| // Verify user list and secondary auth are not shown for one user. |
| LockContentsView::TestApi lock_contents(contents); |
| EXPECT_EQ(nullptr, lock_contents.users_list()); |
| EXPECT_FALSE(lock_contents.opt_secondary_big_view()); |
| |
| // Verify user list is not shown for two users, but secondary auth is. |
| SetUserCount(2); |
| EXPECT_EQ(nullptr, lock_contents.users_list()); |
| EXPECT_TRUE(lock_contents.opt_secondary_big_view()); |
| |
| // Verify user names and pod style is set correctly for 3-25 users. This also |
| // sanity checks that LockContentsView can respond to a multiple user change |
| // events fired from the data dispatcher, which is needed for the debug UI. |
| for (size_t user_count = 3; user_count < 25; ++user_count) { |
| SetUserCount(user_count); |
| ScrollableUsersListView::TestApi users_list(lock_contents.users_list()); |
| EXPECT_EQ(user_count - 1, users_list.user_views().size()); |
| |
| // 1 extra user gets large style. |
| LoginDisplayStyle expected_style = LoginDisplayStyle::kLarge; |
| // 2-6 extra users get small style. |
| if (user_count >= 3) |
| expected_style = LoginDisplayStyle::kSmall; |
| // 7+ users get get extra small style. |
| if (user_count >= 7) |
| expected_style = LoginDisplayStyle::kExtraSmall; |
| |
| for (size_t i = 0; i < users_list.user_views().size(); ++i) { |
| LoginUserView::TestApi user_test_api(users_list.user_views()[i]); |
| EXPECT_EQ(expected_style, user_test_api.display_style()); |
| |
| const mojom::LoginUserInfoPtr& user = users()[i + 1]; |
| EXPECT_EQ(base::UTF8ToUTF16(user->basic_user_info->display_name), |
| user_test_api.displayed_name()); |
| } |
| } |
| } |
| |
| // Verifies that the single user view is centered. |
| TEST_F(LockContentsViewUnitTest, SingleUserCentered) { |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| |
| LockContentsView::TestApi test_api(contents); |
| LoginBigUserView* auth_view = test_api.primary_big_view(); |
| gfx::Rect widget_bounds = widget->GetWindowBoundsInScreen(); |
| int expected_margin = |
| (widget_bounds.width() - auth_view->GetPreferredSize().width()) / 2; |
| gfx::Rect auth_bounds = auth_view->GetBoundsInScreen(); |
| |
| EXPECT_NE(0, expected_margin); |
| EXPECT_EQ(expected_margin, auth_bounds.x()); |
| EXPECT_EQ(expected_margin, |
| widget_bounds.width() - (auth_bounds.x() + auth_bounds.width())); |
| } |
| |
| // Verifies that the single user view is centered when lock screen notes are |
| // enabled. |
| TEST_F(LockContentsViewUnitTest, SingleUserCenteredNoteActionEnabled) { |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| |
| LockContentsView::TestApi test_api(contents); |
| LoginBigUserView* auth_view = test_api.primary_big_view(); |
| gfx::Rect widget_bounds = widget->GetWindowBoundsInScreen(); |
| int expected_margin = |
| (widget_bounds.width() - auth_view->GetPreferredSize().width()) / 2; |
| gfx::Rect auth_bounds = auth_view->GetBoundsInScreen(); |
| |
| EXPECT_NE(0, expected_margin); |
| EXPECT_EQ(expected_margin, auth_bounds.x()); |
| EXPECT_EQ(expected_margin, |
| widget_bounds.width() - (auth_bounds.x() + auth_bounds.width())); |
| } |
| |
| // Verifies that any top-level spacing views go down to width zero in small |
| // screen sizes. |
| TEST_F(LockContentsViewUnitTest, LayoutInSmallScreenSize) { |
| // Build lock screen. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| LockContentsView::TestApi lock_contents(contents); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| display::test::DisplayManagerTestApi display_manager_test_api( |
| display_manager()); |
| |
| auto get_left_view = [&]() -> views::View* { |
| return lock_contents.primary_big_view(); |
| }; |
| auto get_right_view = [&]() -> views::View* { |
| if (lock_contents.opt_secondary_big_view()) |
| return lock_contents.opt_secondary_big_view(); |
| return lock_contents.users_list(); |
| }; |
| |
| for (int i = 2; i < 10; ++i) { |
| SetUserCount(i); |
| views::View* left_view = get_left_view(); |
| views::View* right_view = get_right_view(); |
| |
| // Determine the full-sized widths when there is plenty of spacing available |
| display_manager_test_api.UpdateDisplay("2000x1000"); |
| int left_width = left_view->width(); |
| int right_width = right_view->width(); |
| |
| int left_x = left_view->x(); |
| int right_x = right_view->x(); |
| |
| // Resize to the minimum width that will fit both the left and right views |
| int display_width = left_width + right_width; |
| display_manager_test_api.UpdateDisplay(std::to_string(display_width) + |
| "x400"); |
| |
| // Verify the views moved, ie, a layout was performed |
| EXPECT_NE(left_view->x(), left_x); |
| EXPECT_NE(right_view->x(), right_x); |
| |
| // Left and right views still have their full widths |
| EXPECT_EQ(left_width, left_view->width()); |
| EXPECT_EQ(right_width, right_view->width()); |
| |
| // Left edge of |left_view| should be at start of the screen. |
| EXPECT_EQ(left_view->GetBoundsInScreen().x(), 0); |
| // Left edge of |right_view| should immediately follow |left_view| with no |
| // gap. |
| EXPECT_EQ(left_view->GetBoundsInScreen().right(), |
| right_view->GetBoundsInScreen().x()); |
| // Right edge of |right_view| should be at the end of the screen. |
| EXPECT_EQ(right_view->GetBoundsInScreen().right(), display_width); |
| } |
| } |
| |
| // Verifies that layout dynamically updates after a rotation by checking the |
| // distance between the auth user and the user list in landscape and portrait |
| // mode. |
| TEST_F(LockContentsViewUnitTest, AutoLayoutAfterRotation) { |
| // Build lock screen with three users. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| LockContentsView::TestApi lock_contents(contents); |
| SetUserCount(3); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| |
| // Returns the distance between the auth user view and the user view. |
| auto calculate_distance = [&]() { |
| if (lock_contents.opt_secondary_big_view()) { |
| return lock_contents.opt_secondary_big_view()->GetBoundsInScreen().x() - |
| lock_contents.primary_big_view()->GetBoundsInScreen().x(); |
| } |
| ScrollableUsersListView::TestApi users_list(lock_contents.users_list()); |
| return users_list.user_views()[0]->GetBoundsInScreen().x() - |
| lock_contents.primary_big_view()->GetBoundsInScreen().x(); |
| }; |
| |
| const display::Display& display = |
| display::Screen::GetScreen()->GetDisplayNearestWindow( |
| widget->GetNativeWindow()); |
| for (int i = 2; i < 10; ++i) { |
| SetUserCount(i); |
| |
| // Start at 0 degrees (landscape). |
| display_manager()->SetDisplayRotation( |
| display.id(), display::Display::ROTATE_0, |
| display::Display::RotationSource::ACTIVE); |
| int distance_0deg = calculate_distance(); |
| EXPECT_NE(distance_0deg, 0); |
| |
| // Rotate the display to 90 degrees (portrait). |
| display_manager()->SetDisplayRotation( |
| display.id(), display::Display::ROTATE_90, |
| display::Display::RotationSource::ACTIVE); |
| int distance_90deg = calculate_distance(); |
| EXPECT_GT(distance_0deg, distance_90deg); |
| |
| // Rotate the display back to 0 degrees (landscape). |
| display_manager()->SetDisplayRotation( |
| display.id(), display::Display::ROTATE_0, |
| display::Display::RotationSource::ACTIVE); |
| int distance_180deg = calculate_distance(); |
| EXPECT_EQ(distance_0deg, distance_180deg); |
| EXPECT_NE(distance_0deg, distance_90deg); |
| } |
| } |
| |
| TEST_F(LockContentsViewUnitTest, AutoLayoutExtraSmallUsersListAfterRotation) { |
| // Build lock screen with extra small layout (> 6 users). |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(9); |
| ScrollableUsersListView* users_list = |
| LockContentsView::TestApi(contents).users_list(); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| |
| // Users list in extra small layout should adjust its height to parent. |
| EXPECT_EQ(contents->height(), users_list->height()); |
| |
| const display::Display& display = |
| display::Screen::GetScreen()->GetDisplayNearestWindow( |
| widget->GetNativeWindow()); |
| |
| // Start at 0 degrees (landscape). |
| display_manager()->SetDisplayRotation( |
| display.id(), display::Display::ROTATE_0, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_EQ(contents->height(), users_list->height()); |
| |
| // Rotate the display to 90 degrees (portrait). |
| display_manager()->SetDisplayRotation( |
| display.id(), display::Display::ROTATE_90, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_EQ(contents->height(), users_list->height()); |
| |
| // Rotate the display back to 0 degrees (landscape). |
| display_manager()->SetDisplayRotation( |
| display.id(), display::Display::ROTATE_0, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_EQ(contents->height(), users_list->height()); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, AutoLayoutSmallUsersListAfterRotation) { |
| // Build lock screen with small layout (3-6 users). |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(4); |
| ScrollableUsersListView* users_list = |
| LockContentsView::TestApi(contents).users_list(); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| |
| // Calculate top spacing between users list and lock screen contents. |
| auto top_margin = [&]() { |
| return users_list->GetBoundsInScreen().y() - |
| contents->GetBoundsInScreen().y(); |
| }; |
| |
| // Calculate bottom spacing between users list and lock screen contents. |
| auto bottom_margin = [&]() { |
| return contents->GetBoundsInScreen().bottom() - |
| users_list->GetBoundsInScreen().bottom(); |
| }; |
| |
| // Users list in small layout should adjust its height to content and be |
| // vertical centered in parent. |
| EXPECT_EQ(top_margin(), bottom_margin()); |
| EXPECT_EQ(users_list->height(), users_list->contents()->height()); |
| |
| const display::Display& display = |
| display::Screen::GetScreen()->GetDisplayNearestWindow( |
| widget->GetNativeWindow()); |
| |
| // Start at 0 degrees (landscape). |
| display_manager()->SetDisplayRotation( |
| display.id(), display::Display::ROTATE_0, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_EQ(top_margin(), bottom_margin()); |
| EXPECT_EQ(users_list->height(), users_list->contents()->height()); |
| |
| // Rotate the display to 90 degrees (portrait). |
| display_manager()->SetDisplayRotation( |
| display.id(), display::Display::ROTATE_90, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_EQ(top_margin(), bottom_margin()); |
| EXPECT_EQ(users_list->height(), users_list->contents()->height()); |
| |
| // Rotate the display back to 0 degrees (landscape). |
| display_manager()->SetDisplayRotation( |
| display.id(), display::Display::ROTATE_0, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_EQ(top_margin(), bottom_margin()); |
| EXPECT_EQ(users_list->height(), users_list->contents()->height()); |
| } |
| |
| TEST_F(LockContentsViewKeyboardUnitTest, |
| AutoLayoutExtraSmallUsersListForKeyboard) { |
| // Build lock screen with extra small layout (> 6 users). |
| ASSERT_NO_FATAL_FAILURE(ShowLoginScreen()); |
| LockContentsView* contents = |
| LockScreen::TestApi(LockScreen::Get()).contents_view(); |
| ASSERT_NE(nullptr, contents); |
| SetUserCount(9); |
| |
| // Users list in extra small layout should adjust its height to parent. |
| ScrollableUsersListView* users_list = |
| LockContentsView::TestApi(contents).users_list(); |
| EXPECT_EQ(contents->height(), users_list->height()); |
| |
| ASSERT_NO_FATAL_FAILURE(ShowKeyboard()); |
| gfx::Rect keyboard_bounds = GetKeyboardBoundsInScreen(); |
| EXPECT_FALSE(users_list->GetBoundsInScreen().Intersects(keyboard_bounds)); |
| EXPECT_EQ(contents->height(), users_list->height()); |
| |
| ASSERT_NO_FATAL_FAILURE(HideKeyboard()); |
| EXPECT_EQ(contents->height(), users_list->height()); |
| } |
| |
| TEST_F(LockContentsViewKeyboardUnitTest, AutoLayoutSmallUsersListForKeyboard) { |
| // Build lock screen with small layout (3-6 users). |
| ASSERT_NO_FATAL_FAILURE(ShowLoginScreen()); |
| LockContentsView* contents = |
| LockScreen::TestApi(LockScreen::Get()).contents_view(); |
| ASSERT_NE(nullptr, contents); |
| SetUserCount(4); |
| ScrollableUsersListView* users_list = |
| LockContentsView::TestApi(contents).users_list(); |
| |
| // Calculate top spacing between users list and lock screen contents. |
| auto top_margin = [&]() { |
| return users_list->GetBoundsInScreen().y() - |
| contents->GetBoundsInScreen().y(); |
| }; |
| |
| // Calculate bottom spacing between users list and lock screen contents. |
| auto bottom_margin = [&]() { |
| return contents->GetBoundsInScreen().bottom() - |
| users_list->GetBoundsInScreen().bottom(); |
| }; |
| |
| // Users list in small layout should adjust its height to content and be |
| // vertical centered in parent. |
| EXPECT_EQ(top_margin(), bottom_margin()); |
| EXPECT_EQ(users_list->height(), users_list->contents()->height()); |
| |
| ASSERT_NO_FATAL_FAILURE(ShowKeyboard()); |
| gfx::Rect keyboard_bounds = GetKeyboardBoundsInScreen(); |
| EXPECT_FALSE(users_list->GetBoundsInScreen().Intersects(keyboard_bounds)); |
| EXPECT_EQ(top_margin(), bottom_margin()); |
| |
| ASSERT_NO_FATAL_FAILURE(HideKeyboard()); |
| EXPECT_EQ(top_margin(), bottom_margin()); |
| EXPECT_EQ(users_list->height(), users_list->contents()->height()); |
| } |
| |
| // Ensures that when swapping between two users, only auth method display swaps. |
| TEST_F(LockContentsViewUnitTest, SwapAuthUsersInTwoUserLayout) { |
| // Build lock screen with two users. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| LockContentsView::TestApi test_api(contents); |
| SetUserCount(2); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| |
| // Capture user info to validate it did not change during the swap. |
| AccountId primary_user = test_api.primary_big_view() |
| ->GetCurrentUser() |
| ->basic_user_info->account_id; |
| AccountId secondary_user = test_api.opt_secondary_big_view() |
| ->GetCurrentUser() |
| ->basic_user_info->account_id; |
| EXPECT_NE(primary_user, secondary_user); |
| |
| // Primary user starts with auth. Secondary user does not have any auth. |
| EXPECT_TRUE(test_api.primary_big_view()->IsAuthEnabled()); |
| EXPECT_FALSE(test_api.opt_secondary_big_view()->IsAuthEnabled()); |
| ASSERT_NE(nullptr, test_api.opt_secondary_big_view()->auth_user()); |
| |
| // Send event to swap users. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| LoginAuthUserView::TestApi secondary_test_api( |
| test_api.opt_secondary_big_view()->auth_user()); |
| generator->MoveMouseTo( |
| secondary_test_api.user_view()->GetBoundsInScreen().CenterPoint()); |
| generator->ClickLeftButton(); |
| |
| // User info is not swapped. |
| EXPECT_EQ(primary_user, test_api.primary_big_view() |
| ->GetCurrentUser() |
| ->basic_user_info->account_id); |
| EXPECT_EQ(secondary_user, test_api.opt_secondary_big_view() |
| ->GetCurrentUser() |
| ->basic_user_info->account_id); |
| |
| // Active auth user (ie, which user is showing password) is swapped. |
| EXPECT_FALSE(test_api.primary_big_view()->IsAuthEnabled()); |
| EXPECT_TRUE(test_api.opt_secondary_big_view()->IsAuthEnabled()); |
| } |
| |
| // Ensures that when swapping from a user list, the entire user info is swapped. |
| TEST_F(LockContentsViewUnitTest, SwapUserListToPrimaryAuthUser) { |
| // Build lock screen with five users. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| LockContentsView::TestApi lock_contents(contents); |
| SetUserCount(5); |
| ScrollableUsersListView::TestApi users_list(lock_contents.users_list()); |
| EXPECT_EQ(users().size() - 1, users_list.user_views().size()); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| |
| LoginBigUserView* auth_view = lock_contents.primary_big_view(); |
| |
| for (const LoginUserView* const list_user_view : users_list.user_views()) { |
| // Capture user info to validate it did not change during the swap. |
| AccountId auth_id = |
| auth_view->GetCurrentUser()->basic_user_info->account_id; |
| AccountId list_user_id = |
| list_user_view->current_user()->basic_user_info->account_id; |
| EXPECT_NE(auth_id, list_user_id); |
| |
| // Send event to swap users. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->MoveMouseTo(list_user_view->GetBoundsInScreen().CenterPoint()); |
| generator->ClickLeftButton(); |
| |
| // User info is swapped. |
| EXPECT_EQ(list_user_id, |
| auth_view->GetCurrentUser()->basic_user_info->account_id); |
| EXPECT_EQ(auth_id, |
| list_user_view->current_user()->basic_user_info->account_id); |
| |
| // Validate that every user is still unique. |
| std::unordered_set<std::string> emails; |
| for (const LoginUserView* const view : users_list.user_views()) { |
| std::string email = |
| view->current_user()->basic_user_info->account_id.GetUserEmail(); |
| EXPECT_TRUE(emails.insert(email).second); |
| } |
| } |
| } |
| |
| // Test goes through different lock screen note state changes and tests that |
| // the note action visibility is updated accordingly. |
| TEST_F(LockContentsViewUnitTest, NoteActionButtonVisibilityChanges) { |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| SetWidget(CreateWidgetWithContent(contents)); |
| |
| LockContentsView::TestApi test_api(contents); |
| views::View* note_action_button = test_api.note_action(); |
| |
| // In kAvailable state, the note action button should be visible. |
| EXPECT_TRUE(note_action_button->visible()); |
| |
| // In kLaunching state, the note action button should not be visible. |
| DataDispatcher()->SetLockScreenNoteState(mojom::TrayActionState::kLaunching); |
| EXPECT_FALSE(note_action_button->visible()); |
| |
| // In kActive state, the note action button should not be visible. |
| DataDispatcher()->SetLockScreenNoteState(mojom::TrayActionState::kActive); |
| EXPECT_FALSE(note_action_button->visible()); |
| |
| // When moved back to kAvailable state, the note action button should become |
| // visible again. |
| DataDispatcher()->SetLockScreenNoteState(mojom::TrayActionState::kAvailable); |
| EXPECT_TRUE(note_action_button->visible()); |
| |
| // In kNotAvailable state, the note action button should not be visible. |
| DataDispatcher()->SetLockScreenNoteState( |
| mojom::TrayActionState::kNotAvailable); |
| EXPECT_FALSE(note_action_button->visible()); |
| } |
| |
| // Verifies note action view bounds. |
| TEST_F(LockContentsViewUnitTest, NoteActionButtonBounds) { |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| |
| LockContentsView::TestApi test_api(contents); |
| |
| // The note action button should not be visible if the note action is not |
| // available. |
| EXPECT_FALSE(test_api.note_action()->visible()); |
| |
| // When the note action becomes available, the note action button should be |
| // shown. |
| DataDispatcher()->SetLockScreenNoteState(mojom::TrayActionState::kAvailable); |
| EXPECT_TRUE(test_api.note_action()->visible()); |
| |
| // Verify the bounds of the note action button are as expected. |
| gfx::Rect widget_bounds = widget->GetWindowBoundsInScreen(); |
| gfx::Size note_action_size = test_api.note_action()->GetPreferredSize(); |
| EXPECT_EQ(gfx::Rect(widget_bounds.top_right() - |
| gfx::Vector2d(note_action_size.width(), 0), |
| note_action_size), |
| test_api.note_action()->GetBoundsInScreen()); |
| |
| // If the note action is disabled again, the note action button should be |
| // hidden. |
| DataDispatcher()->SetLockScreenNoteState( |
| mojom::TrayActionState::kNotAvailable); |
| EXPECT_FALSE(test_api.note_action()->visible()); |
| } |
| |
| // Verifies the note action view bounds when note action is available at lock |
| // contents view creation. |
| TEST_F(LockContentsViewUnitTest, NoteActionButtonBoundsInitiallyAvailable) { |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| |
| LockContentsView::TestApi test_api(contents); |
| |
| // Verify the note action button is visible and positioned in the top right |
| // corner of the screen. |
| EXPECT_TRUE(test_api.note_action()->visible()); |
| gfx::Rect widget_bounds = widget->GetWindowBoundsInScreen(); |
| gfx::Size note_action_size = test_api.note_action()->GetPreferredSize(); |
| EXPECT_EQ(gfx::Rect(widget_bounds.top_right() - |
| gfx::Vector2d(note_action_size.width(), 0), |
| note_action_size), |
| test_api.note_action()->GetBoundsInScreen()); |
| |
| // If the note action is disabled, the note action button should be hidden. |
| DataDispatcher()->SetLockScreenNoteState( |
| mojom::TrayActionState::kNotAvailable); |
| EXPECT_FALSE(test_api.note_action()->visible()); |
| } |
| |
| // Verifies the system info view bounds interaction with the note-taking button. |
| TEST_F(LockContentsViewUnitTest, SystemInfoViewBounds) { |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| gfx::Rect widget_bounds = widget->GetWindowBoundsInScreen(); |
| LockContentsView::TestApi test_api(contents); |
| // Verify that the system info view is hidden by default. |
| EXPECT_FALSE(test_api.system_info()->visible()); |
| |
| // Verify that the system info view becomes visible and it doesn't block the |
| // note action button. |
| DataDispatcher()->SetSystemInfo(true /*show_if_hidden*/, "Best version ever", |
| "Asset ID: 6666", "Bluetooth adapter"); |
| EXPECT_TRUE(test_api.system_info()->visible()); |
| EXPECT_TRUE(test_api.note_action()->visible()); |
| gfx::Size note_action_size = test_api.note_action()->GetPreferredSize(); |
| EXPECT_GE(widget_bounds.right() - |
| test_api.system_info()->GetBoundsInScreen().right(), |
| note_action_size.width()); |
| |
| // Verify that if the note action is disabled, the system info view moves to |
| // the right to fill the empty space. |
| DataDispatcher()->SetLockScreenNoteState( |
| mojom::TrayActionState::kNotAvailable); |
| EXPECT_FALSE(test_api.note_action()->visible()); |
| EXPECT_LT(widget_bounds.right() - |
| test_api.system_info()->GetBoundsInScreen().right(), |
| note_action_size.width()); |
| } |
| |
| // Alt-V toggles display of system information. |
| TEST_F(LockContentsViewUnitTest, AltVShowsHiddenSystemInfo) { |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| LockContentsView::TestApi test_api(contents); |
| // Verify that the system info view is hidden by default. |
| EXPECT_FALSE(test_api.system_info()->visible()); |
| |
| // Verify that the system info view does not become visible when given data |
| // but show is false. |
| DataDispatcher()->SetSystemInfo(false /*show_if_hidden*/, "Best version ever", |
| "Asset ID: 6666", "Bluetooth adapter"); |
| EXPECT_FALSE(test_api.system_info()->visible()); |
| |
| // Alt-V shows hidden system info. |
| GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_V, ui::EF_ALT_DOWN); |
| EXPECT_TRUE(test_api.system_info()->visible()); |
| // System info is not empty, ie, it is actually being displayed. |
| EXPECT_FALSE(test_api.system_info()->bounds().IsEmpty()); |
| |
| // Alt-V again does nothing. |
| GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_V, ui::EF_ALT_DOWN); |
| EXPECT_TRUE(test_api.system_info()->visible()); |
| } |
| |
| // Updating existing system info and setting show_if_hidden=true later will |
| // reveal hidden system info. |
| TEST_F(LockContentsViewUnitTest, ShowRevealsHiddenSystemInfo) { |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| LockContentsView::TestApi test_api(contents); |
| |
| auto set_system_info = [&](bool show_if_hidden) { |
| DataDispatcher()->SetSystemInfo(show_if_hidden, "Best version ever", |
| "Asset ID: 6666", "Bluetooth adapter"); |
| }; |
| |
| // Start with hidden system info. |
| set_system_info(false); |
| EXPECT_FALSE(test_api.system_info()->visible()); |
| |
| // Update system info but request it be shown. |
| set_system_info(true); |
| EXPECT_TRUE(test_api.system_info()->visible()); |
| |
| // Trying to hide system info from mojom call doesn't do anything. |
| set_system_info(false); |
| EXPECT_TRUE(test_api.system_info()->visible()); |
| } |
| |
| // Verifies the easy unlock tooltip is automatically displayed when requested. |
| TEST_F(LockContentsViewUnitTest, EasyUnlockForceTooltipCreatesTooltipWidget) { |
| auto* lock = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| |
| SetUserCount(1); |
| SetWidget(CreateWidgetWithContent(lock)); |
| |
| LockContentsView::TestApi test_api(lock); |
| // Creating lock screen does not show tooltip bubble. |
| EXPECT_FALSE(test_api.tooltip_bubble()->IsVisible()); |
| |
| // Show an icon with |autoshow_tooltip| is false. Tooltip bubble is not |
| // activated. |
| auto icon = mojom::EasyUnlockIconOptions::New(); |
| icon->icon = mojom::EasyUnlockIconId::LOCKED; |
| icon->autoshow_tooltip = false; |
| DataDispatcher()->ShowEasyUnlockIcon(users()[0]->basic_user_info->account_id, |
| icon); |
| EXPECT_FALSE(test_api.tooltip_bubble()->IsVisible()); |
| |
| // Show icon with |autoshow_tooltip| set to true. Tooltip bubble is shown. |
| icon->autoshow_tooltip = true; |
| DataDispatcher()->ShowEasyUnlockIcon(users()[0]->basic_user_info->account_id, |
| icon); |
| EXPECT_TRUE(test_api.tooltip_bubble()->IsVisible()); |
| } |
| |
| // Verifies that easy unlock icon state persists when changing auth user. |
| TEST_F(LockContentsViewUnitTest, EasyUnlockIconUpdatedDuringUserSwap) { |
| // Build lock screen with two users. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(2); |
| SetWidget(CreateWidgetWithContent(contents)); |
| |
| LockContentsView::TestApi test_api(contents); |
| LoginBigUserView* primary = test_api.primary_big_view(); |
| LoginBigUserView* secondary = test_api.opt_secondary_big_view(); |
| |
| // Returns true if the easy unlock icon is displayed for |view|. |
| auto showing_easy_unlock_icon = [&](LoginBigUserView* view) { |
| if (!view->auth_user()) |
| return false; |
| |
| views::View* icon = |
| LoginPasswordView::TestApi( |
| LoginAuthUserView::TestApi(view->auth_user()).password_view()) |
| .easy_unlock_icon(); |
| return icon->visible(); |
| }; |
| |
| // Enables easy unlock icon for |view|. |
| auto enable_icon = [&](LoginBigUserView* view) { |
| auto icon = mojom::EasyUnlockIconOptions::New(); |
| icon->icon = mojom::EasyUnlockIconId::LOCKED; |
| DataDispatcher()->ShowEasyUnlockIcon( |
| view->GetCurrentUser()->basic_user_info->account_id, icon); |
| }; |
| |
| // Disables easy unlock icon for |view|. |
| auto disable_icon = [&](LoginBigUserView* view) { |
| auto icon = mojom::EasyUnlockIconOptions::New(); |
| icon->icon = mojom::EasyUnlockIconId::NONE; |
| DataDispatcher()->ShowEasyUnlockIcon( |
| view->GetCurrentUser()->basic_user_info->account_id, icon); |
| }; |
| |
| // Makes |view| the active auth view so it will can show auth methods. |
| auto make_active_auth_view = [&](LoginBigUserView* view) { |
| // Send event to swap users. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| LoginUserView* user_view = view->GetUserView(); |
| generator->MoveMouseTo(user_view->GetBoundsInScreen().CenterPoint()); |
| generator->ClickLeftButton(); |
| }; |
| |
| // NOTE: we cannot assert on non-active auth views because the easy unlock |
| // icon is lazily updated, ie, if we're not showing the view we will not |
| // update icon state. |
| |
| // No easy unlock icons are shown. |
| make_active_auth_view(primary); |
| EXPECT_FALSE(showing_easy_unlock_icon(primary)); |
| |
| // Activate icon for primary. |
| enable_icon(primary); |
| EXPECT_TRUE(showing_easy_unlock_icon(primary)); |
| |
| // Secondary does not have easy unlock enabled; swapping auth hides the icon. |
| make_active_auth_view(secondary); |
| EXPECT_FALSE(showing_easy_unlock_icon(secondary)); |
| |
| // Switching back enables the icon again. |
| make_active_auth_view(primary); |
| EXPECT_TRUE(showing_easy_unlock_icon(primary)); |
| |
| // Activate icon for secondary. Primary visiblity does not change. |
| enable_icon(secondary); |
| EXPECT_TRUE(showing_easy_unlock_icon(primary)); |
| |
| // Swap to secondary, icon still visible. |
| make_active_auth_view(secondary); |
| EXPECT_TRUE(showing_easy_unlock_icon(secondary)); |
| |
| // Deactivate secondary, icon hides. |
| disable_icon(secondary); |
| EXPECT_FALSE(showing_easy_unlock_icon(secondary)); |
| |
| // Deactivate primary, icon still hidden. |
| disable_icon(primary); |
| EXPECT_FALSE(showing_easy_unlock_icon(secondary)); |
| |
| // Enable primary, icon still hidden. |
| enable_icon(primary); |
| EXPECT_FALSE(showing_easy_unlock_icon(secondary)); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, ShowErrorBubbleOnAuthFailure) { |
| // Build lock screen with a single user. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| SetWidget(CreateWidgetWithContent(contents)); |
| |
| LockContentsView::TestApi test_api(contents); |
| |
| // Password submit runs mojo. |
| std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient(); |
| client->set_authenticate_user_callback_result(false); |
| EXPECT_CALL(*client, |
| AuthenticateUserWithPasswordOrPin_( |
| users()[0]->basic_user_info->account_id, _, false, _)); |
| |
| // Submit password. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->PressKey(ui::KeyboardCode::VKEY_A, 0); |
| generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(test_api.auth_error_bubble()->IsVisible()); |
| |
| // The error bubble is expected to close on a user action - e.g. if they start |
| // typing the password again. |
| generator->PressKey(ui::KeyboardCode::VKEY_B, 0); |
| EXPECT_FALSE(test_api.auth_error_bubble()->IsVisible()); |
| } |
| |
| // Gaia is never shown on lock, no mater how many times auth fails. |
| TEST_F(LockContentsViewUnitTest, GaiaNeverShownOnLockAfterFailedAuth) { |
| // Build lock screen with a single user. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| SetWidget(CreateWidgetWithContent(contents)); |
| |
| std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient(); |
| client->set_authenticate_user_callback_result(false); |
| |
| auto submit_password = [&]() { |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->PressKey(ui::KeyboardCode::VKEY_A, 0); |
| generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| base::RunLoop().RunUntilIdle(); |
| }; |
| |
| // ShowGaiaSignin is never triggered. |
| EXPECT_CALL(*client, ShowGaiaSignin(_, _)).Times(0); |
| for (int i = 0; i < LockContentsView::kLoginAttemptsBeforeGaiaDialog + 1; ++i) |
| submit_password(); |
| } |
| |
| // Gaia is shown in login on the 4th bad password attempt. |
| TEST_F(LockContentsViewUnitTest, ShowGaiaAuthAfterManyFailedLoginAttempts) { |
| // Build lock screen with a single user. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLogin, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| SetWidget(CreateWidgetWithContent(contents)); |
| |
| std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient(); |
| client->set_authenticate_user_callback_result(false); |
| |
| auto submit_password = [&]() { |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->PressKey(ui::KeyboardCode::VKEY_A, 0); |
| generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| base::RunLoop().RunUntilIdle(); |
| }; |
| |
| // The first n-1 attempts do not trigger ShowGaiaSignin. |
| EXPECT_CALL(*client, ShowGaiaSignin(_, _)).Times(0); |
| for (int i = 0; i < LockContentsView::kLoginAttemptsBeforeGaiaDialog - 1; ++i) |
| submit_password(); |
| Mock::VerifyAndClearExpectations(client.get()); |
| |
| // The final attempt triggers ShowGaiaSignin. |
| EXPECT_CALL(*client, |
| ShowGaiaSignin(true /*can_close*/, |
| base::Optional<AccountId>( |
| users()[0]->basic_user_info->account_id))) |
| .Times(1); |
| submit_password(); |
| Mock::VerifyAndClearExpectations(client.get()); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, ErrorBubbleOnUntrustedDetachableBase) { |
| auto fake_detachable_base_model = |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher()); |
| FakeLoginDetachableBaseModel* detachable_base_model = |
| fake_detachable_base_model.get(); |
| |
| // Build lock screen with 2 users. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), std::move(fake_detachable_base_model)); |
| SetUserCount(2); |
| |
| const AccountId& kFirstUserAccountId = |
| users()[0]->basic_user_info->account_id; |
| const AccountId& kSecondUserAccountId = |
| users()[1]->basic_user_info->account_id; |
| |
| // Initialize the detachable base state, so the user 1 has previously used |
| // detachable base. |
| detachable_base_model->InitLastUsedBases({{kFirstUserAccountId, "1234"}}); |
| detachable_base_model->SetPairingStatus( |
| DetachableBasePairingStatus::kAuthenticated, "1234"); |
| SetWidget(CreateWidgetWithContent(contents)); |
| |
| LockContentsView::TestApi test_api(contents); |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| |
| EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible()); |
| |
| // Change detachable base to a base different than the one previously used by |
| // the user - verify that a detachable base error bubble is shown. |
| detachable_base_model->SetPairingStatus( |
| DetachableBasePairingStatus::kAuthenticated, "5678"); |
| EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible()); |
| |
| // Verify that the bubble is not hidden if the user starts typing. |
| generator->PressKey(ui::KeyboardCode::VKEY_B, 0); |
| EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible()); |
| |
| // Switching to the user that doesn't have previously used detachable base |
| // (and should thus not be warned about the detachable base missmatch) should |
| // hide the login bubble. |
| LoginAuthUserView::TestApi secondary_test_api( |
| test_api.opt_secondary_big_view()->auth_user()); |
| generator->MoveMouseTo( |
| secondary_test_api.user_view()->GetBoundsInScreen().CenterPoint()); |
| generator->ClickLeftButton(); |
| |
| EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible()); |
| |
| // The error should be shown again when switching back to the primary user. |
| LoginAuthUserView::TestApi primary_test_api( |
| test_api.primary_big_view()->auth_user()); |
| generator->MoveMouseTo( |
| primary_test_api.user_view()->GetBoundsInScreen().CenterPoint()); |
| generator->ClickLeftButton(); |
| |
| EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible()); |
| EXPECT_FALSE(primary_test_api.password_view()->HasFocus()); |
| |
| EXPECT_EQ("1234", |
| detachable_base_model->GetLastUsedBase(kFirstUserAccountId)); |
| EXPECT_EQ("", detachable_base_model->GetLastUsedBase(kSecondUserAccountId)); |
| |
| // The current detachable base should be set as the last used one by the user |
| // after they authenticate - test for this. |
| std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient(); |
| client->set_authenticate_user_callback_result(true); |
| EXPECT_CALL(*client, AuthenticateUserWithPasswordOrPin_(kFirstUserAccountId, |
| _, false, _)); |
| |
| // Submit password. |
| primary_test_api.password_view()->RequestFocus(); |
| generator->PressKey(ui::KeyboardCode::VKEY_A, 0); |
| generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ("5678", |
| detachable_base_model->GetLastUsedBase(kFirstUserAccountId)); |
| EXPECT_EQ("", detachable_base_model->GetLastUsedBase(kSecondUserAccountId)); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, ErrorBubbleForUnauthenticatedDetachableBase) { |
| auto fake_detachable_base_model = |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher()); |
| FakeLoginDetachableBaseModel* detachable_base_model = |
| fake_detachable_base_model.get(); |
| |
| // Build lock screen with 2 users. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), std::move(fake_detachable_base_model)); |
| SetUserCount(2); |
| |
| const AccountId& kFirstUserAccountId = |
| users()[0]->basic_user_info->account_id; |
| const AccountId& kSecondUserAccountId = |
| users()[1]->basic_user_info->account_id; |
| |
| detachable_base_model->InitLastUsedBases({{kSecondUserAccountId, "5678"}}); |
| |
| SetWidget(CreateWidgetWithContent(contents)); |
| |
| LockContentsView::TestApi test_api(contents); |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| |
| EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible()); |
| |
| // Show notification if unauthenticated base is attached. |
| detachable_base_model->SetPairingStatus( |
| DetachableBasePairingStatus::kNotAuthenticated, ""); |
| EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible()); |
| |
| // Verify that the bubble is not hidden if the user starts typing. |
| generator->PressKey(ui::KeyboardCode::VKEY_B, 0); |
| EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible()); |
| |
| // Switching to another user should not hide the error bubble. |
| LoginAuthUserView::TestApi secondary_test_api( |
| test_api.opt_secondary_big_view()->auth_user()); |
| generator->MoveMouseTo( |
| secondary_test_api.user_view()->GetBoundsInScreen().CenterPoint()); |
| generator->ClickLeftButton(); |
| |
| EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible()); |
| EXPECT_FALSE(secondary_test_api.password_view()->HasFocus()); |
| |
| // The last trusted detachable used by the user should not be overriden by |
| // user authentication. |
| std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient(); |
| client->set_authenticate_user_callback_result(true); |
| EXPECT_CALL(*client, AuthenticateUserWithPasswordOrPin_(kSecondUserAccountId, |
| _, false, _)); |
| |
| // Submit password. |
| secondary_test_api.password_view()->RequestFocus(); |
| generator->PressKey(ui::KeyboardCode::VKEY_A, 0); |
| generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ("", detachable_base_model->GetLastUsedBase(kFirstUserAccountId)); |
| EXPECT_EQ("5678", |
| detachable_base_model->GetLastUsedBase(kSecondUserAccountId)); |
| } |
| TEST_F(LockContentsViewUnitTest, |
| RemovingAttachedBaseHidesDetachableBaseNotification) { |
| auto fake_detachable_base_model = |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher()); |
| FakeLoginDetachableBaseModel* detachable_base_model = |
| fake_detachable_base_model.get(); |
| |
| // Build lock screen with 2 users. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), std::move(fake_detachable_base_model)); |
| SetUserCount(1); |
| |
| const AccountId& kUserAccountId = users()[0]->basic_user_info->account_id; |
| |
| // Initialize the detachable base state, as if the user has previously used |
| // detachable base. |
| detachable_base_model->InitLastUsedBases({{kUserAccountId, "1234"}}); |
| detachable_base_model->SetPairingStatus( |
| DetachableBasePairingStatus::kAuthenticated, "1234"); |
| |
| SetWidget(CreateWidgetWithContent(contents)); |
| |
| LockContentsView::TestApi test_api(contents); |
| |
| // Change detachable base to a base different than the one previously used by |
| // the user - verify that a detachable base error bubble is shown. |
| detachable_base_model->SetPairingStatus( |
| DetachableBasePairingStatus::kAuthenticated, "5678"); |
| EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible()); |
| |
| // The notification should be hidden if the base gets detached. |
| detachable_base_model->SetPairingStatus(DetachableBasePairingStatus::kNone, |
| ""); |
| EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible()); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, DetachableBaseErrorClearsAuthError) { |
| auto fake_detachable_base_model = |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher()); |
| FakeLoginDetachableBaseModel* detachable_base_model = |
| fake_detachable_base_model.get(); |
| |
| // Build lock screen with a single user. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), std::move(fake_detachable_base_model)); |
| SetUserCount(1); |
| |
| const AccountId& kUserAccountId = users()[0]->basic_user_info->account_id; |
| |
| // Initialize the detachable base state, as if the user has previously used |
| // detachable base. |
| detachable_base_model->InitLastUsedBases({{kUserAccountId, "1234"}}); |
| detachable_base_model->SetPairingStatus( |
| DetachableBasePairingStatus::kAuthenticated, "1234"); |
| |
| SetWidget(CreateWidgetWithContent(contents)); |
| |
| LockContentsView::TestApi test_api(contents); |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| |
| EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible()); |
| |
| // Attempt and fail user auth - an auth error is expected to be shown. |
| std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient(); |
| client->set_authenticate_user_callback_result(false); |
| EXPECT_CALL(*client, |
| AuthenticateUserWithPasswordOrPin_(kUserAccountId, _, false, _)); |
| |
| // Submit password. |
| generator->PressKey(ui::KeyboardCode::VKEY_A, 0); |
| generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(test_api.auth_error_bubble()->IsVisible()); |
| EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible()); |
| |
| // Change detachable base to a base different than the one previously used by |
| // the user - verify that a detachable base error bubble is shown, and the |
| // auth error is hidden. |
| detachable_base_model->SetPairingStatus( |
| DetachableBasePairingStatus::kAuthenticated, "5678"); |
| |
| EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible()); |
| EXPECT_FALSE(test_api.auth_error_bubble()->IsVisible()); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, AuthErrorDoesNotRemoveDetachableBaseError) { |
| auto fake_detachable_base_model = |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher()); |
| FakeLoginDetachableBaseModel* detachable_base_model = |
| fake_detachable_base_model.get(); |
| |
| // Build lock screen with a single user. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), std::move(fake_detachable_base_model)); |
| SetUserCount(1); |
| |
| const AccountId& kUserAccountId = users()[0]->basic_user_info->account_id; |
| |
| // Initialize the detachable base state, as if the user has previously used |
| // detachable base. |
| detachable_base_model->InitLastUsedBases({{kUserAccountId, "1234"}}); |
| SetWidget(CreateWidgetWithContent(contents)); |
| |
| detachable_base_model->SetPairingStatus( |
| DetachableBasePairingStatus::kAuthenticated, "1234"); |
| |
| LockContentsView::TestApi test_api(contents); |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| |
| EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible()); |
| |
| // Change detachable base to a base different than the one previously used by |
| // the user - verify that a detachable base error bubble is shown, and the |
| // auth error is hidden. |
| detachable_base_model->SetPairingStatus( |
| DetachableBasePairingStatus::kAuthenticated, "5678"); |
| |
| EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible()); |
| |
| // Attempt and fail user auth - an auth error is expected to be shown. |
| // Detachable base error should not be hidden. |
| std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient(); |
| client->set_authenticate_user_callback_result(false); |
| EXPECT_CALL(*client, |
| AuthenticateUserWithPasswordOrPin_(kUserAccountId, _, false, _)); |
| |
| // Submit password. |
| LoginAuthUserView::TestApi(test_api.primary_big_view()->auth_user()) |
| .password_view() |
| ->RequestFocus(); |
| generator->PressKey(ui::KeyboardCode::VKEY_A, 0); |
| generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(test_api.auth_error_bubble()->IsVisible()); |
| EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible()); |
| |
| // User action, like pressing a key should close the auth error bubble, but |
| // not the detachable base error bubble. |
| generator->PressKey(ui::KeyboardCode::VKEY_A, 0); |
| |
| EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible()); |
| EXPECT_FALSE(test_api.auth_error_bubble()->IsVisible()); |
| } |
| |
| TEST_F(LockContentsViewKeyboardUnitTest, SwitchPinAndVirtualKeyboard) { |
| ASSERT_NO_FATAL_FAILURE(ShowLockScreen()); |
| LockContentsView* contents = |
| LockScreen::TestApi(LockScreen::Get()).contents_view(); |
| ASSERT_NE(nullptr, contents); |
| |
| // Add user who can use pin authentication. |
| const std::string email = "user@domain.com"; |
| AddUserByEmail(email); |
| contents->OnPinEnabledForUserChanged(AccountId::FromUserEmail(email), true); |
| LoginBigUserView* big_view = |
| LockContentsView::TestApi(contents).primary_big_view(); |
| ASSERT_NE(nullptr, big_view); |
| ASSERT_NE(nullptr, big_view->auth_user()); |
| |
| // Pin keyboard should only be visible when there is no virtual keyboard |
| // shown. |
| LoginPinView* pin_view = |
| LoginAuthUserView::TestApi(big_view->auth_user()).pin_view(); |
| EXPECT_TRUE(pin_view->visible()); |
| |
| ASSERT_NO_FATAL_FAILURE(ShowKeyboard()); |
| EXPECT_FALSE(pin_view->visible()); |
| |
| ASSERT_NO_FATAL_FAILURE(HideKeyboard()); |
| EXPECT_TRUE(pin_view->visible()); |
| } |
| |
| // Verifies that swapping auth users while the virtual keyboard is active |
| // focuses the other user's password field. |
| TEST_F(LockContentsViewKeyboardUnitTest, SwitchUserWhileKeyboardShown) { |
| ASSERT_NO_FATAL_FAILURE(ShowLoginScreen()); |
| LockContentsView* contents = |
| LockScreen::TestApi(LockScreen::Get()).contents_view(); |
| ASSERT_NE(nullptr, contents); |
| |
| SetUserCount(2); |
| |
| LoginAuthUserView::TestApi primary_user( |
| LockContentsView::TestApi(contents).primary_big_view()->auth_user()); |
| LoginAuthUserView::TestApi secondary_user(LockContentsView::TestApi(contents) |
| .opt_secondary_big_view() |
| ->auth_user()); |
| |
| ASSERT_NO_FATAL_FAILURE(ShowKeyboard()); |
| EXPECT_TRUE(LoginPasswordView::TestApi(primary_user.password_view()) |
| .textfield() |
| ->HasFocus()); |
| |
| // Simulate a button click on the secondary UserView. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->MoveMouseTo( |
| secondary_user.user_view()->GetBoundsInScreen().CenterPoint()); |
| generator->ClickLeftButton(); |
| |
| EXPECT_TRUE(LoginPasswordView::TestApi(secondary_user.password_view()) |
| .textfield() |
| ->HasFocus()); |
| EXPECT_FALSE(LoginPasswordView::TestApi(primary_user.password_view()) |
| .textfield() |
| ->HasFocus()); |
| |
| // Simulate a button click on the primary UserView. |
| generator->MoveMouseTo( |
| primary_user.user_view()->GetBoundsInScreen().CenterPoint()); |
| generator->ClickLeftButton(); |
| |
| EXPECT_TRUE(LoginPasswordView::TestApi(primary_user.password_view()) |
| .textfield() |
| ->HasFocus()); |
| EXPECT_FALSE(LoginPasswordView::TestApi(secondary_user.password_view()) |
| .textfield() |
| ->HasFocus()); |
| } |
| |
| TEST_F(LockContentsViewKeyboardUnitTest, PinSubmitWithVirtualKeyboardShown) { |
| ASSERT_NO_FATAL_FAILURE(ShowLockScreen()); |
| LockContentsView* contents = |
| LockScreen::TestApi(LockScreen::Get()).contents_view(); |
| |
| // Add user who can use pin authentication. |
| const std::string email = "user@domain.com"; |
| AddUserByEmail(email); |
| contents->OnPinEnabledForUserChanged(AccountId::FromUserEmail(email), true); |
| LoginBigUserView* big_view = |
| LockContentsView::TestApi(contents).primary_big_view(); |
| |
| // Require that AuthenticateUser is called with authenticated_by_pin set to |
| // true. |
| auto client = BindMockLoginScreenClient(); |
| EXPECT_CALL(*client, AuthenticateUserWithPasswordOrPin_( |
| _, "1111", true /*authenticated_by_pin*/, _)); |
| |
| // Hide the PIN keyboard. |
| LoginPinView* pin_view = |
| LoginAuthUserView::TestApi(big_view->auth_user()).pin_view(); |
| EXPECT_TRUE(pin_view->visible()); |
| ASSERT_NO_FATAL_FAILURE(ShowKeyboard()); |
| EXPECT_FALSE(pin_view->visible()); |
| |
| // Submit a password. |
| LoginAuthUserView::TestApi(big_view->auth_user()) |
| .password_view() |
| ->RequestFocus(); |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->PressKey(ui::KeyboardCode::VKEY_1, 0); |
| generator->PressKey(ui::KeyboardCode::VKEY_1, 0); |
| generator->PressKey(ui::KeyboardCode::VKEY_1, 0); |
| generator->PressKey(ui::KeyboardCode::VKEY_1, 0); |
| generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Verify that swapping works in two user layout between one regular auth user |
| // and one public account user. |
| TEST_F(LockContentsViewUnitTest, SwapAuthAndPublicAccountUserInTwoUserLayout) { |
| // Build lock screen with two users: one public account user and one regular |
| // user. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| AddPublicAccountUsers(1); |
| AddUsers(1); |
| |
| LockContentsView::TestApi test_api(contents); |
| |
| // Capture user info to validate it did not change during the swap. |
| AccountId primary_user = test_api.primary_big_view() |
| ->GetCurrentUser() |
| ->basic_user_info->account_id; |
| AccountId secondary_user = test_api.opt_secondary_big_view() |
| ->GetCurrentUser() |
| ->basic_user_info->account_id; |
| EXPECT_NE(primary_user, secondary_user); |
| |
| // Primary user starts with auth. Secondary user does not have any auth. |
| EXPECT_TRUE(test_api.primary_big_view()->IsAuthEnabled()); |
| EXPECT_FALSE(test_api.opt_secondary_big_view()->IsAuthEnabled()); |
| |
| // Verify the LoginBigUserView has built the child view correctly. |
| ASSERT_TRUE(test_api.primary_big_view()->public_account()); |
| ASSERT_FALSE(test_api.primary_big_view()->auth_user()); |
| ASSERT_FALSE(test_api.opt_secondary_big_view()->public_account()); |
| ASSERT_TRUE(test_api.opt_secondary_big_view()->auth_user()); |
| |
| // Send event to swap users. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| LoginAuthUserView::TestApi secondary_test_api( |
| test_api.opt_secondary_big_view()->auth_user()); |
| generator->MoveMouseTo( |
| secondary_test_api.user_view()->GetBoundsInScreen().CenterPoint()); |
| generator->ClickLeftButton(); |
| |
| // User info is not swapped. |
| EXPECT_EQ(primary_user, test_api.primary_big_view() |
| ->GetCurrentUser() |
| ->basic_user_info->account_id); |
| EXPECT_EQ(secondary_user, test_api.opt_secondary_big_view() |
| ->GetCurrentUser() |
| ->basic_user_info->account_id); |
| |
| // Child view of LoginBigUserView stays the same. |
| ASSERT_TRUE(test_api.primary_big_view()->public_account()); |
| ASSERT_FALSE(test_api.primary_big_view()->auth_user()); |
| ASSERT_FALSE(test_api.opt_secondary_big_view()->public_account()); |
| ASSERT_TRUE(test_api.opt_secondary_big_view()->auth_user()); |
| |
| // Active auth (ie, which user is showing password) is swapped. |
| EXPECT_FALSE(test_api.primary_big_view()->IsAuthEnabled()); |
| EXPECT_TRUE(test_api.opt_secondary_big_view()->IsAuthEnabled()); |
| } |
| |
| // Ensures that when swapping from a user list, the entire user info is swapped |
| // and the primary big user will rebuild its child view when necessary. |
| TEST_F(LockContentsViewUnitTest, SwapUserListToPrimaryBigUser) { |
| // Build lock screen with 4 users: two public account users and two regular |
| // users. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| AddPublicAccountUsers(2); |
| AddUsers(2); |
| |
| LockContentsView::TestApi contents_test_api(contents); |
| ScrollableUsersListView::TestApi users_list(contents_test_api.users_list()); |
| EXPECT_EQ(users().size() - 1, users_list.user_views().size()); |
| |
| LoginBigUserView* primary_big_view = contents_test_api.primary_big_view(); |
| |
| // Verify that primary_big_view is public account user. |
| ASSERT_TRUE(primary_big_view->public_account()); |
| ASSERT_FALSE(primary_big_view->auth_user()); |
| |
| const LoginUserView* user_view0 = users_list.user_views()[0]; |
| const LoginUserView* user_view1 = users_list.user_views()[1]; |
| const LoginUserView* user_view2 = users_list.user_views()[2]; |
| |
| // Clicks on |view| to make it swap with the primary big user. |
| auto click_view = [&](const LoginUserView* view) { |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->MoveMouseTo(view->GetBoundsInScreen().CenterPoint()); |
| generator->ClickLeftButton(); |
| }; |
| |
| auto is_public_account = [](const LoginUserView* view) -> bool { |
| return view->current_user()->basic_user_info->type == |
| user_manager::USER_TYPE_PUBLIC_ACCOUNT; |
| }; |
| |
| // Case 1: Swap user_view0 (public account user) with primary big user (public |
| // account user). |
| EXPECT_TRUE(is_public_account(user_view0)); |
| AccountId primary_id = |
| primary_big_view->GetCurrentUser()->basic_user_info->account_id; |
| AccountId list_user_id = |
| user_view0->current_user()->basic_user_info->account_id; |
| EXPECT_NE(primary_id, list_user_id); |
| |
| // Send event to swap users. |
| click_view(user_view0); |
| |
| // User info is swapped. |
| EXPECT_EQ(list_user_id, |
| primary_big_view->GetCurrentUser()->basic_user_info->account_id); |
| EXPECT_EQ(primary_id, |
| user_view0->current_user()->basic_user_info->account_id); |
| |
| // Child view of primary big user stays the same. |
| ASSERT_TRUE(primary_big_view->public_account()); |
| ASSERT_FALSE(primary_big_view->auth_user()); |
| // user_view0 is still public account user. |
| EXPECT_TRUE(is_public_account(user_view0)); |
| |
| // Case 2: Swap user_view1 (auth user) with primary big user (public account |
| // user). |
| EXPECT_FALSE(is_public_account(user_view1)); |
| primary_id = primary_big_view->GetCurrentUser()->basic_user_info->account_id; |
| list_user_id = user_view1->current_user()->basic_user_info->account_id; |
| EXPECT_NE(primary_id, list_user_id); |
| |
| // Send event to swap users. |
| click_view(user_view1); |
| |
| // User info is swapped. |
| EXPECT_EQ(list_user_id, |
| primary_big_view->GetCurrentUser()->basic_user_info->account_id); |
| EXPECT_EQ(primary_id, |
| user_view1->current_user()->basic_user_info->account_id); |
| |
| // Primary big user becomes auth user and its child view is rebuilt. |
| ASSERT_FALSE(primary_big_view->public_account()); |
| ASSERT_TRUE(primary_big_view->auth_user()); |
| // user_view1 becomes public account user. |
| EXPECT_TRUE(is_public_account(user_view1)); |
| |
| // Case 3: Swap user_view2 (auth user) with primary big user (auth user). |
| EXPECT_FALSE(is_public_account(user_view2)); |
| primary_id = primary_big_view->GetCurrentUser()->basic_user_info->account_id; |
| list_user_id = user_view2->current_user()->basic_user_info->account_id; |
| EXPECT_NE(primary_id, list_user_id); |
| |
| // Send event to swap users. |
| click_view(user_view2); |
| |
| // User info is swapped. |
| EXPECT_EQ(list_user_id, |
| primary_big_view->GetCurrentUser()->basic_user_info->account_id); |
| EXPECT_EQ(primary_id, |
| user_view2->current_user()->basic_user_info->account_id); |
| |
| // Child view of primary big user stays the same. |
| ASSERT_FALSE(primary_big_view->public_account()); |
| ASSERT_TRUE(primary_big_view->auth_user()); |
| // user_view2 is still auth user. |
| EXPECT_FALSE(is_public_account(user_view2)); |
| |
| // Case 4: Swap user_view0 (public account user) with with primary big user |
| // (auth user). |
| EXPECT_TRUE(is_public_account(user_view0)); |
| primary_id = primary_big_view->GetCurrentUser()->basic_user_info->account_id; |
| list_user_id = user_view0->current_user()->basic_user_info->account_id; |
| EXPECT_NE(primary_id, list_user_id); |
| |
| // Send event to swap users. |
| click_view(user_view0); |
| |
| // User info is swapped. |
| EXPECT_EQ(list_user_id, |
| primary_big_view->GetCurrentUser()->basic_user_info->account_id); |
| EXPECT_EQ(primary_id, |
| user_view0->current_user()->basic_user_info->account_id); |
| |
| // Primary big user becomes public account user and its child view is rebuilt. |
| ASSERT_TRUE(primary_big_view->public_account()); |
| ASSERT_FALSE(primary_big_view->auth_user()); |
| // user_view0 becomes auth user. |
| EXPECT_FALSE(is_public_account(user_view0)); |
| } |
| |
| // Validates that swapping between two auth users also changes password focus. |
| TEST_F(LockContentsViewUnitTest, AuthUserSwapFocusesPassword) { |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| AddUsers(2); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| |
| auto do_test = [&](AuthTarget auth_target) { |
| SCOPED_TRACE(AuthTargetToString(auth_target)); |
| |
| LoginAuthUserView::TestApi test_api = |
| MakeLoginAuthTestApi(contents, auth_target); |
| LoginPasswordView* password = test_api.password_view(); |
| |
| // Focus user, validate password did not get focused, then activate the |
| // user, which shows and focuses the password. |
| test_api.user_view()->RequestFocus(); |
| EXPECT_FALSE(HasFocusInAnyChildView(password)); |
| GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| EXPECT_TRUE(HasFocusInAnyChildView(password)); |
| }; |
| |
| // do_test requires that the auth target is not active, so do secondary before |
| // primary. |
| do_test(AuthTarget::kSecondary); |
| do_test(AuthTarget::kPrimary); |
| } |
| |
| // Validates that tapping on an auth user will refocus the password. |
| TEST_F(LockContentsViewUnitTest, TapOnAuthUserFocusesPassword) { |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| |
| auto do_test = [&](AuthTarget auth_target) { |
| SCOPED_TRACE(testing::Message() |
| << "users=" << users().size() |
| << ", auth_target=" << AuthTargetToString(auth_target)); |
| |
| LoginAuthUserView::TestApi auth_user_test_api = |
| MakeLoginAuthTestApi(contents, auth_target); |
| LoginPasswordView* password = auth_user_test_api.password_view(); |
| |
| // Activate |auth_target|. |
| auth_user_test_api.user_view()->RequestFocus(); |
| GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| // Move focus off of |auth_target|'s password. |
| ASSERT_TRUE(HasFocusInAnyChildView(password)); |
| GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_TAB, 0); |
| EXPECT_FALSE(HasFocusInAnyChildView(password)); |
| |
| // Click the user view, verify the password was focused. |
| GetEventGenerator()->MoveMouseTo( |
| auth_user_test_api.user_view()->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| EXPECT_TRUE(HasFocusInAnyChildView(password)); |
| }; |
| |
| SetUserCount(1); |
| do_test(AuthTarget::kPrimary); |
| |
| SetUserCount(2); |
| do_test(AuthTarget::kPrimary); |
| do_test(AuthTarget::kSecondary); |
| |
| SetUserCount(3); |
| do_test(AuthTarget::kPrimary); |
| } |
| |
| // Validates that swapping between users in user lists maintains password focus. |
| TEST_F(LockContentsViewUnitTest, UserListUserSwapFocusesPassword) { |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| LockContentsView::TestApi contents_test_api(contents); |
| AddUsers(3); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| |
| LoginPasswordView* password_view = |
| LoginAuthUserView::TestApi( |
| contents_test_api.primary_big_view()->auth_user()) |
| .password_view(); |
| LoginUserView* user_view = contents_test_api.users_list()->user_view_at(0); |
| |
| // Focus the user view, verify the password does not have focus, activate the |
| // user view, verify the password now has focus. |
| user_view->RequestFocus(); |
| EXPECT_FALSE(HasFocusInAnyChildView(password_view)); |
| GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| EXPECT_TRUE(HasFocusInAnyChildView(password_view)); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, BadDetachableBaseUnfocusesPasswordView) { |
| auto fake_detachable_base_model = |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher()); |
| FakeLoginDetachableBaseModel* detachable_base_model = |
| fake_detachable_base_model.get(); |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), std::move(fake_detachable_base_model)); |
| SetUserCount(3); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| |
| LockContentsView::TestApi test_api(contents); |
| LoginBigUserView* primary_view = test_api.primary_big_view(); |
| LoginPasswordView* primary_password_view = |
| LoginAuthUserView::TestApi(primary_view->auth_user()).password_view(); |
| |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(primary_password_view)); |
| |
| detachable_base_model->SetPairingStatus( |
| DetachableBasePairingStatus::kNotAuthenticated, ""); |
| EXPECT_FALSE( |
| login_views_utils::HasFocusInAnyChildView(primary_password_view)); |
| |
| // Swapping to another user should still not focus password view. |
| LoginUserView* first_list_user = test_api.users_list()->user_view_at(0); |
| first_list_user->RequestFocus(); |
| GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| EXPECT_FALSE( |
| login_views_utils::HasFocusInAnyChildView(primary_password_view)); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, ExpandedPublicSessionView) { |
| // Build lock screen with 3 users: one public account user and two regular |
| // users. |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| LockContentsView::TestApi lock_contents(contents); |
| std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); |
| AddPublicAccountUsers(1); |
| AddUsers(2); |
| |
| views::View* main_view = lock_contents.main_view(); |
| LoginExpandedPublicAccountView* expanded_view = lock_contents.expanded_view(); |
| EXPECT_TRUE(main_view->visible()); |
| EXPECT_FALSE(expanded_view->visible()); |
| |
| LoginBigUserView* primary_big_view = lock_contents.primary_big_view(); |
| AccountId primary_id = |
| primary_big_view->GetCurrentUser()->basic_user_info->account_id; |
| |
| // Open the expanded public session view. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| |
| EXPECT_FALSE(main_view->visible()); |
| EXPECT_TRUE(expanded_view->visible()); |
| EXPECT_EQ(expanded_view->current_user()->basic_user_info->account_id, |
| primary_id); |
| |
| // Expect LanuchPublicSession mojo call when the submit button is clicked. |
| std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient(); |
| EXPECT_CALL(*client, LaunchPublicSession(primary_id, _, _)); |
| |
| // Click on the submit button. |
| LoginExpandedPublicAccountView::TestApi expanded_view_api(expanded_view); |
| generator->MoveMouseTo( |
| expanded_view_api.submit_button()->GetBoundsInScreen().CenterPoint()); |
| generator->ClickLeftButton(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, OnAuthEnabledForUserChanged) { |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| SetWidget(CreateWidgetWithContent(contents)); |
| |
| const AccountId& kFirstUserAccountId = |
| users()[0]->basic_user_info->account_id; |
| LockContentsView::TestApi contents_test_api(contents); |
| LoginAuthUserView::TestApi auth_test_api( |
| contents_test_api.primary_big_view()->auth_user()); |
| LoginPasswordView* password_view = auth_test_api.password_view(); |
| LoginPinView* pin_view = auth_test_api.pin_view(); |
| views::View* disabled_auth_message = auth_test_api.disabled_auth_message(); |
| |
| // The password field is shown by default. |
| EXPECT_TRUE(password_view->visible()); |
| EXPECT_FALSE(pin_view->visible()); |
| EXPECT_FALSE(disabled_auth_message->visible()); |
| // Setting auth disabled will hide the password field and show the message. |
| DataDispatcher()->SetAuthEnabledForUser( |
| kFirstUserAccountId, false, |
| base::Time::Now() + base::TimeDelta::FromHours(8)); |
| EXPECT_FALSE(password_view->visible()); |
| EXPECT_FALSE(pin_view->visible()); |
| EXPECT_TRUE(disabled_auth_message->visible()); |
| // Setting auth enabled will hide the message and show the password field. |
| DataDispatcher()->SetAuthEnabledForUser(kFirstUserAccountId, true, |
| base::nullopt); |
| EXPECT_TRUE(password_view->visible()); |
| EXPECT_FALSE(pin_view->visible()); |
| EXPECT_FALSE(disabled_auth_message->visible()); |
| |
| // Set auth disabled again. |
| DataDispatcher()->SetAuthEnabledForUser( |
| kFirstUserAccountId, false, |
| base::Time::Now() + base::TimeDelta::FromHours(8)); |
| EXPECT_FALSE(password_view->visible()); |
| EXPECT_FALSE(pin_view->visible()); |
| EXPECT_TRUE(disabled_auth_message->visible()); |
| // Enable PIN. There's no UI change because auth is currently disabled. |
| DataDispatcher()->SetPinEnabledForUser(kFirstUserAccountId, true); |
| EXPECT_FALSE(password_view->visible()); |
| EXPECT_FALSE(pin_view->visible()); |
| EXPECT_TRUE(disabled_auth_message->visible()); |
| // Set auth enabled again. Both password field and PIN keyboard are shown. |
| DataDispatcher()->SetAuthEnabledForUser(kFirstUserAccountId, true, |
| base::nullopt); |
| EXPECT_TRUE(password_view->visible()); |
| EXPECT_TRUE(pin_view->visible()); |
| EXPECT_FALSE(disabled_auth_message->visible()); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, |
| ToggleNoteActionVisibilityOnAuthEnabledChanged) { |
| auto* tray_action = Shell::Get()->tray_action(); |
| TestTrayActionClient action_client; |
| tray_action->SetClient(action_client.CreateInterfacePtrAndBind(), |
| mojom::TrayActionState::kAvailable); |
| auto* contents = new LockContentsView( |
| Shell::Get()->tray_action()->GetLockScreenNoteState(), |
| LockScreen::ScreenType::kLock, DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| SetWidget(CreateWidgetWithContent(contents)); |
| |
| const AccountId& kFirstUserAccountId = |
| users()[0]->basic_user_info->account_id; |
| LockContentsView::TestApi contents_test_api(contents); |
| views::View* note_action_button = contents_test_api.note_action(); |
| |
| EXPECT_TRUE(note_action_button->visible()); |
| // Setting auth disabled hides the note action button. |
| DataDispatcher()->SetAuthEnabledForUser( |
| kFirstUserAccountId, false, |
| base::Time::Now() + base::TimeDelta::FromHours(8)); |
| EXPECT_FALSE(note_action_button->visible()); |
| // Setting auth enabled shows the note action button. |
| DataDispatcher()->SetAuthEnabledForUser(kFirstUserAccountId, true, |
| base::nullopt); |
| EXPECT_TRUE(note_action_button->visible()); |
| |
| // Set auth disabled again. |
| DataDispatcher()->SetAuthEnabledForUser( |
| kFirstUserAccountId, false, |
| base::Time::Now() + base::TimeDelta::FromHours(8)); |
| EXPECT_FALSE(note_action_button->visible()); |
| // Set the lock screen note state to |kNotAvailable| while the note action |
| // button is hidden. |
| tray_action->UpdateLockScreenNoteState(mojom::TrayActionState::kNotAvailable); |
| DataDispatcher()->SetAuthEnabledForUser(kFirstUserAccountId, true, |
| base::nullopt); |
| // The note action button remains hidden after setting auth enabled. |
| EXPECT_FALSE(note_action_button->visible()); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, DisabledAuthMessageFocusBehavior) { |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| SetWidget(CreateWidgetWithContent(contents)); |
| |
| const AccountId& kFirstUserAccountId = |
| users()[0]->basic_user_info->account_id; |
| LockContentsView::TestApi contents_test_api(contents); |
| LoginAuthUserView::TestApi auth_test_api( |
| contents_test_api.primary_big_view()->auth_user()); |
| views::View* disabled_auth_message = auth_test_api.disabled_auth_message(); |
| LoginUserView* user_view = auth_test_api.user_view(); |
| |
| // The message is visible after disabling auth and it receives initial focus. |
| DataDispatcher()->SetAuthEnabledForUser( |
| kFirstUserAccountId, false, |
| base::Time::Now() + base::TimeDelta::FromHours(8)); |
| EXPECT_TRUE(disabled_auth_message->visible()); |
| EXPECT_TRUE(HasFocusInAnyChildView(disabled_auth_message)); |
| // Tabbing from the message will move focus to the user view. |
| ASSERT_TRUE(TabThroughView(GetEventGenerator(), disabled_auth_message, |
| false /*reverse*/)); |
| EXPECT_TRUE(HasFocusInAnyChildView(user_view)); |
| // Shift-tabbing from the user view will move focus back to the message. |
| ASSERT_TRUE(TabThroughView(GetEventGenerator(), user_view, true /*reverse*/)); |
| EXPECT_TRUE(HasFocusInAnyChildView(disabled_auth_message)); |
| // Additional shift-tabbing will eventually move focus to the status area. |
| ASSERT_TRUE(TabThroughView(GetEventGenerator(), disabled_auth_message, |
| true /*reverse*/)); |
| views::View* status_area = |
| RootWindowController::ForWindow(contents->GetWidget()->GetNativeWindow()) |
| ->GetStatusAreaWidget() |
| ->GetContentsView(); |
| EXPECT_TRUE(HasFocusInAnyChildView(status_area)); |
| } |
| |
| class LockContentsViewPowerManagerUnitTest : public LockContentsViewUnitTest { |
| public: |
| void SetUp() override { |
| chromeos::DBusThreadManager::GetSetterForTesting()->SetPowerManagerClient( |
| std::make_unique<chromeos::FakePowerManagerClient>()); |
| |
| LockContentsViewUnitTest::SetUp(); |
| } |
| }; |
| |
| // Ensures that a PowerManagerClient::Observer is added on LockScreen::Show() |
| // and removed on LockScreen::Destroy(). |
| TEST_F(LockContentsViewPowerManagerUnitTest, |
| LockScreenManagesPowerManagerObserver) { |
| ASSERT_NO_FATAL_FAILURE(ShowLockScreen()); |
| LockContentsView* contents = |
| LockScreen::TestApi(LockScreen::Get()).contents_view(); |
| EXPECT_TRUE( |
| chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->HasObserver( |
| contents)); |
| |
| LockScreen::Get()->Destroy(); |
| // Wait for LockScreen to be fully destroyed |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE( |
| chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->HasObserver( |
| contents)); |
| } |
| |
| // Verifies that the password box for the active user is cleared if a suspend |
| // event is received. |
| TEST_F(LockContentsViewUnitTest, PasswordClearedOnSuspend) { |
| ASSERT_NO_FATAL_FAILURE(ShowLoginScreen()); |
| AddUsers(1); |
| |
| LockScreen::TestApi lock_screen = LockScreen::TestApi(LockScreen::Get()); |
| LockContentsView* contents = lock_screen.contents_view(); |
| LoginPasswordView* password_view = LockContentsView::TestApi(contents) |
| .primary_big_view() |
| ->auth_user() |
| ->password_view(); |
| views::Textfield* textfield = |
| LoginPasswordView::TestApi(password_view).textfield(); |
| |
| textfield->SetText(base::ASCIIToUTF16("some_password")); |
| // Suspend clears password. |
| EXPECT_FALSE(textfield->text().empty()); |
| contents->SuspendImminent(power_manager::SuspendImminent_Reason_LID_CLOSED); |
| EXPECT_TRUE(textfield->text().empty()); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, ArrowNavSingleUser) { |
| ASSERT_NO_FATAL_FAILURE(ShowLoginScreen()); |
| SetUserCount(1); |
| LockContentsView* lock_contents = |
| LockScreen::TestApi(LockScreen::Get()).contents_view(); |
| |
| LoginBigUserView* primary_big_view = |
| LockContentsView::TestApi(lock_contents).primary_big_view(); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(primary_big_view)); |
| |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->PressKey(ui::VKEY_RIGHT, 0); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(primary_big_view)); |
| |
| generator->PressKey(ui::VKEY_LEFT, 0); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(primary_big_view)); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, ArrowNavTwoUsers) { |
| ASSERT_NO_FATAL_FAILURE(ShowLoginScreen()); |
| AddUsers(1); |
| AddPublicAccountUsers(1); |
| LockContentsView::TestApi lock_contents = LockContentsView::TestApi( |
| LockScreen::TestApi(LockScreen::Get()).contents_view()); |
| |
| LoginPasswordView* primary_password_view = |
| LoginAuthUserView::TestApi(lock_contents.primary_big_view()->auth_user()) |
| .password_view(); |
| LoginBigUserView* secondary_user_view = |
| lock_contents.opt_secondary_big_view(); |
| |
| ASSERT_NE(nullptr, secondary_user_view); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(primary_password_view)); |
| |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->PressKey(ui::VKEY_RIGHT, 0); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(secondary_user_view)); |
| |
| generator->PressKey(ui::VKEY_RIGHT, 0); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(primary_password_view)); |
| |
| generator->PressKey(ui::VKEY_LEFT, 0); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(secondary_user_view)); |
| |
| generator->PressKey(ui::VKEY_LEFT, 0); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(primary_password_view)); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, ArrowNavThreeUsers) { |
| ASSERT_NO_FATAL_FAILURE(ShowLoginScreen()); |
| SetUserCount(3); |
| LockContentsView::TestApi lock_contents = LockContentsView::TestApi( |
| LockScreen::TestApi(LockScreen::Get()).contents_view()); |
| |
| LoginPasswordView* primary_password_view = |
| LoginAuthUserView::TestApi(lock_contents.primary_big_view()->auth_user()) |
| .password_view(); |
| LoginUserView* first_list_user = lock_contents.users_list()->user_view_at(0); |
| LoginUserView* second_list_user = lock_contents.users_list()->user_view_at(1); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(primary_password_view)); |
| |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->PressKey(ui::VKEY_RIGHT, 0); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(first_list_user)); |
| |
| generator->PressKey(ui::VKEY_RIGHT, 0); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(second_list_user)); |
| |
| generator->PressKey(ui::VKEY_RIGHT, 0); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(primary_password_view)); |
| |
| generator->PressKey(ui::VKEY_LEFT, 0); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(second_list_user)); |
| |
| generator->PressKey(ui::VKEY_LEFT, 0); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(first_list_user)); |
| |
| generator->PressKey(ui::VKEY_LEFT, 0); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(primary_password_view)); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, UserSwapFocusesBigView) { |
| ASSERT_NO_FATAL_FAILURE(ShowLoginScreen()); |
| SetUserCount(3); |
| LockContentsView::TestApi lock_contents = LockContentsView::TestApi( |
| LockScreen::TestApi(LockScreen::Get()).contents_view()); |
| |
| LoginPasswordView* primary_password_view = |
| LoginAuthUserView::TestApi(lock_contents.primary_big_view()->auth_user()) |
| .password_view(); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(primary_password_view)); |
| |
| lock_contents.users_list()->user_view_at(0)->RequestFocus(); |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->PressKey(ui::VKEY_RETURN, 0); |
| EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(primary_password_view)); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, PowerwashShortcutSendsMojoCall) { |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLogin, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| SetWidget(CreateWidgetWithContent(contents)); |
| |
| std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient(); |
| EXPECT_CALL(*client, ShowResetScreen()); |
| |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->PressKey(ui::KeyboardCode::VKEY_R, ui::EF_CONTROL_DOWN | |
| ui::EF_ALT_DOWN | |
| ui::EF_SHIFT_DOWN); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, UsersChangedRetainsExistingState) { |
| auto* contents = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(2); |
| SetWidget(CreateWidgetWithContent(contents)); |
| |
| LockContentsView::TestApi test_api(contents); |
| |
| AccountId primary_user = test_api.primary_big_view() |
| ->GetCurrentUser() |
| ->basic_user_info->account_id; |
| DataDispatcher()->SetPinEnabledForUser(primary_user, true); |
| |
| // This user should be identical to the user we enabled PIN for. |
| SetUserCount(1); |
| |
| EXPECT_TRUE( |
| LoginAuthUserView::TestApi(test_api.primary_big_view()->auth_user()) |
| .pin_view() |
| ->visible()); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, ShowHideWarningBannerBubble) { |
| // Build lock screen with a single user. |
| auto* lock = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetUserCount(1); |
| SetWidget(CreateWidgetWithContent(lock)); |
| |
| const AccountId& kUserAccountId = users()[0]->basic_user_info->account_id; |
| |
| LockContentsView::TestApi test_api(lock); |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| |
| // Creating lock screen does not show warning banner bubble. |
| EXPECT_FALSE(test_api.warning_banner_bubble()->IsVisible()); |
| |
| // Verifies that a warning banner is shown by giving a non-empty message. |
| DataDispatcher()->ShowWarningBanner(base::ASCIIToUTF16("foo")); |
| EXPECT_TRUE(test_api.warning_banner_bubble()->IsVisible()); |
| |
| // Verifies that a warning banner is hidden by HideWarningBanner(). |
| DataDispatcher()->HideWarningBanner(); |
| EXPECT_FALSE(test_api.warning_banner_bubble()->IsVisible()); |
| |
| // Shows a warning banner again. |
| DataDispatcher()->ShowWarningBanner(base::ASCIIToUTF16("foo")); |
| EXPECT_TRUE(test_api.warning_banner_bubble()->IsVisible()); |
| |
| // Attempt and fail user auth - an auth error is expected to be shown. |
| // The warning banner should not be hidden. |
| std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient(); |
| client->set_authenticate_user_callback_result(false); |
| EXPECT_CALL(*client, |
| AuthenticateUserWithPasswordOrPin_(kUserAccountId, _, false, _)); |
| |
| // Submit password. |
| LoginAuthUserView::TestApi(test_api.primary_big_view()->auth_user()) |
| .password_view() |
| ->RequestFocus(); |
| generator->PressKey(ui::KeyboardCode::VKEY_A, 0); |
| generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(test_api.auth_error_bubble()->IsVisible()); |
| EXPECT_TRUE(test_api.warning_banner_bubble()->IsVisible()); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, RemoveUserFocusMovesBackToPrimaryUser) { |
| // Build lock screen with one public account and one normal user. |
| auto* lock = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| AddPublicAccountUsers(1); |
| AddUsers(1); |
| users()[1]->can_remove = true; |
| DataDispatcher()->NotifyUsers(users()); |
| SetWidget(CreateWidgetWithContent(lock)); |
| |
| LockContentsView::TestApi test_api(lock); |
| LoginAuthUserView::TestApi secondary_test_api( |
| test_api.opt_secondary_big_view()->auth_user()); |
| LoginUserView::TestApi user_test_api(secondary_test_api.user_view()); |
| |
| // Remove the user. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| // Focus the dropdown to raise the bubble. |
| user_test_api.dropdown()->RequestFocus(); |
| generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| base::RunLoop().RunUntilIdle(); |
| // Focus the remove user bubble, tap twice to remove the user. |
| user_test_api.menu()->RequestFocus(); |
| generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| base::RunLoop().RunUntilIdle(); |
| generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Secondary user was removed. |
| EXPECT_EQ(nullptr, test_api.opt_secondary_big_view()); |
| // Primary user has focus. |
| EXPECT_TRUE(HasFocusInAnyChildView(test_api.primary_big_view())); |
| } |
| |
| // Verifies that setting fingerprint state keeps the backlights forced off. A |
| // fingerprint state change is not a user action, excluding too many |
| // authentication attempts, which will trigger the auth attempt flow. |
| TEST_F(LockContentsViewUnitTest, |
| BacklightRemainsForcedOffAfterFingerprintStateChange) { |
| // Enter tablet mode so the power button events force the backlight off. |
| Shell::Get()->power_button_controller()->OnTabletModeStarted(); |
| |
| // Show lock screen with one normal user. |
| auto* lock = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| AddUsers(1); |
| SetWidget(CreateWidgetWithContent(lock)); |
| |
| // Force the backlights off. |
| PressAndReleasePowerButton(); |
| EXPECT_TRUE( |
| Shell::Get()->backlights_forced_off_setter()->backlights_forced_off()); |
| |
| // Change fingerprint state; backlights remain forced off. |
| DataDispatcher()->SetFingerprintState( |
| users()[0]->basic_user_info->account_id, |
| mojom::FingerprintState::DISABLED_FROM_ATTEMPTS); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE( |
| Shell::Get()->backlights_forced_off_setter()->backlights_forced_off()); |
| |
| Shell::Get()->power_button_controller()->OnTabletModeEnded(); |
| } |
| |
| // Verifies that a fingerprint authentication attempt makes sure the backlights |
| // are not forced off. |
| TEST_F(LockContentsViewUnitTest, |
| BacklightIsNotForcedOffAfterFingerprintAuthenticationAttempt) { |
| // Enter tablet mode so the power button events force the backlight off. |
| Shell::Get()->power_button_controller()->OnTabletModeStarted(); |
| |
| // Show lock screen with one normal user. |
| auto* lock = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| AddUsers(1); |
| SetWidget(CreateWidgetWithContent(lock)); |
| |
| // Force the backlights off. |
| PressAndReleasePowerButton(); |
| EXPECT_TRUE( |
| Shell::Get()->backlights_forced_off_setter()->backlights_forced_off()); |
| |
| // Validate a fingerprint authentication attempt resets backlights being |
| // forced off. |
| DataDispatcher()->NotifyFingerprintAuthResult( |
| users()[0]->basic_user_info->account_id, false /*successful*/); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE( |
| Shell::Get()->backlights_forced_off_setter()->backlights_forced_off()); |
| |
| Shell::Get()->power_button_controller()->OnTabletModeEnded(); |
| } |
| |
| TEST_F(LockContentsViewUnitTest, RightAndLeftAcceleratorsWithNoUser) { |
| // Show lock screen but do *not* initialize any users. |
| auto* lock = new LockContentsView( |
| mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock, |
| DataDispatcher(), |
| std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher())); |
| SetWidget(CreateWidgetWithContent(lock)); |
| |
| // Nothing to validate except that there is no crash. |
| lock->AcceleratorPressed(ui::Accelerator(ui::VKEY_RIGHT, 0)); |
| lock->AcceleratorPressed(ui::Accelerator(ui::VKEY_LEFT, 0)); |
| } |
| |
| } // namespace ash |