| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "ash/api/tasks/fake_tasks_client.h" |
| #include "ash/api/tasks/tasks_types.h" |
| #include "ash/constants/ash_features.h" |
| #include "ash/glanceables/classroom/fake_glanceables_classroom_client.h" |
| #include "ash/glanceables/classroom/glanceables_classroom_item_view.h" |
| #include "ash/glanceables/classroom/glanceables_classroom_student_view.h" |
| #include "ash/glanceables/common/glanceables_view_id.h" |
| #include "ash/glanceables/glanceables_controller.h" |
| #include "ash/glanceables/tasks/glanceables_task_view.h" |
| #include "ash/glanceables/tasks/glanceables_tasks_view.h" |
| #include "ash/glanceables/tasks/test/glanceables_tasks_test_util.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/shell.h" |
| #include "ash/style/combobox.h" |
| #include "ash/style/counter_expand_button.h" |
| #include "ash/style/error_message_toast.h" |
| #include "ash/system/status_area_widget_test_helper.h" |
| #include "ash/system/unified/date_tray.h" |
| #include "ash/system/unified/glanceable_tray_bubble.h" |
| #include "ash/test/ash_test_util.h" |
| #include "base/test/gtest_tags.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/types/cxx23_to_underlying.h" |
| #include "chrome/browser/ash/profiles/profile_helper.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "components/account_id/account_id.h" |
| #include "components/session_manager/core/session_manager.h" |
| #include "components/user_manager/user_manager.h" |
| #include "content/public/test/browser_test.h" |
| #include "google_apis/gaia/gaia_id.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/keycodes/keyboard_codes_posix.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/views/controls/button/label_button.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/scroll_view.h" |
| #include "ui/views/controls/textfield/textfield.h" |
| #include "ui/views/view.h" |
| #include "ui/views/view_utils.h" |
| #include "url/gurl.h" |
| |
| namespace ash { |
| namespace { |
| |
| constexpr char kTestUserName[] = "test@test.test"; |
| constexpr GaiaId::Literal kTestUserGaiaId("123456"); |
| |
| constexpr char kDueDate[] = "2 Aug 2025 10:00 GMT"; |
| |
| views::Label* FindViewWithLabel(views::View* search_root, |
| const std::u16string& label) { |
| if (views::Label* const label_view = |
| views::AsViewClass<views::Label>(search_root); |
| label_view && label_view->GetText() == label) { |
| return label_view; |
| } |
| |
| // Keep searching in children views. |
| for (views::View* const child : search_root->children()) { |
| if (views::Label* const found = FindViewWithLabel(child, label)) { |
| return found; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| views::Label* FindViewWithLabelFromWindow(aura::Window* search_root, |
| const std::u16string& label) { |
| if (views::Widget* const root_widget = |
| views::Widget::GetWidgetForNativeWindow(search_root)) { |
| return FindViewWithLabel(root_widget->GetRootView(), label); |
| } |
| |
| for (aura::Window* const child : search_root->children()) { |
| if (auto* found = FindViewWithLabelFromWindow(child, label)) { |
| return found; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| views::Label* FindMenuItemLabelWithString(const std::u16string& label) { |
| return FindViewWithLabelFromWindow( |
| Shell::GetContainer(Shell::GetPrimaryRootWindow(), |
| kShellWindowId_MenuContainer), |
| label); |
| } |
| |
| } // namespace |
| |
| class GlanceablesBrowserTest : public InProcessBrowserTest { |
| public: |
| GlanceablesController* glanceables_controller() { |
| return Shell::Get()->glanceables_controller(); |
| } |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| |
| base::Time date; |
| ASSERT_TRUE(base::Time::FromString(kDueDate, &date)); |
| fake_glanceables_tasks_client_ = |
| glanceables_tasks_test_util::InitializeFakeTasksClient(date); |
| fake_glanceables_tasks_client_->set_http_error( |
| google_apis::ApiErrorCode::HTTP_SUCCESS); |
| fake_glanceables_classroom_client_ = |
| std::make_unique<FakeGlanceablesClassroomClient>(); |
| |
| Shell::Get()->glanceables_controller()->UpdateClientsRegistration( |
| account_id_, |
| GlanceablesController::ClientsRegistration{ |
| .classroom_client = fake_glanceables_classroom_client_.get(), |
| .tasks_client = fake_glanceables_tasks_client_.get()}); |
| Shell::Get()->glanceables_controller()->OnActiveUserSessionChanged( |
| account_id_); |
| |
| event_generator_ = std::make_unique<ui::test::EventGenerator>( |
| Shell::GetPrimaryRootWindow()); |
| } |
| |
| DateTray* GetDateTray() const { |
| return StatusAreaWidgetTestHelper::GetStatusAreaWidget()->date_tray(); |
| } |
| |
| ui::test::EventGenerator* GetEventGenerator() const { |
| return event_generator_.get(); |
| } |
| |
| void ToggleDateTray() { |
| GetEventGenerator()->MoveMouseTo( |
| GetDateTray()->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| } |
| |
| GlanceableTrayBubble* GetGlanceableTrayBubble() const { |
| return GetDateTray()->glanceables_bubble_for_test(); |
| } |
| |
| api::FakeTasksClient* fake_glanceables_tasks_client() const { |
| return fake_glanceables_tasks_client_.get(); |
| } |
| |
| GlanceablesTasksView* GetTasksView() const { |
| return views::AsViewClass<GlanceablesTasksView>( |
| GetGlanceableTrayBubble()->GetTasksView()); |
| } |
| |
| Combobox* GetTasksComboBoxView() const { |
| return views::AsViewClass<Combobox>(GetTasksView()->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kTimeManagementBubbleComboBox))); |
| } |
| |
| views::ScrollView* GetTasksScrollView() const { |
| return views::AsViewClass<views::ScrollView>(GetTasksView()->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kContentsScrollView))); |
| } |
| |
| views::View* GetTasksItemContainerView() const { |
| return views::AsViewClass<views::View>( |
| GetTasksView()->GetViewByID(base::to_underlying( |
| GlanceablesViewId::kTimeManagementBubbleListContainer))); |
| } |
| |
| CounterExpandButton* GetTasksExpandButtonView() const { |
| return views::AsViewClass<CounterExpandButton>( |
| GetTasksView()->GetViewByID(base::to_underlying( |
| GlanceablesViewId::kTimeManagementBubbleExpandButton))); |
| } |
| |
| views::LabelButton* GetAddNewTaskButton() const { |
| return views::AsViewClass<views::LabelButton>(GetTasksView()->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kTasksBubbleAddNewButton))); |
| } |
| |
| std::vector<std::string> GetCurrentTaskListItemTitles() const { |
| std::vector<std::string> current_items; |
| for (views::View* child : GetTasksItemContainerView()->children()) { |
| if (views::View* task_item = views::AsViewClass<views::View>(child)) { |
| current_items.push_back( |
| base::UTF16ToUTF8(views::AsViewClass<views::Label>( |
| task_item->GetViewByID(base::to_underlying( |
| GlanceablesViewId::kTaskItemTitleLabel))) |
| ->GetText())); |
| } |
| } |
| return current_items; |
| } |
| |
| void SetStudentAssignmentsCount(size_t count) { |
| fake_glanceables_classroom_client_->SetAssignmentsCount(count); |
| } |
| |
| views::View* GetStudentView() const { |
| return GetGlanceableTrayBubble()->GetClassroomStudentView(); |
| } |
| |
| views::View* GetStudentComboBoxView() const { |
| return views::AsViewClass<views::View>(GetStudentView()->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kTimeManagementBubbleComboBox))); |
| } |
| |
| CounterExpandButton* GetStudentExpandButtonView() const { |
| return views::AsViewClass<CounterExpandButton>( |
| GetStudentView()->GetViewByID(base::to_underlying( |
| GlanceablesViewId::kTimeManagementBubbleExpandButton))); |
| } |
| |
| views::View* GetStudentItemContainerView() const { |
| return views::AsViewClass<views::View>( |
| GetStudentView()->GetViewByID(base::to_underlying( |
| GlanceablesViewId::kTimeManagementBubbleListContainer))); |
| } |
| |
| std::vector<std::string> GetCurrentStudentAssignmentCourseWorkTitles() const { |
| std::vector<std::string> assignment_titles; |
| for (views::View* child : GetStudentItemContainerView()->children()) { |
| if (views::View* assignment = views::AsViewClass<views::View>(child)) { |
| assignment_titles.push_back(base::UTF16ToUTF8( |
| views::AsViewClass<views::Label>( |
| assignment->GetViewByID(base::to_underlying( |
| GlanceablesViewId::kClassroomItemCourseWorkTitleLabel))) |
| ->GetText())); |
| } |
| } |
| return assignment_titles; |
| } |
| |
| GlanceablesClassroomItemView* GetClassroomItemView(int item_index) { |
| return views::AsViewClass<GlanceablesClassroomItemView>( |
| GetStudentItemContainerView()->children()[item_index]); |
| } |
| |
| views::LabelButton* GetStudentFooterSeeAllButton() const { |
| return views::AsViewClass<views::LabelButton>(GetStudentView()->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kListFooterSeeAllButton))); |
| } |
| |
| private: |
| std::unique_ptr<ui::test::EventGenerator> event_generator_; |
| AccountId account_id_ = |
| AccountId::FromUserEmailGaiaId(kTestUserName, kTestUserGaiaId); |
| std::unique_ptr<api::FakeTasksClient> fake_glanceables_tasks_client_; |
| std::unique_ptr<FakeGlanceablesClassroomClient> |
| fake_glanceables_classroom_client_; |
| }; |
| |
| // ----------------------------------------------------------------------------- |
| |
| class GlanceablesMvpBrowserTest : public GlanceablesBrowserTest { |
| public: |
| GlanceablesMvpBrowserTest() { |
| features_.InitWithFeatures( |
| /*enabled_features=*/ |
| {features::kGlanceablesTimeManagementClassroomStudentView}, |
| /*disabled_features=*/{features::kGlanceablesTimeManagementTasksView}); |
| } |
| |
| void SetUpOnMainThread() override { |
| GlanceablesBrowserTest::SetUpOnMainThread(); |
| base::AddFeatureIdTagToTestResult( |
| "screenplay-ace3b729-5402-40cd-b2bf-d488bc95b7e2"); |
| } |
| |
| private: |
| base::test::ScopedFeatureList features_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(GlanceablesMvpBrowserTest, OpenStudentCourseItemURL) { |
| ASSERT_TRUE(glanceables_controller()->GetClassroomClient()); |
| |
| // Click the date tray to show the glanceable bubbles. |
| ToggleDateTray(); |
| |
| EXPECT_TRUE(GetGlanceableTrayBubble()); |
| EXPECT_TRUE(GetStudentView()); |
| |
| EXPECT_TRUE( |
| Shell::Get()->GetPrimaryRootWindow()->GetBoundsInScreen().Contains( |
| GetStudentView()->GetBoundsInScreen())); |
| |
| // Check that the approaching course work items are shown. |
| EXPECT_EQ(GetCurrentStudentAssignmentCourseWorkTitles(), |
| std::vector<std::string>({"Approaching Course Work 0", |
| "Approaching Course Work 1", |
| "Approaching Course Work 2"})); |
| |
| // Click the first item view assignment and check that its url was opened. |
| GetEventGenerator()->MoveMouseTo(GetClassroomItemView(/*item_index=*/0) |
| ->GetBoundsInScreen() |
| .CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| EXPECT_EQ( |
| browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL(), |
| "https://classroom.google.com/c/test/a/test_course_id_0/details"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlanceablesMvpBrowserTest, ClickSeeAllStudentButton) { |
| ASSERT_TRUE(glanceables_controller()->GetClassroomClient()); |
| SetStudentAssignmentsCount(101); |
| |
| // Click the date tray to show the glanceable bubbles. |
| ToggleDateTray(); |
| |
| EXPECT_TRUE(GetGlanceableTrayBubble()); |
| EXPECT_TRUE(GetStudentView()); |
| |
| EXPECT_TRUE( |
| Shell::Get()->GetPrimaryRootWindow()->GetBoundsInScreen().Contains( |
| GetStudentView()->GetBoundsInScreen())); |
| |
| // Click the "See All" button in the student glanceable footer, and check that |
| // the correct URL is opened. |
| GetStudentFooterSeeAllButton()->ScrollViewToVisible(); |
| GetEventGenerator()->MoveMouseTo( |
| GetStudentFooterSeeAllButton()->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| EXPECT_EQ( |
| browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL(), |
| "https://classroom.google.com/u/0/a/not-turned-in/all"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlanceablesMvpBrowserTest, |
| ViewAndSwitchStudentClassroomLists) { |
| ASSERT_TRUE(glanceables_controller()->GetClassroomClient()); |
| |
| // Click the date tray to show the glanceable bubbles. |
| ToggleDateTray(); |
| |
| EXPECT_TRUE(GetGlanceableTrayBubble()); |
| EXPECT_TRUE(GetStudentView()); |
| |
| EXPECT_TRUE( |
| Shell::Get()->GetPrimaryRootWindow()->GetBoundsInScreen().Contains( |
| GetStudentView()->GetBoundsInScreen())); |
| |
| // Check that the approaching course work items are shown. |
| EXPECT_EQ(GetCurrentStudentAssignmentCourseWorkTitles(), |
| std::vector<std::string>({"Approaching Course Work 0", |
| "Approaching Course Work 1", |
| "Approaching Course Work 2"})); |
| |
| // Click on the combo box to show the student classroom lists. |
| GetEventGenerator()->MoveMouseTo( |
| GetStudentComboBoxView()->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| |
| // Expect that the correct menu items are shown for the student glanceable. |
| const views::View* const due_soon_menu_item = |
| FindMenuItemLabelWithString(u"Due soon"); |
| const views::View* const no_due_date_menu_item = |
| FindMenuItemLabelWithString(u"No due date"); |
| const views::View* const missing_menu_item = |
| FindMenuItemLabelWithString(u"Missing"); |
| const views::View* const done_menu_item = |
| FindMenuItemLabelWithString(u"Done"); |
| EXPECT_TRUE(due_soon_menu_item); |
| EXPECT_TRUE(no_due_date_menu_item); |
| EXPECT_TRUE(missing_menu_item); |
| EXPECT_TRUE(done_menu_item); |
| |
| // Click on the no due date label to switch to a new assignment list. |
| ASSERT_TRUE(no_due_date_menu_item); |
| GetEventGenerator()->MoveMouseTo( |
| no_due_date_menu_item->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| |
| // Check that the no due date course work items are shown after switching |
| // lists. |
| EXPECT_EQ(GetCurrentStudentAssignmentCourseWorkTitles(), |
| std::vector<std::string>({"No Due Date Course Work 0", |
| "No Due Date Course Work 1", |
| "No Due Date Course Work 2"})); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| |
| class GlanceablesTasksBrowserTest : public GlanceablesBrowserTest { |
| public: |
| GlanceablesTasksBrowserTest() { |
| features_.InitWithFeatures( |
| /*enabled_features=*/{features::kGlanceablesTimeManagementTasksView}, |
| /*disabled_features=*/{ |
| features::kGlanceablesTimeManagementClassroomStudentView}); |
| } |
| |
| void SetUpOnMainThread() override { |
| GlanceablesBrowserTest::SetUpOnMainThread(); |
| ASSERT_TRUE(glanceables_controller()->GetTasksClient()); |
| } |
| |
| // Returns the task view at `item_index`. |
| GlanceablesTaskView* GetTaskItemView(int item_index) { |
| return views::AsViewClass<GlanceablesTaskView>( |
| GetTasksItemContainerView()->children()[item_index]); |
| } |
| |
| private: |
| base::test::ScopedFeatureList features_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(GlanceablesTasksBrowserTest, ViewAndSwitchTaskLists) { |
| base::AddFeatureIdTagToTestResult( |
| "screenplay-07815625-e657-471e-80b4-73fca7bd939b"); // view |
| base::AddFeatureIdTagToTestResult( |
| "screenplay-1b44afe6-b6fa-4391-92dd-7063555091ff"); // switch |
| |
| EXPECT_FALSE(GetGlanceableTrayBubble()); |
| |
| ToggleDateTray(); |
| |
| EXPECT_TRUE(GetGlanceableTrayBubble()); |
| EXPECT_TRUE(GetTasksView()); |
| |
| // Check that task list items from the first list are shown. |
| EXPECT_EQ(GetCurrentTaskListItemTitles(), |
| std::vector<std::string>( |
| {"Task List 1 Item 1 Title", "Task List 1 Item 2 Title"})); |
| |
| // Click on the combo box to show the task lists. |
| GetEventGenerator()->MoveMouseTo( |
| GetTasksComboBoxView()->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| |
| views::Label* second_menu_item_label = |
| FindMenuItemLabelWithString(u"Task List 2 Title"); |
| |
| // Click on the second menu item label to switch to the second task list. |
| ASSERT_TRUE(second_menu_item_label); |
| GetEventGenerator()->MoveMouseTo( |
| second_menu_item_label->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| |
| // Make sure that task list items from the second list are shown. |
| EXPECT_EQ(GetCurrentTaskListItemTitles(), |
| std::vector<std::string>({"Task List 2 Item 1 Title", |
| "Task List 2 Item 2 Title", |
| "Task List 2 Item 3 Title"})); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlanceablesTasksBrowserTest, CheckOffTaskItems) { |
| base::AddFeatureIdTagToTestResult( |
| "screenplay-399a1737-5502-4bba-a909-2dbe5a97e2c7"); |
| |
| EXPECT_FALSE(GetGlanceableTrayBubble()); |
| |
| // Click the date tray to show the glanceable bubbles. |
| ToggleDateTray(); |
| |
| EXPECT_TRUE(GetGlanceableTrayBubble()); |
| EXPECT_TRUE(GetTasksView()); |
| |
| // Check that task list items from the first list are shown. |
| EXPECT_EQ(GetCurrentTaskListItemTitles(), |
| std::vector<std::string>( |
| {"Task List 1 Item 1 Title", "Task List 1 Item 2 Title"})); |
| |
| EXPECT_FALSE(GetTaskItemView(/*item_index=*/0)->GetCompletedForTest()); |
| EXPECT_FALSE(GetTaskItemView(/*item_index=*/1)->GetCompletedForTest()); |
| |
| // Click to check off the first task item and check that it has been marked |
| // complete. |
| GetEventGenerator()->MoveMouseTo(GetTaskItemView(/*item_index=*/0) |
| ->GetCheckButtonForTest() |
| ->GetBoundsInScreen() |
| .CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| EXPECT_TRUE(GetTaskItemView(/*item_index=*/0)->GetCompletedForTest()); |
| EXPECT_FALSE(GetTaskItemView(/*item_index=*/1)->GetCompletedForTest()); |
| |
| // Click to check off the second task item and check that it has been marked |
| // complete. |
| GetEventGenerator()->MoveMouseTo(GetTaskItemView(/*item_index=*/1) |
| ->GetCheckButtonForTest() |
| ->GetBoundsInScreen() |
| .CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| EXPECT_TRUE(GetTaskItemView(/*item_index=*/0)->GetCompletedForTest()); |
| EXPECT_TRUE(GetTaskItemView(/*item_index=*/1)->GetCompletedForTest()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlanceablesTasksBrowserTest, AddTaskItem) { |
| base::AddFeatureIdTagToTestResult( |
| "screenplay-14a3974a-cb94-44fb-8a70-95e5f761fa2d"); |
| |
| EXPECT_FALSE(GetGlanceableTrayBubble()); |
| |
| // Click the date tray to show the glanceable bubbles. |
| ToggleDateTray(); |
| |
| ASSERT_TRUE(GetGlanceableTrayBubble()); |
| ASSERT_TRUE(GetTasksView()); |
| |
| const auto* const add_task_button = GetAddNewTaskButton(); |
| ASSERT_TRUE(add_task_button); |
| |
| const auto* const task_items_container = GetTasksItemContainerView(); |
| ASSERT_TRUE(task_items_container); |
| |
| // Click on `add_task_button` and verify that `task_items_container` has the |
| // new "pending" item. |
| EXPECT_EQ(task_items_container->children().size(), 2u); |
| GetEventGenerator()->MoveMouseTo( |
| add_task_button->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| EXPECT_EQ(task_items_container->children().size(), 3u); |
| |
| const auto* const pending_task_view = GetTaskItemView(0); |
| |
| { |
| const auto* const title_label = |
| views::AsViewClass<views::Label>(pending_task_view->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kTaskItemTitleLabel))); |
| const auto* const title_text_field = |
| views::AsViewClass<views::Textfield>(pending_task_view->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kTaskItemTitleTextField))); |
| |
| // Check that the view is in "edit" mode (the text field is displayed). |
| EXPECT_FALSE(title_label); |
| ASSERT_TRUE(title_text_field); |
| EXPECT_TRUE(title_text_field->IsDrawn()); |
| EXPECT_TRUE(title_text_field->GetText().empty()); |
| |
| // Append "New task" text. |
| GetEventGenerator()->PressAndReleaseKey(ui::VKEY_N, ui::EF_SHIFT_DOWN); |
| GetEventGenerator()->PressAndReleaseKey(ui::VKEY_E); |
| GetEventGenerator()->PressAndReleaseKey(ui::VKEY_W); |
| GetEventGenerator()->PressAndReleaseKey(ui::VKEY_SPACE); |
| GetEventGenerator()->PressAndReleaseKey(ui::VKEY_T); |
| GetEventGenerator()->PressAndReleaseKey(ui::VKEY_A); |
| GetEventGenerator()->PressAndReleaseKey(ui::VKEY_S); |
| GetEventGenerator()->PressAndReleaseKey(ui::VKEY_K); |
| |
| // Finish editing by pressing Esc key. |
| GetEventGenerator()->PressAndReleaseKey(ui::VKEY_ESCAPE); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| { |
| const auto* const title_label = |
| views::AsViewClass<views::Label>(pending_task_view->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kTaskItemTitleLabel))); |
| const auto* const title_text_field = |
| views::AsViewClass<views::Textfield>(pending_task_view->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kTaskItemTitleTextField))); |
| |
| // Check that the view is in "view" mode with the expected label |
| ASSERT_TRUE(title_label); |
| EXPECT_TRUE(title_label->IsDrawn()); |
| EXPECT_FALSE(title_text_field); |
| EXPECT_EQ(title_label->GetText(), u"New task"); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlanceablesTasksBrowserTest, EditTaskItem) { |
| base::AddFeatureIdTagToTestResult( |
| "screenplay-aa9616ed-117c-4d9f-b223-f2e603506780"); |
| |
| EXPECT_FALSE(GetGlanceableTrayBubble()); |
| |
| // Click the date tray to show the glanceable bubbles. |
| ToggleDateTray(); |
| |
| EXPECT_TRUE(GetGlanceableTrayBubble()); |
| EXPECT_TRUE(GetTasksView()); |
| |
| const auto* const task_view = GetTaskItemView(0); |
| ASSERT_TRUE(task_view); |
| |
| { |
| const auto* const title_label = |
| views::AsViewClass<views::Label>(task_view->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kTaskItemTitleLabel))); |
| const auto* const title_text_field = |
| views::AsViewClass<views::Textfield>(task_view->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kTaskItemTitleTextField))); |
| |
| // Check that the view is in "view" mode (the label is displayed). |
| ASSERT_TRUE(title_label); |
| EXPECT_TRUE(title_label->IsDrawn()); |
| EXPECT_FALSE(title_text_field); |
| EXPECT_EQ(title_label->GetText(), u"Task List 1 Item 1 Title"); |
| |
| // Click the label to switch to "edit" mode. |
| GetEventGenerator()->MoveMouseTo( |
| title_label->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| } |
| |
| { |
| const auto* const title_label = |
| views::AsViewClass<views::Label>(task_view->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kTaskItemTitleLabel))); |
| const auto* const title_text_field = |
| views::AsViewClass<views::Textfield>(task_view->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kTaskItemTitleTextField))); |
| |
| // Check that the view is in "edit" mode (the text field is displayed). |
| EXPECT_FALSE(title_label); |
| ASSERT_TRUE(title_text_field); |
| EXPECT_TRUE(title_text_field->IsDrawn()); |
| EXPECT_EQ(title_text_field->GetText(), u"Task List 1 Item 1 Title"); |
| |
| // Append " upd" text. |
| GetEventGenerator()->PressAndReleaseKey(ui::VKEY_SPACE); |
| GetEventGenerator()->PressAndReleaseKey(ui::VKEY_U); |
| GetEventGenerator()->PressAndReleaseKey(ui::VKEY_P); |
| GetEventGenerator()->PressAndReleaseKey(ui::VKEY_D); |
| |
| // Finish editing by pressing Esc key. |
| GetEventGenerator()->PressAndReleaseKey(ui::VKEY_ESCAPE); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| { |
| const auto* const title_label = |
| views::AsViewClass<views::Label>(task_view->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kTaskItemTitleLabel))); |
| const auto* const title_text_field = |
| views::AsViewClass<views::Textfield>(task_view->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kTaskItemTitleTextField))); |
| |
| // Check that the view is in "view" mode with the updated label |
| ASSERT_TRUE(title_label); |
| EXPECT_TRUE(title_label->IsDrawn()); |
| EXPECT_EQ(title_label->GetText(), u"Task List 1 Item 1 Title upd"); |
| EXPECT_FALSE(title_text_field); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlanceablesTasksBrowserTest, TasksViewLayout) { |
| // Click the date tray to show the glanceable bubbles. |
| ToggleDateTray(); |
| |
| ASSERT_TRUE(GetGlanceableTrayBubble()); |
| ASSERT_TRUE(GetTasksView()); |
| |
| // Calculate the available space for tasks and make sure there is enough for |
| // additional task view. |
| auto display = display::Screen::Get()->GetPrimaryDisplay(); |
| const int kGlanceableMargins = 8; |
| const int kCalendarHeight = 340; |
| const int available_height_for_tasks = |
| display.work_area().height() - kCalendarHeight - kGlanceableMargins; |
| const int original_task_view_height = GetTasksView()->height(); |
| ASSERT_GT(available_height_for_tasks, original_task_view_height); |
| |
| const auto* const add_task_button = GetAddNewTaskButton(); |
| ASSERT_TRUE(add_task_button); |
| |
| const auto* const task_items_container = GetTasksItemContainerView(); |
| ASSERT_TRUE(task_items_container); |
| |
| // Use the visibility of the scroll bar to determine if the contents of the |
| // scroll view is larger than its viewport. In this case, they should have the |
| // same sizes. |
| const auto* scroll_bar = GetTasksScrollView()->vertical_scroll_bar(); |
| EXPECT_FALSE(scroll_bar->GetVisible()); |
| |
| // Click on `add_task_button` and verify that `task_items_container` has the |
| // new "pending" item. |
| EXPECT_EQ(task_items_container->children().size(), 2u); |
| GetEventGenerator()->MoveMouseTo( |
| add_task_button->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| EXPECT_EQ(task_items_container->children().size(), 3u); |
| GetTasksView()->EndResizeAnimationForTest(); |
| GetTasksView()->GetWidget()->LayoutRootViewIfNecessary(); |
| |
| // The tasks view should update its height if there is space available. |
| EXPECT_GT(GetTasksView()->height(), original_task_view_height); |
| EXPECT_FALSE(scroll_bar->GetVisible()); |
| |
| // Commit the empty new task, which removes the temporary task view. |
| GetEventGenerator()->PressAndReleaseKey(ui::VKEY_ESCAPE); |
| base::RunLoop().RunUntilIdle(); |
| GetTasksView()->EndResizeAnimationForTest(); |
| GetTasksView()->GetWidget()->LayoutRootViewIfNecessary(); |
| EXPECT_EQ(task_items_container->children().size(), 2u); |
| |
| // Verify that the tasks view height is resized to its original height without |
| // the new task. |
| EXPECT_EQ(GetTasksView()->height(), original_task_view_height); |
| EXPECT_FALSE(scroll_bar->GetVisible()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlanceablesTasksBrowserTest, ShowsCachedDataBasic) { |
| auto* const client = fake_glanceables_tasks_client(); |
| client->set_paused_on_fetch(true); |
| |
| // Click the date tray to show the glanceable bubbles. For the first time the |
| // glanceables are shown, the tasks need to be fetched and the view should not |
| // be shown before the data returns. |
| ToggleDateTray(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(GetGlanceableTrayBubble()); |
| ASSERT_FALSE(GetTasksView()); |
| |
| client->RunPendingGetTaskListsCallbacks(); |
| client->RunPendingGetTasksCallbacks(); |
| ASSERT_TRUE(GetTasksView()); |
| |
| // Close the glanceables. |
| ToggleDateTray(); |
| ASSERT_FALSE(GetGlanceableTrayBubble()); |
| |
| // The second and following times when the tasks are shown, the cached |
| // tasks should be shown while waiting the new change to be fetched. |
| ToggleDateTray(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(GetGlanceableTrayBubble()); |
| ASSERT_TRUE(GetTasksView()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlanceablesTasksBrowserTest, |
| CachedTaskListAreUpdatedAfterFetch) { |
| // Click the date tray to show the glanceable bubbles. |
| ToggleDateTray(); |
| |
| EXPECT_TRUE(GetGlanceableTrayBubble()); |
| EXPECT_TRUE(GetTasksView()); |
| |
| // Check that task list items from the first list are shown. |
| EXPECT_EQ(GetCurrentTaskListItemTitles(), |
| std::vector<std::string>( |
| {"Task List 1 Item 1 Title", "Task List 1 Item 2 Title"})); |
| |
| // Close the glanceables. |
| ToggleDateTray(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Turn on the pause_on_fetch to verify the cached tasks and the updated |
| // tasks. |
| auto* const client = fake_glanceables_tasks_client(); |
| client->set_paused_on_fetch(true); |
| |
| // Add a task in Task List 1 directly via the client as an updated task. |
| client->AddTask( |
| /*task_list_id=*/"TaskListID1", |
| std::make_unique<api::Task>( |
| /*id=*/"TaskListItem5", /*title=*/"Task List 1 Item 3 Title", |
| /*due=*/base::Time::Now(), /*completed=*/false, |
| /*has_subtasks=*/false, /*has_email_link=*/false, |
| /*has_notes=*/false, /*updated=*/base::Time::Now(), |
| /*web_view_link=*/GURL(), api::Task::OriginSurfaceType::kRegular)); |
| |
| // Open the glanceables again. |
| ToggleDateTray(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Check that only the cached task list items from the first list are shown. |
| EXPECT_EQ(GetCurrentTaskListItemTitles(), |
| std::vector<std::string>( |
| {"Task List 1 Item 1 Title", "Task List 1 Item 2 Title"})); |
| |
| client->RunPendingGetTaskListsCallbacks(); |
| EXPECT_FALSE(GetTasksView()->GetCanProcessEventsWithinSubtree()); |
| client->RunPendingGetTasksCallbacks(); |
| |
| // After running the get callbacks, the newly added task is shown. |
| EXPECT_EQ(GetCurrentTaskListItemTitles(), |
| std::vector<std::string>({"Task List 1 Item 1 Title", |
| "Task List 1 Item 2 Title", |
| "Task List 1 Item 3 Title"})); |
| EXPECT_TRUE(GetTasksView()->GetCanProcessEventsWithinSubtree()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlanceablesTasksBrowserTest, |
| UpdateShownListIfCachedTaskListDeleted) { |
| // Click the date tray to show the glanceable bubbles. |
| ToggleDateTray(); |
| |
| EXPECT_TRUE(GetGlanceableTrayBubble()); |
| EXPECT_TRUE(GetTasksView()); |
| |
| // Check that task list items from the first list are shown. |
| const auto* combobox = GetTasksComboBoxView(); |
| EXPECT_EQ(combobox->GetTextForRow(combobox->GetSelectedIndex().value()), |
| u"Task List 1 Title"); |
| EXPECT_EQ(GetCurrentTaskListItemTitles(), |
| std::vector<std::string>( |
| {"Task List 1 Item 1 Title", "Task List 1 Item 2 Title"})); |
| |
| // Close the glanceables. |
| ToggleDateTray(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Turn on the pause_on_fetch to verify the cached tasks and the updated |
| // tasks. |
| auto* const client = fake_glanceables_tasks_client(); |
| client->set_paused_on_fetch(true); |
| |
| // Delete the task list that was shown. |
| client->DeleteTaskList("TaskListID1"); |
| |
| // Open the glanceables again. |
| ToggleDateTray(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Check that deleted list is still showing as it is cached. |
| combobox = GetTasksComboBoxView(); |
| EXPECT_EQ(combobox->GetTextForRow(combobox->GetSelectedIndex().value()), |
| u"Task List 1 Title"); |
| EXPECT_EQ(GetCurrentTaskListItemTitles(), |
| std::vector<std::string>( |
| {"Task List 1 Item 1 Title", "Task List 1 Item 2 Title"})); |
| |
| client->RunPendingGetTaskListsCallbacks(); |
| client->RunPendingGetTasksCallbacks(); |
| |
| // After running the get callbacks, the task list shown is updated. |
| combobox = GetTasksComboBoxView(); |
| EXPECT_EQ(combobox->GetTextForRow(combobox->GetSelectedIndex().value()), |
| u"Task List 2 Title"); |
| EXPECT_EQ(GetCurrentTaskListItemTitles(), |
| std::vector<std::string>({"Task List 2 Item 1 Title", |
| "Task List 2 Item 2 Title", |
| "Task List 2 Item 3 Title"})); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlanceablesTasksBrowserTest, DontShowTasksIfNoNetwork) { |
| fake_glanceables_tasks_client()->set_get_task_lists_error(true); |
| |
| // Click the date tray to show the glanceable bubbles. |
| ToggleDateTray(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(GetGlanceableTrayBubble()); |
| EXPECT_FALSE(GetTasksView()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlanceablesTasksBrowserTest, |
| ShowFailedToLoadViewIfNoNetwork) { |
| fake_glanceables_tasks_client()->set_get_tasks_error(true); |
| |
| // Click the date tray to show the glanceable bubbles. |
| ToggleDateTray(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(GetGlanceableTrayBubble()); |
| EXPECT_TRUE(GetTasksView()); |
| |
| auto* error_view = views::AsViewClass<ErrorMessageToast>( |
| GetTasksView()->GetViewByID(base::to_underlying( |
| GlanceablesViewId::kTimeManagementErrorMessageToast))); |
| ASSERT_TRUE(error_view); |
| EXPECT_EQ(error_view->GetMessageForTest(), u"Couldn't load items."); |
| EXPECT_EQ(error_view->GetButtonForTest()->GetText(), u"Reload"); |
| |
| // Reset the error flag so that the next tasks fetch will succeed. |
| fake_glanceables_tasks_client()->set_get_tasks_error(false); |
| |
| GetEventGenerator()->MoveMouseTo( |
| error_view->GetButtonForTest()->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| |
| EXPECT_FALSE(GetTasksView()->GetViewByID(base::to_underlying( |
| GlanceablesViewId::kTimeManagementErrorMessageToast))); |
| auto* combobox = GetTasksComboBoxView(); |
| EXPECT_EQ(combobox->GetTextForRow(combobox->GetSelectedIndex().value()), |
| u"Task List 1 Title"); |
| EXPECT_EQ(GetCurrentTaskListItemTitles(), |
| std::vector<std::string>( |
| {"Task List 1 Item 1 Title", "Task List 1 Item 2 Title"})); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlanceablesTasksBrowserTest, SwitchTaskListsWithError) { |
| ToggleDateTray(); |
| |
| EXPECT_TRUE(GetGlanceableTrayBubble()); |
| EXPECT_TRUE(GetTasksView()); |
| |
| // Set the error flag to true so that it fails on the next time the tasks are |
| // fetched. |
| fake_glanceables_tasks_client()->set_get_tasks_error(true); |
| |
| // Check that task list items from the first list are shown. |
| const auto* combobox = GetTasksComboBoxView(); |
| EXPECT_EQ(combobox->GetTextForRow(combobox->GetSelectedIndex().value()), |
| u"Task List 1 Title"); |
| |
| // Click on the combo box to show the task lists. |
| GetEventGenerator()->MoveMouseTo(combobox->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| |
| views::Label* second_menu_item_label = |
| FindMenuItemLabelWithString(u"Task List 2 Title"); |
| |
| // Click on the second menu item label to switch to the second task list. |
| ASSERT_TRUE(second_menu_item_label); |
| GetEventGenerator()->MoveMouseTo( |
| second_menu_item_label->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Failing to update the task list will reset the combobox to the task list |
| // before switching. |
| EXPECT_EQ(combobox->GetTextForRow(combobox->GetSelectedIndex().value()), |
| u"Task List 1 Title"); |
| |
| auto* error_view = views::AsViewClass<ErrorMessageToast>( |
| GetTasksView()->GetViewByID(base::to_underlying( |
| GlanceablesViewId::kTimeManagementErrorMessageToast))); |
| ASSERT_TRUE(error_view); |
| EXPECT_EQ(error_view->GetMessageForTest(), u"Couldn't load items."); |
| EXPECT_EQ(error_view->GetButtonForTest()->GetText(), u"Dismiss"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlanceablesTasksBrowserTest, |
| SavelyRemoveTaskViewInEditState) { |
| // Click the date tray to show the glanceable bubbles. |
| ToggleDateTray(); |
| |
| EXPECT_TRUE(GetGlanceableTrayBubble()); |
| EXPECT_TRUE(GetTasksView()); |
| |
| // Check that task list items from the first list are shown. |
| EXPECT_EQ(GetCurrentTaskListItemTitles(), |
| std::vector<std::string>( |
| {"Task List 1 Item 1 Title", "Task List 1 Item 2 Title"})); |
| |
| // Close the glanceables. |
| ToggleDateTray(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Turn on the pause_on_fetch to pause in between the cached tasks is shown |
| // and the tasks has started fetching. |
| auto* const client = fake_glanceables_tasks_client(); |
| client->set_paused_on_fetch(true); |
| |
| // Delete the whole task list. |
| client->DeleteTaskList(/*task_list_id=*/"TaskListID1"); |
| |
| // Open the glanceables again. |
| ToggleDateTray(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Check that the deleted task list is still shown. |
| EXPECT_EQ(GetCurrentTaskListItemTitles(), |
| std::vector<std::string>( |
| {"Task List 1 Item 1 Title", "Task List 1 Item 2 Title"})); |
| |
| GetTasksView()->GetWidget()->LayoutRootViewIfNecessary(); |
| |
| // Before fetch, click on the cached task and see if the textfield shows up. |
| auto* first_task_view_label = |
| GetTaskItemView(/*item_index=*/0) |
| ->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kTaskItemTitleLabel)); |
| GetEventGenerator()->MoveMouseTo( |
| first_task_view_label->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->ClickLeftButton(); |
| |
| auto* first_task_view_textfield = |
| GetTaskItemView(/*item_index=*/0) |
| ->GetViewByID( |
| base::to_underlying(GlanceablesViewId::kTaskItemTitleTextField)); |
| ASSERT_TRUE(first_task_view_textfield); |
| ASSERT_TRUE(first_task_view_textfield->GetVisible()); |
| |
| // Start fetching new data. |
| client->RunPendingGetTaskListsCallbacks(); |
| EXPECT_FALSE(GetTasksView()->GetCanProcessEventsWithinSubtree()); |
| client->RunPendingGetTasksCallbacks(); |
| EXPECT_TRUE(GetTasksView()->GetCanProcessEventsWithinSubtree()); |
| |
| // Check if the second list is shown after fetch and nothing crashed. |
| EXPECT_EQ(GetCurrentTaskListItemTitles(), |
| std::vector<std::string>({"Task List 2 Item 1 Title", |
| "Task List 2 Item 2 Title", |
| "Task List 2 Item 3 Title"})); |
| } |
| |
| } // namespace ash |