// 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 "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[] = "[email protected]";
constexpr char 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::GetScreen()->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