| // Copyright (c) 2012 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 "ash/app_list/views/apps_grid_view.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <string> |
| |
| #include "ash/app_list/app_list_metrics.h" |
| #include "ash/app_list/app_list_util.h" |
| #include "ash/app_list/model/app_list_folder_item.h" |
| #include "ash/app_list/model/app_list_item.h" |
| #include "ash/app_list/model/app_list_model.h" |
| #include "ash/app_list/test/app_list_test_model.h" |
| #include "ash/app_list/test/app_list_test_view_delegate.h" |
| #include "ash/app_list/test/test_search_result.h" |
| #include "ash/app_list/views/app_list_folder_view.h" |
| #include "ash/app_list/views/app_list_item_view.h" |
| #include "ash/app_list/views/app_list_main_view.h" |
| #include "ash/app_list/views/app_list_view.h" |
| #include "ash/app_list/views/apps_container_view.h" |
| #include "ash/app_list/views/apps_grid_view_folder_delegate.h" |
| #include "ash/app_list/views/contents_view.h" |
| #include "ash/app_list/views/expand_arrow_view.h" |
| #include "ash/app_list/views/folder_background_view.h" |
| #include "ash/app_list/views/search_box_view.h" |
| #include "ash/app_list/views/search_result_tile_item_view.h" |
| #include "ash/app_list/views/suggestion_chip_container_view.h" |
| #include "ash/app_list/views/test/apps_grid_view_test_api.h" |
| #include "ash/keyboard/ui/keyboard_ui_controller.h" |
| #include "ash/public/cpp/app_list/app_list_config.h" |
| #include "ash/public/cpp/app_list/app_list_features.h" |
| #include "ash/public/cpp/app_list/app_list_switches.h" |
| #include "ash/public/cpp/pagination/pagination_model.h" |
| #include "ash/public/cpp/presentation_time_recorder.h" |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/icu_test_util.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/aura/window.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/textfield/textfield.h" |
| #include "ui/views/test/views_test_base.h" |
| |
| namespace ash { |
| namespace test { |
| |
| namespace { |
| |
| constexpr int kNumOfSuggestedApps = 3; |
| |
| class PageFlipWaiter : public ash::PaginationModelObserver { |
| public: |
| explicit PageFlipWaiter(ash::PaginationModel* model) : model_(model) { |
| model_->AddObserver(this); |
| } |
| |
| ~PageFlipWaiter() override { model_->RemoveObserver(this); } |
| |
| void Wait() { |
| DCHECK(!wait_); |
| wait_ = true; |
| |
| ui_run_loop_ = std::make_unique<base::RunLoop>(); |
| ui_run_loop_->Run(); |
| wait_ = false; |
| } |
| |
| void Reset() { selected_pages_.clear(); } |
| |
| const std::string& selected_pages() const { return selected_pages_; } |
| |
| private: |
| // PaginationModelObserver overrides: |
| void SelectedPageChanged(int old_selected, int new_selected) override { |
| if (!selected_pages_.empty()) |
| selected_pages_ += ','; |
| selected_pages_ += base::NumberToString(new_selected); |
| |
| if (wait_) |
| ui_run_loop_->QuitWhenIdle(); |
| } |
| |
| std::unique_ptr<base::RunLoop> ui_run_loop_; |
| ash::PaginationModel* model_ = nullptr; |
| bool wait_ = false; |
| std::string selected_pages_; |
| |
| DISALLOW_COPY_AND_ASSIGN(PageFlipWaiter); |
| }; |
| |
| // WindowDeletionWaiter waits for the specified window to be deleted. |
| class WindowDeletionWaiter : aura::WindowObserver { |
| public: |
| explicit WindowDeletionWaiter(aura::Window* window) : window_(window) { |
| window_->AddObserver(this); |
| } |
| ~WindowDeletionWaiter() override = default; |
| |
| void Wait() { run_loop_.Run(); } |
| |
| private: |
| // WindowObserver: |
| void OnWindowDestroying(aura::Window* window) override { |
| window->RemoveObserver(this); |
| run_loop_.QuitWhenIdle(); |
| } |
| |
| base::RunLoop run_loop_; |
| aura::Window* window_; |
| |
| DISALLOW_COPY_AND_ASSIGN(WindowDeletionWaiter); |
| }; |
| |
| // Find the window with type WINDOW_TYPE_MENU and returns the firstly found one. |
| // Returns nullptr if no such window exists. |
| aura::Window* FindMenuWindow(aura::Window* root) { |
| if (root->type() == aura::client::WINDOW_TYPE_MENU) |
| return root; |
| for (auto* child : root->children()) { |
| auto* menu_in_child = FindMenuWindow(child); |
| if (menu_in_child) |
| return menu_in_child; |
| } |
| return nullptr; |
| } |
| |
| // Dragging task to be run after page flip is observed. |
| class DragAfterPageFlipTask : public ash::PaginationModelObserver { |
| public: |
| DragAfterPageFlipTask(ash::PaginationModel* model, |
| AppsGridView* view, |
| const ui::MouseEvent& drag_event) |
| : model_(model), view_(view), drag_event_(drag_event) { |
| model_->AddObserver(this); |
| } |
| |
| ~DragAfterPageFlipTask() override { model_->RemoveObserver(this); } |
| |
| private: |
| // PaginationModelObserver overrides: |
| void TotalPagesChanged(int previous_page_count, int new_page_count) override { |
| } |
| void SelectedPageChanged(int old_selected, int new_selected) override { |
| view_->UpdateDragFromItem(AppsGridView::MOUSE, drag_event_); |
| } |
| void TransitionStarted() override {} |
| void TransitionChanged() override {} |
| void TransitionEnded() override {} |
| |
| ash::PaginationModel* model_; |
| AppsGridView* view_; |
| ui::MouseEvent drag_event_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DragAfterPageFlipTask); |
| }; |
| |
| class TestSuggestedSearchResult : public TestSearchResult { |
| public: |
| TestSuggestedSearchResult() { |
| set_display_type(ash::SearchResultDisplayType::kTile); |
| set_is_recommendation(true); |
| } |
| ~TestSuggestedSearchResult() override {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TestSuggestedSearchResult); |
| }; |
| |
| } // namespace |
| |
| class AppsGridViewTest : public views::ViewsTestBase, |
| public testing::WithParamInterface<bool> { |
| public: |
| AppsGridViewTest() = default; |
| explicit AppsGridViewTest(bool create_as_tablet_mode) |
| : create_as_tablet_mode_(create_as_tablet_mode) {} |
| ~AppsGridViewTest() override = default; |
| |
| // testing::Test overrides: |
| void SetUp() override { |
| AppListView::SetShortAnimationForTesting(true); |
| if (testing::UnitTest::GetInstance()->current_test_info()->value_param()) { |
| is_rtl_ = GetParam(); |
| if (is_rtl_) |
| base::i18n::SetICUDefaultLocale("he"); |
| } |
| views::ViewsTestBase::SetUp(); |
| gfx::NativeView parent = GetContext(); |
| // Ensure that parent is big enough to show the full AppListView. |
| parent->SetBounds(gfx::Rect(gfx::Point(0, 0), gfx::Size(1024, 768))); |
| delegate_ = std::make_unique<AppListTestViewDelegate>(); |
| app_list_view_ = new AppListView(delegate_.get()); |
| app_list_view_->InitView( |
| create_as_tablet_mode_, parent, |
| base::BindRepeating(&UpdateActivationForAppListView, app_list_view_, |
| create_as_tablet_mode_)); |
| app_list_view_->Show(false /*is_side_shelf*/, create_as_tablet_mode_); |
| contents_view_ = app_list_view_->app_list_main_view()->contents_view(); |
| apps_grid_view_ = contents_view_->GetAppsContainerView()->apps_grid_view(); |
| app_list_view_->GetWidget()->Show(); |
| |
| model_ = delegate_->GetTestModel(); |
| search_model_ = delegate_->GetSearchModel(); |
| suggestions_container_ = contents_view_->GetAppsContainerView() |
| ->suggestion_chip_container_view_for_test(); |
| expand_arrow_view_ = contents_view_->expand_arrow_view(); |
| for (size_t i = 0; i < kNumOfSuggestedApps; ++i) { |
| search_model_->results()->Add( |
| std::make_unique<TestSuggestedSearchResult>()); |
| } |
| // Needed to update suggestions from |model_|. |
| suggestions_container_->Update(); |
| app_list_view_->SetState(ash::AppListViewState::kFullscreenAllApps); |
| app_list_view_->Layout(); |
| |
| test_api_ = std::make_unique<AppsGridViewTestApi>(apps_grid_view_); |
| ash::PresentationTimeRecorder::SetReportPresentationTimeImmediatelyForTest( |
| true); |
| } |
| void TearDown() override { |
| ash::PresentationTimeRecorder::SetReportPresentationTimeImmediatelyForTest( |
| false); |
| app_list_view_->GetWidget()->Close(); |
| views::ViewsTestBase::TearDown(); |
| AppListView::SetShortAnimationForTesting(false); |
| } |
| |
| protected: |
| AppListItemView* GetItemViewAt(int index) const { |
| return static_cast<AppListItemView*>(test_api_->GetViewAtModelIndex(index)); |
| } |
| |
| AppListItemView* GetItemViewForPoint(const gfx::Point& point) const { |
| for (size_t i = 0; i < model_->top_level_item_list()->item_count(); ++i) { |
| AppListItemView* view = GetItemViewAt(i); |
| gfx::Point view_origin = view->origin(); |
| views::View::ConvertPointToTarget(view->parent(), apps_grid_view_, |
| &view_origin); |
| if (gfx::Rect(view_origin, view->size()).Contains(point)) |
| return view; |
| } |
| return nullptr; |
| } |
| |
| gfx::Rect GetItemRectOnCurrentPageAt(int row, int col) const { |
| DCHECK_GT(model_->top_level_item_list()->item_count(), 0u); |
| return test_api_->GetItemTileRectOnCurrentPageAt(row, col); |
| } |
| |
| int GetTilesPerPage(int page) const { return test_api_->TilesPerPage(page); } |
| |
| ash::PaginationModel* GetPaginationModel() const { |
| return apps_grid_view_->pagination_model(); |
| } |
| |
| AppListFolderView* app_list_folder_view() const { |
| return contents_view_->GetAppsContainerView()->app_list_folder_view(); |
| } |
| |
| // Points are in |apps_grid_view_|'s coordinates, and fixed for RTL. |
| AppListItemView* SimulateDrag(AppsGridView::Pointer pointer, |
| const gfx::Point& from, |
| const gfx::Point& to) { |
| AppListItemView* view = GetItemViewForPoint(from); |
| DCHECK(view); |
| |
| gfx::NativeWindow window = app_list_view_->GetWidget()->GetNativeWindow(); |
| gfx::Point root_from(from); |
| views::View::ConvertPointToWidget(apps_grid_view_, &root_from); |
| aura::Window::ConvertPointToTarget(window, window->GetRootWindow(), |
| &root_from); |
| // Ensure that the |root_from| point is correct if RTL. |
| root_from.set_x(apps_grid_view_->GetMirroredXInView(root_from.x())); |
| gfx::Point root_to(to); |
| views::View::ConvertPointToWidget(apps_grid_view_, &root_to); |
| aura::Window::ConvertPointToTarget(window, window->GetRootWindow(), |
| &root_to); |
| // Ensure that the |root_to| point is correct if RTL. |
| root_to.set_x(apps_grid_view_->GetMirroredXInView(root_to.x())); |
| apps_grid_view_->InitiateDrag(view, pointer, from, root_from); |
| |
| ui::MouseEvent drag_event(ui::ET_MOUSE_DRAGGED, to, root_to, |
| ui::EventTimeForNow(), 0, 0); |
| apps_grid_view_->UpdateDragFromItem(pointer, drag_event); |
| return view; |
| } |
| |
| void SimulateKeyPress(ui::KeyboardCode key_code) { |
| SimulateKeyPress(key_code, ui::EF_NONE); |
| } |
| |
| void SimulateKeyPress(ui::KeyboardCode key_code, int flags) { |
| ui::KeyEvent key_event(ui::ET_KEY_PRESSED, key_code, flags); |
| apps_grid_view_->OnKeyPressed(key_event); |
| } |
| |
| void SimulateKeyReleased(ui::KeyboardCode key_code, int flags) { |
| ui::KeyEvent key_event(ui::ET_KEY_RELEASED, key_code, flags); |
| apps_grid_view_->OnKeyReleased(key_event); |
| } |
| |
| // Points are in |apps_grid_view_|'s coordinates, and fixed for RTL. |
| ui::GestureEvent SimulateTap(const gfx::Point& location) { |
| ui::GestureEvent gesture_event( |
| apps_grid_view_->GetMirroredXInView(location.x()), location.y(), 0, |
| base::TimeTicks(), ui::GestureEventDetails(ui::ET_GESTURE_TAP)); |
| apps_grid_view_->OnGestureEvent(&gesture_event); |
| return gesture_event; |
| } |
| |
| // Tests that the order of item views in the AppsGridView is in accordance |
| // with the order in the view model. |
| void TestAppListItemViewIndice() { |
| const views::ViewModelT<AppListItemView>* view_model = |
| apps_grid_view_->view_model(); |
| DCHECK_GT(view_model->view_size(), 0); |
| views::View* items_container = apps_grid_view_->items_container_; |
| auto app_iter = items_container->FindChild(view_model->view_at(0)); |
| DCHECK(app_iter != items_container->children().cend()); |
| for (int i = 1; i < view_model->view_size(); ++i) { |
| ++app_iter; |
| ASSERT_NE(items_container->children().cend(), app_iter); |
| EXPECT_EQ(view_model->view_at(i), *app_iter); |
| } |
| } |
| |
| gfx::Point GetDragViewCenter() { |
| gfx::Point drag_view_center = |
| apps_grid_view_->drag_view()->GetLocalBounds().CenterPoint(); |
| views::View::ConvertPointToTarget(apps_grid_view_->drag_view(), |
| apps_grid_view_, &drag_view_center); |
| return drag_view_center; |
| } |
| |
| AppListView* app_list_view_ = nullptr; // Owned by native widget. |
| AppsGridView* apps_grid_view_ = nullptr; // Owned by |app_list_view_|. |
| ContentsView* contents_view_ = nullptr; // Owned by |app_list_view_|. |
| SearchResultContainerView* suggestions_container_ = |
| nullptr; // Owned by |apps_grid_view_|. |
| ExpandArrowView* expand_arrow_view_ = nullptr; // Owned by |apps_grid_view_|. |
| std::unique_ptr<AppListTestViewDelegate> delegate_; |
| AppListTestModel* model_ = nullptr; // Owned by |delegate_|. |
| SearchModel* search_model_ = nullptr; // Owned by |delegate_|. |
| std::unique_ptr<AppsGridViewTestApi> test_api_; |
| bool is_rtl_ = false; |
| bool test_with_fullscreen_ = true; |
| bool create_as_tablet_mode_ = false; |
| |
| private: |
| // Restores the locale to default when destructor is called. |
| base::test::ScopedRestoreICUDefaultLocale restore_locale_; |
| |
| // Used by AppListFolderView::UpdatePreferredBounds. |
| keyboard::KeyboardUIController keyboard_ui_controller_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AppsGridViewTest); |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, AppsGridViewTest, testing::Bool()); |
| |
| class TestAppsGridViewFolderDelegate : public AppsGridViewFolderDelegate { |
| public: |
| TestAppsGridViewFolderDelegate() = default; |
| ~TestAppsGridViewFolderDelegate() override = default; |
| |
| void ReparentItem(AppListItemView* original_drag_view, |
| const gfx::Point& drag_point_in_folder_grid, |
| bool has_native_drag) override {} |
| |
| void DispatchDragEventForReparent( |
| AppsGridView::Pointer pointer, |
| const gfx::Point& drag_point_in_folder_grid) override {} |
| |
| void DispatchEndDragEventForReparent(bool events_forwarded_to_drag_drop_host, |
| bool cancel_drag) override {} |
| |
| bool IsPointOutsideOfFolderBoundary(const gfx::Point& point) override { |
| return false; |
| } |
| |
| bool IsOEMFolder() const override { return false; } |
| |
| void SetRootLevelDragViewVisible(bool visible) override {} |
| |
| void HandleKeyboardReparent(AppListItemView* reparented_item, |
| ui::KeyboardCode key_code) override {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TestAppsGridViewFolderDelegate); |
| }; |
| |
| TEST_P(AppsGridViewTest, CreatePage) { |
| // Fully populates a page. |
| const int kPages = 1; |
| |
| EXPECT_EQ(kNumOfSuggestedApps, suggestions_container_->num_results()); |
| // For new style launcher, each page has the same number of rows. |
| const int kExpectedTilesOnFirstPage = |
| apps_grid_view_->cols() * (apps_grid_view_->rows_per_page()); |
| EXPECT_EQ(kExpectedTilesOnFirstPage, GetTilesPerPage(kPages - 1)); |
| |
| model_->PopulateApps(kPages * GetTilesPerPage(kPages - 1)); |
| EXPECT_EQ(kPages, GetPaginationModel()->total_pages()); |
| |
| // Adds one more and gets a new page created. |
| model_->CreateAndAddItem("Extra"); |
| EXPECT_EQ(kPages + 1, GetPaginationModel()->total_pages()); |
| } |
| |
| TEST_F(AppsGridViewTest, EnsureHighlightedVisible) { |
| const int kPages = 3; |
| model_->PopulateApps(GetTilesPerPage(0) + (kPages - 1) * GetTilesPerPage(1)); |
| EXPECT_EQ(kPages, GetPaginationModel()->total_pages()); |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| |
| // Highlight first one and last one one first page and first page should be |
| // selected. |
| model_->HighlightItemAt(0); |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| model_->HighlightItemAt(GetTilesPerPage(0) - 1); |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| |
| // Highlight first one on 2nd page and 2nd page should be selected. |
| model_->HighlightItemAt(GetTilesPerPage(1) + 1); |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| |
| // Highlight last one in the model and last page should be selected. |
| model_->HighlightItemAt(model_->top_level_item_list()->item_count() - 1); |
| EXPECT_EQ(kPages - 1, GetPaginationModel()->selected_page()); |
| } |
| |
| TEST_F(AppsGridViewTest, RemoveSelectedLastApp) { |
| const int kTotalItems = 2; |
| const int kLastItemIndex = kTotalItems - 1; |
| |
| model_->PopulateApps(kTotalItems); |
| |
| AppListItemView* last_view = GetItemViewAt(kLastItemIndex); |
| apps_grid_view_->SetSelectedView(last_view); |
| model_->DeleteItem(model_->GetItemName(kLastItemIndex)); |
| |
| EXPECT_FALSE(apps_grid_view_->IsSelectedView(last_view)); |
| |
| // No crash happens. |
| AppListItemView* view = GetItemViewAt(0); |
| apps_grid_view_->SetSelectedView(view); |
| EXPECT_TRUE(apps_grid_view_->IsSelectedView(view)); |
| } |
| |
| // Tests that UMA is properly collected when either a suggested or normal app is |
| // launched. |
| TEST_F(AppsGridViewTest, UMATestForLaunchingApps) { |
| base::HistogramTester histogram_tester; |
| model_->PopulateApps(5); |
| |
| // Select the first app in grid and launch it. |
| contents_view_->GetAppListMainView()->ActivateApp(GetItemViewAt(0)->item(), |
| 0); |
| |
| // Test that histogram recorded app launch from grid. |
| histogram_tester.ExpectBucketCount( |
| "Apps.AppListAppLaunchedV2.FullscreenAllApps", 1 /* kAppListItem */, |
| 1 /* Times kAppListItem launched */); |
| |
| // Launch a suggested app. |
| suggestions_container_->children().front()->OnKeyPressed( |
| ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, ui::EF_NONE)); |
| |
| // Test that histogram recorded app launched from suggestion chip. |
| histogram_tester.ExpectBucketCount( |
| "Apps.AppListAppLaunchedV2.FullscreenAllApps", 2 /* kSuggestionChip */, |
| 1 /* Times kSuggestionChip Launched */); |
| } |
| |
| // Tests that the item list changed without user operations; this happens on |
| // active user switch. See https://crbug.com/980082. |
| TEST_F(AppsGridViewTest, MoveItemAcrossRowDoesNotCauseCrash) { |
| const int cols = apps_grid_view_->cols(); |
| ASSERT_LE(0, cols); |
| model_->PopulateApps(cols * 2); |
| |
| AppListItemView* view0 = GetItemViewAt(0); |
| model_->top_level_item_list()->MoveItem(0, cols + 2); |
| |
| // Make sure the logical location of the view. |
| EXPECT_NE(view0, GetItemViewAt(0)); |
| EXPECT_EQ(view0, GetItemViewAt(cols + 2)); |
| |
| // |view0| should be animating with layer. |
| EXPECT_TRUE(view0->layer()); |
| |
| test_api_->WaitForItemMoveAnimationDone(); |
| // |view0| layer should be cleared after the animation. |
| EXPECT_FALSE(view0->layer()); |
| EXPECT_EQ(view0->bounds(), GetItemRectOnCurrentPageAt(1, 2)); |
| } |
| |
| TEST_F(AppsGridViewTest, MoveItemAcrossRowDoesNotCauseAnimation) { |
| const int cols = apps_grid_view_->cols(); |
| ASSERT_LE(0, cols); |
| model_->PopulateApps(cols * 2); |
| |
| apps_grid_view_->GetWidget()->Hide(); |
| |
| AppListItemView* view0 = GetItemViewAt(0); |
| model_->top_level_item_list()->MoveItem(0, cols + 2); |
| |
| // Make sure the logical location of the view. |
| EXPECT_NE(view0, GetItemViewAt(0)); |
| EXPECT_EQ(view0, GetItemViewAt(cols + 2)); |
| |
| // The item should be repositioned immediately when the widget is not visible. |
| EXPECT_FALSE(view0->layer()); |
| EXPECT_EQ(view0->bounds(), GetItemRectOnCurrentPageAt(1, 2)); |
| } |
| |
| // Tests that control + arrow while a suggested chip is focused does not crash. |
| TEST_F(AppsGridViewTest, ControlArrowOnSuggestedChip) { |
| model_->PopulateApps(5); |
| suggestions_container_->children().front()->RequestFocus(); |
| |
| SimulateKeyPress(ui::VKEY_UP, ui::EF_CONTROL_DOWN); |
| |
| EXPECT_EQ(suggestions_container_->children().front(), |
| apps_grid_view_->GetFocusManager()->GetFocusedView()); |
| } |
| |
| TEST_F(AppsGridViewTest, ItemLabelShortNameOverride) { |
| // If the app's full name and short name differ, the title label's tooltip |
| // should always be the full name of the app. |
| std::string expected_text("xyz"); |
| std::string expected_tooltip("tooltip"); |
| AppListItem* item = model_->CreateAndAddItem("Item with short name"); |
| model_->SetItemNameAndShortName(item, expected_tooltip, expected_text); |
| |
| AppListItemView* item_view = GetItemViewAt(0); |
| ASSERT_TRUE(item_view); |
| const views::Label* title_label = item_view->title(); |
| EXPECT_EQ(base::ASCIIToUTF16(expected_tooltip), |
| item_view->GetTooltipText(title_label->bounds().CenterPoint())); |
| EXPECT_EQ(base::ASCIIToUTF16(expected_text), title_label->GetText()); |
| } |
| |
| TEST_F(AppsGridViewTest, ItemLabelNoShortName) { |
| // If the app's full name and short name are the same, use the default tooltip |
| // behavior of the label (only show a tooltip if the title is truncated). |
| std::string title("a"); |
| AppListItem* item = model_->CreateAndAddItem(title); |
| model_->SetItemNameAndShortName(item, title, ""); |
| |
| AppListItemView* item_view = GetItemViewAt(0); |
| ASSERT_TRUE(item_view); |
| const views::Label* title_label = item_view->title(); |
| EXPECT_TRUE( |
| title_label->GetTooltipText(title_label->bounds().CenterPoint()).empty()); |
| EXPECT_EQ(base::ASCIIToUTF16(title), title_label->GetText()); |
| } |
| |
| TEST_P(AppsGridViewTest, ScrollSequenceHandledByAppListView) { |
| base::HistogramTester histogram_tester; |
| |
| model_->PopulateApps(GetTilesPerPage(0) + 1); |
| EXPECT_EQ(2, GetPaginationModel()->total_pages()); |
| |
| gfx::Point apps_grid_view_origin = |
| apps_grid_view_->GetBoundsInScreen().origin(); |
| ui::GestureEvent scroll_begin( |
| apps_grid_view_origin.x(), apps_grid_view_origin.y(), 0, |
| base::TimeTicks(), |
| ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, 0, 1)); |
| ui::GestureEvent scroll_update( |
| apps_grid_view_origin.x(), apps_grid_view_origin.y(), 0, |
| base::TimeTicks(), |
| ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, 0, 10)); |
| ui::GestureEvent scroll_end(apps_grid_view_origin.x(), |
| apps_grid_view_origin.y(), 0, base::TimeTicks(), |
| ui::GestureEventDetails(ui::ET_GESTURE_END)); |
| |
| // Drag down on the app grid when on page 1, this should move the AppListView |
| // and not move the AppsGridView. |
| apps_grid_view_->OnGestureEvent(&scroll_begin); |
| EXPECT_FALSE(scroll_begin.handled()); |
| |
| // Simulate redirecting the event to app list view through views hierarchy. |
| app_list_view_->OnGestureEvent(&scroll_begin); |
| EXPECT_TRUE(scroll_begin.handled()); |
| histogram_tester.ExpectTotalCount( |
| "Apps.StateTransition.Drag.PresentationTime.ClamshellMode", 0); |
| |
| // The following scroll update events will be sent to the view that handled |
| // the scroll begin event. |
| app_list_view_->OnGestureEvent(&scroll_update); |
| EXPECT_TRUE(scroll_update.handled()); |
| ASSERT_TRUE(app_list_view_->is_in_drag()); |
| ASSERT_EQ(0, GetPaginationModel()->transition().progress); |
| histogram_tester.ExpectTotalCount( |
| "Apps.StateTransition.Drag.PresentationTime.ClamshellMode", 1); |
| histogram_tester.ExpectTotalCount( |
| "Apps.StateTransition.Drag.PresentationTime.MaxLatency.ClamshellMode", 0); |
| |
| app_list_view_->OnGestureEvent(&scroll_end); |
| EXPECT_TRUE(scroll_end.handled()); |
| |
| histogram_tester.ExpectTotalCount( |
| "Apps.StateTransition.Drag.PresentationTime.ClamshellMode", 1); |
| histogram_tester.ExpectTotalCount( |
| "Apps.StateTransition.Drag.PresentationTime.MaxLatency.ClamshellMode", 1); |
| } |
| |
| TEST_F(AppsGridViewTest, |
| OnGestureEventScrollSequenceHandleByPaginationController) { |
| base::HistogramTester histogram_tester; |
| |
| model_->PopulateApps(GetTilesPerPage(0) + 1); |
| EXPECT_EQ(2, GetPaginationModel()->total_pages()); |
| |
| gfx::Point apps_grid_view_origin = |
| apps_grid_view_->GetBoundsInScreen().origin(); |
| ui::GestureEvent scroll_begin( |
| apps_grid_view_origin.x(), apps_grid_view_origin.y(), 0, |
| base::TimeTicks(), |
| ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, 0, -1)); |
| ui::GestureEvent scroll_update( |
| apps_grid_view_origin.x(), apps_grid_view_origin.y(), 0, |
| base::TimeTicks(), |
| ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, 0, -10)); |
| ui::GestureEvent scroll_end( |
| apps_grid_view_origin.x(), apps_grid_view_origin.y(), 0, |
| base::TimeTicks(), ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END)); |
| |
| // Drag up on the app grid when on page 1, this should move the AppsGridView |
| // but not the AppListView. |
| apps_grid_view_->OnGestureEvent(&scroll_begin); |
| EXPECT_TRUE(scroll_begin.handled()); |
| histogram_tester.ExpectTotalCount( |
| "Apps.PaginationTransition.DragScroll.PresentationTime.ClamshellMode", 0); |
| |
| apps_grid_view_->OnGestureEvent(&scroll_update); |
| EXPECT_TRUE(scroll_update.handled()); |
| ASSERT_FALSE(app_list_view_->is_in_drag()); |
| ASSERT_NE(0, GetPaginationModel()->transition().progress); |
| histogram_tester.ExpectTotalCount( |
| "Apps.PaginationTransition.DragScroll.PresentationTime.ClamshellMode", 1); |
| histogram_tester.ExpectTotalCount( |
| "Apps.PaginationTransition.DragScroll.PresentationTime.MaxLatency." |
| "ClamshellMode", |
| 0); |
| |
| apps_grid_view_->OnGestureEvent(&scroll_end); |
| |
| histogram_tester.ExpectTotalCount( |
| "Apps.PaginationTransition.DragScroll.PresentationTime.MaxLatency." |
| "ClamshellMode", |
| 1); |
| } |
| |
| TEST_F(AppsGridViewTest, CloseFolderByClickingBackground) { |
| AppsContainerView* apps_container_view = |
| contents_view_->GetAppsContainerView(); |
| |
| const size_t kTotalItems = |
| AppListConfig::instance().max_folder_items_per_page(); |
| model_->CreateAndPopulateFolderWithApps(kTotalItems); |
| EXPECT_EQ(1u, model_->top_level_item_list()->item_count()); |
| EXPECT_EQ(AppListFolderItem::kItemType, |
| model_->top_level_item_list()->item_at(0)->GetItemType()); |
| |
| // Open the folder. |
| test_api_->PressItemAt(0); |
| EXPECT_TRUE(apps_container_view->IsInFolderView()); |
| |
| // Simulate mouse press on folder background to close the folder. |
| ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| apps_container_view->folder_background_view()->OnMouseEvent(&event); |
| EXPECT_FALSE(apps_container_view->IsInFolderView()); |
| } |
| |
| // Tests that taps between apps within the AppsGridView does not result in the |
| // AppList closing. |
| TEST_P(AppsGridViewTest, TapsBetweenAppsWontCloseAppList) { |
| model_->PopulateApps(2); |
| gfx::Point between_apps = GetItemRectOnCurrentPageAt(0, 0).right_center(); |
| gfx::Point empty_space = GetItemRectOnCurrentPageAt(0, 2).CenterPoint(); |
| |
| // Taps between apps should be handled to prevent them from going into |
| // app_list |
| ui::GestureEvent tap_between = SimulateTap(between_apps); |
| EXPECT_TRUE(tap_between.handled()); |
| |
| // Taps outside of occupied tiles should not be handled, that they may close |
| // the app_list |
| ui::GestureEvent tap_outside = SimulateTap(empty_space); |
| EXPECT_FALSE(tap_outside.handled()); |
| } |
| |
| TEST_F(AppsGridViewTest, PageResetAfterOpenFolder) { |
| const size_t kTotalItems = |
| AppListConfig::instance().max_folder_pages() * |
| AppListConfig::instance().max_folder_items_per_page(); |
| model_->CreateAndPopulateFolderWithApps(kTotalItems); |
| EXPECT_EQ(1u, model_->top_level_item_list()->item_count()); |
| EXPECT_EQ(AppListFolderItem::kItemType, |
| model_->top_level_item_list()->item_at(0)->GetItemType()); |
| |
| // Open the folder. It should be at page 0. |
| test_api_->PressItemAt(0); |
| ash::PaginationModel* pagination_model = |
| app_list_folder_view()->items_grid_view()->pagination_model(); |
| EXPECT_EQ(3, pagination_model->total_pages()); |
| EXPECT_EQ(0, pagination_model->selected_page()); |
| |
| // Select page 2. |
| pagination_model->SelectPage(2, false /* animate */); |
| EXPECT_EQ(2, pagination_model->selected_page()); |
| |
| // Close the folder and reopen it. It should be at page 0. |
| app_list_folder_view()->CloseFolderPage(); |
| test_api_->PressItemAt(0); |
| EXPECT_EQ(3, pagination_model->total_pages()); |
| EXPECT_EQ(0, pagination_model->selected_page()); |
| } |
| |
| TEST_F(AppsGridViewTest, FolderColsAndRows) { |
| // Populate folders with different number of apps. |
| model_->CreateAndPopulateFolderWithApps(2); |
| model_->CreateAndPopulateFolderWithApps(5); |
| model_->CreateAndPopulateFolderWithApps(9); |
| model_->CreateAndPopulateFolderWithApps(15); |
| model_->CreateAndPopulateFolderWithApps(17); |
| |
| // Check the number of cols and rows for each opened folder. |
| AppsGridView* items_grid_view = app_list_folder_view()->items_grid_view(); |
| test_api_->PressItemAt(0); |
| EXPECT_EQ(2, items_grid_view->view_model()->view_size()); |
| EXPECT_EQ(2, items_grid_view->cols()); |
| EXPECT_EQ(1, items_grid_view->rows_per_page()); |
| app_list_folder_view()->CloseFolderPage(); |
| |
| test_api_->PressItemAt(1); |
| EXPECT_EQ(5, items_grid_view->view_model()->view_size()); |
| EXPECT_EQ(3, items_grid_view->cols()); |
| EXPECT_EQ(2, items_grid_view->rows_per_page()); |
| app_list_folder_view()->CloseFolderPage(); |
| |
| test_api_->PressItemAt(2); |
| EXPECT_EQ(9, items_grid_view->view_model()->view_size()); |
| EXPECT_EQ(3, items_grid_view->cols()); |
| EXPECT_EQ(3, items_grid_view->rows_per_page()); |
| app_list_folder_view()->CloseFolderPage(); |
| |
| test_api_->PressItemAt(3); |
| EXPECT_EQ(15, items_grid_view->view_model()->view_size()); |
| EXPECT_EQ(4, items_grid_view->cols()); |
| EXPECT_EQ(4, items_grid_view->rows_per_page()); |
| app_list_folder_view()->CloseFolderPage(); |
| |
| test_api_->PressItemAt(4); |
| EXPECT_EQ(17, items_grid_view->view_model()->view_size()); |
| EXPECT_EQ(4, items_grid_view->cols()); |
| EXPECT_EQ(4, items_grid_view->rows_per_page()); |
| app_list_folder_view()->CloseFolderPage(); |
| } |
| |
| TEST_P(AppsGridViewTest, ScrollDownShouldNotExitFolder) { |
| const size_t kTotalItems = |
| AppListConfig::instance().max_folder_items_per_page(); |
| model_->CreateAndPopulateFolderWithApps(kTotalItems); |
| EXPECT_EQ(1u, model_->top_level_item_list()->item_count()); |
| EXPECT_EQ(AppListFolderItem::kItemType, |
| model_->top_level_item_list()->item_at(0)->GetItemType()); |
| |
| // Open the folder. |
| test_api_->PressItemAt(0); |
| EXPECT_TRUE(contents_view_->GetAppsContainerView()->IsInFolderView()); |
| |
| AppsGridView* items_grid_view = app_list_folder_view()->items_grid_view(); |
| gfx::Point apps_grid_view_origin = |
| items_grid_view->GetBoundsInScreen().origin(); |
| ui::GestureEvent scroll_begin( |
| apps_grid_view_origin.x(), apps_grid_view_origin.y(), 0, |
| base::TimeTicks(), |
| ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, 0, 1)); |
| ui::GestureEvent scroll_update( |
| apps_grid_view_origin.x(), apps_grid_view_origin.y(), 0, |
| base::TimeTicks(), |
| ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, 0, 10)); |
| |
| // Drag down on the items grid, this should be handled by items grid view and |
| // the folder should not be closed. |
| items_grid_view->OnGestureEvent(&scroll_begin); |
| EXPECT_TRUE(scroll_begin.handled()); |
| EXPECT_TRUE(contents_view_->GetAppsContainerView()->IsInFolderView()); |
| } |
| |
| // Tests that an app icon is selected when a menu is shown by click. |
| TEST_F(AppsGridViewTest, AppIconSelectedWhenMenuIsShown) { |
| model_->PopulateApps(1); |
| ASSERT_EQ(1u, model_->top_level_item_list()->item_count()); |
| AppListItemView* app = GetItemViewAt(0); |
| EXPECT_FALSE(apps_grid_view_->IsSelectedView(app)); |
| |
| // Send a mouse event which would show a context menu. |
| ui::MouseEvent press_event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), |
| ui::EventTimeForNow(), ui::EF_RIGHT_MOUSE_BUTTON, |
| ui::EF_RIGHT_MOUSE_BUTTON); |
| |
| // Use a views::View* to expose OnMouseEvent. |
| views::View* const view = app; |
| view->OnMouseEvent(&press_event); |
| EXPECT_TRUE(apps_grid_view_->IsSelectedView(app)); |
| |
| ui::MouseEvent release_event( |
| ui::ET_MOUSE_RELEASED, gfx::Point(), gfx::Point(), ui::EventTimeForNow(), |
| ui::EF_RIGHT_MOUSE_BUTTON, ui::EF_RIGHT_MOUSE_BUTTON); |
| view->OnMouseEvent(&release_event); |
| EXPECT_TRUE(apps_grid_view_->IsSelectedView(app)); |
| |
| // Cancel the menu, |app| should no longer be selected. |
| app->CancelContextMenu(); |
| EXPECT_FALSE(apps_grid_view_->IsSelectedView(app)); |
| } |
| |
| // Tests that the context menu for app item appears at the right position. |
| TEST_P(AppsGridViewTest, MenuAtRightPosition) { |
| const size_t kItemsInPage = |
| apps_grid_view_->cols() * apps_grid_view_->rows_per_page(); |
| const size_t kPages = 2; |
| model_->PopulateApps(kItemsInPage * kPages); |
| |
| auto* root = apps_grid_view_->GetWidget()->GetNativeWindow()->GetRootWindow(); |
| gfx::Rect root_bounds = root->GetBoundsInScreen(); |
| |
| std::vector<int> pages_to_check = {1, 0}; |
| for (int i : pages_to_check) { |
| apps_grid_view_->pagination_model()->SelectPage(i, /*animate=*/false); |
| |
| for (size_t j = 0; j < kItemsInPage; ++j) { |
| const size_t idx = kItemsInPage * i + j; |
| AppListItemView* item_view = GetItemViewAt(idx); |
| |
| // Send a mouse event which would show a context menu. |
| ui::MouseEvent press_event(ui::ET_MOUSE_PRESSED, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), |
| ui::EF_RIGHT_MOUSE_BUTTON, |
| ui::EF_RIGHT_MOUSE_BUTTON); |
| static_cast<views::View*>(item_view)->OnMouseEvent(&press_event); |
| |
| ui::MouseEvent release_event(ui::ET_MOUSE_RELEASED, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), |
| ui::EF_RIGHT_MOUSE_BUTTON, |
| ui::EF_RIGHT_MOUSE_BUTTON); |
| static_cast<views::View*>(item_view)->OnMouseEvent(&release_event); |
| |
| // Make sure that the menu is drawn on screen. |
| auto* menu_window = FindMenuWindow(root); |
| gfx::Rect menu_bounds = menu_window->GetBoundsInScreen(); |
| EXPECT_TRUE(root_bounds.Contains(menu_bounds)) |
| << "menu bounds for " << idx << "-th item " << menu_bounds.ToString() |
| << " is outside of the screen bounds " << root_bounds.ToString(); |
| |
| // CancelContextMenu doesn't remove the menu window immediately, so wait |
| // for its actual deletion. |
| WindowDeletionWaiter waiter(menu_window); |
| item_view->CancelContextMenu(); |
| waiter.Wait(); |
| } |
| } |
| } |
| |
| TEST_P(AppsGridViewTest, MouseDragItemIntoFolder) { |
| size_t kTotalItems = 3; |
| model_->PopulateApps(kTotalItems); |
| // Normally individual item-view does not have a layer. |
| for (size_t i = 0; i < model_->top_level_item_list()->item_count(); ++i) |
| EXPECT_FALSE(GetItemViewAt(i)->layer()); |
| EXPECT_EQ(model_->top_level_item_list()->item_count(), kTotalItems); |
| EXPECT_EQ(std::string("Item 0,Item 1,Item 2"), model_->GetModelContent()); |
| |
| gfx::Point from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint(); |
| gfx::Point to = GetItemRectOnCurrentPageAt(0, 0).CenterPoint(); |
| |
| // Dragging item_1 over item_0 creates a folder. |
| SimulateDrag(AppsGridView::MOUSE, from, to); |
| // Each item view has its own layer during the drag. |
| for (size_t i = 0; i < model_->top_level_item_list()->item_count(); ++i) |
| EXPECT_TRUE(GetItemViewAt(i)->layer()); |
| apps_grid_view_->EndDrag(false); |
| |
| // The layer should be destroyed after the dragging. |
| test_api_->WaitForItemMoveAnimationDone(); |
| for (size_t i = 0; i < model_->top_level_item_list()->item_count(); ++i) |
| EXPECT_FALSE(GetItemViewAt(i)->layer()); |
| EXPECT_EQ(kTotalItems - 1, model_->top_level_item_list()->item_count()); |
| EXPECT_EQ(AppListFolderItem::kItemType, |
| model_->top_level_item_list()->item_at(0)->GetItemType()); |
| AppListFolderItem* folder_item = static_cast<AppListFolderItem*>( |
| model_->top_level_item_list()->item_at(0)); |
| EXPECT_EQ(2u, folder_item->ChildItemCount()); |
| AppListItem* item_0 = model_->FindItem("Item 0"); |
| EXPECT_TRUE(item_0->IsInFolder()); |
| EXPECT_EQ(folder_item->id(), item_0->folder_id()); |
| AppListItem* item_1 = model_->FindItem("Item 1"); |
| EXPECT_TRUE(item_1->IsInFolder()); |
| EXPECT_EQ(folder_item->id(), item_1->folder_id()); |
| std::string expected_items = folder_item->id() + ",Item 2"; |
| EXPECT_EQ(expected_items, model_->GetModelContent()); |
| test_api_->LayoutToIdealBounds(); |
| |
| // Dragging item_2 to the folder adds item_2 to the folder. |
| SimulateDrag(AppsGridView::MOUSE, from, to); |
| apps_grid_view_->EndDrag(false); |
| |
| EXPECT_EQ(kTotalItems - 2, model_->top_level_item_list()->item_count()); |
| EXPECT_EQ(folder_item->id(), model_->GetModelContent()); |
| EXPECT_EQ(3u, folder_item->ChildItemCount()); |
| item_0 = model_->FindItem("Item 0"); |
| EXPECT_TRUE(item_0->IsInFolder()); |
| EXPECT_EQ(folder_item->id(), item_0->folder_id()); |
| item_1 = model_->FindItem("Item 1"); |
| EXPECT_TRUE(item_1->IsInFolder()); |
| EXPECT_EQ(folder_item->id(), item_1->folder_id()); |
| AppListItem* item_2 = model_->FindItem("Item 2"); |
| EXPECT_TRUE(item_2->IsInFolder()); |
| EXPECT_EQ(folder_item->id(), item_2->folder_id()); |
| test_api_->LayoutToIdealBounds(); |
| } |
| |
| TEST_P(AppsGridViewTest, MouseDragMaxItemsInFolder) { |
| // Create and add a folder with |kMaxFolderItemsFullscreen - 1| items. |
| const size_t kMaxItems = |
| AppListConfig::instance().max_folder_items_per_page() * |
| AppListConfig::instance().max_folder_pages(); |
| const size_t kTotalItems = kMaxItems - 1; |
| AppListFolderItem* folder_item = |
| model_->CreateAndPopulateFolderWithApps(kTotalItems); |
| EXPECT_EQ(1u, model_->top_level_item_list()->item_count()); |
| EXPECT_EQ(AppListFolderItem::kItemType, |
| model_->top_level_item_list()->item_at(0)->GetItemType()); |
| EXPECT_EQ(kTotalItems, folder_item->ChildItemCount()); |
| |
| // Create and add another 2 items. |
| model_->PopulateAppWithId(kTotalItems); |
| model_->PopulateAppWithId(kTotalItems + 1); |
| EXPECT_EQ(3u, model_->top_level_item_list()->item_count()); |
| EXPECT_EQ(folder_item->id(), model_->top_level_item_list()->item_at(0)->id()); |
| EXPECT_EQ(model_->GetItemName(kMaxItems - 1), |
| model_->top_level_item_list()->item_at(1)->id()); |
| EXPECT_EQ(model_->GetItemName(kMaxItems), |
| model_->top_level_item_list()->item_at(2)->id()); |
| |
| gfx::Point from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint(); |
| gfx::Point to = GetItemRectOnCurrentPageAt(0, 0).CenterPoint(); |
| |
| // Dragging one item into the folder, the folder should accept the item. |
| SimulateDrag(AppsGridView::MOUSE, from, to); |
| apps_grid_view_->EndDrag(false); |
| EXPECT_EQ(2u, model_->top_level_item_list()->item_count()); |
| EXPECT_EQ(folder_item->id(), model_->top_level_item_list()->item_at(0)->id()); |
| EXPECT_EQ(kMaxItems, folder_item->ChildItemCount()); |
| EXPECT_EQ(model_->GetItemName(kMaxItems), |
| model_->top_level_item_list()->item_at(1)->id()); |
| test_api_->LayoutToIdealBounds(); |
| |
| // Dragging the last item over the folder, the folder won't accept the new |
| // item. |
| SimulateDrag(AppsGridView::MOUSE, from, to); |
| apps_grid_view_->EndDrag(false); |
| EXPECT_EQ(2u, model_->top_level_item_list()->item_count()); |
| EXPECT_EQ(kMaxItems, folder_item->ChildItemCount()); |
| test_api_->LayoutToIdealBounds(); |
| } |
| |
| // Check that moving items around doesn't allow a drop to happen into a full |
| // folder. |
| TEST_P(AppsGridViewTest, MouseDragMaxItemsInFolderWithMovement) { |
| // Create and add a folder with |kMaxFolderItemsFullscreen| in it. |
| const size_t kMaxItems = |
| AppListConfig::instance().max_folder_items_per_page() * |
| AppListConfig::instance().max_folder_pages(); |
| size_t kTotalItems = kMaxItems; |
| model_->CreateAndPopulateFolderWithApps(kMaxItems); |
| EXPECT_EQ(1u, model_->top_level_item_list()->item_count()); |
| EXPECT_EQ(AppListFolderItem::kItemType, |
| model_->top_level_item_list()->item_at(0)->GetItemType()); |
| AppListFolderItem* folder_item = static_cast<AppListFolderItem*>( |
| model_->top_level_item_list()->item_at(0)); |
| EXPECT_EQ(kTotalItems, folder_item->ChildItemCount()); |
| |
| // Create and add another item. |
| model_->PopulateAppWithId(kTotalItems); |
| EXPECT_EQ(2u, model_->top_level_item_list()->item_count()); |
| EXPECT_EQ(folder_item->id(), model_->top_level_item_list()->item_at(0)->id()); |
| EXPECT_EQ(model_->GetItemName(kMaxItems), |
| model_->top_level_item_list()->item_at(1)->id()); |
| |
| AppListItemView* folder_view = |
| GetItemViewForPoint(GetItemRectOnCurrentPageAt(0, 0).CenterPoint()); |
| |
| // Drag the new item to the left so that the grid reorders. |
| gfx::Point from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint(); |
| gfx::Point to = GetItemRectOnCurrentPageAt(0, 0).bottom_left(); |
| to.Offset(0, -1); // Get a point inside the rect. |
| AppListItemView* dragged_view = SimulateDrag(AppsGridView::MOUSE, from, to); |
| test_api_->LayoutToIdealBounds(); |
| |
| // The grid now looks like | blank | folder |. |
| EXPECT_EQ(nullptr, GetItemViewForPoint( |
| GetItemRectOnCurrentPageAt(0, 0).CenterPoint())); |
| EXPECT_EQ(folder_view, GetItemViewForPoint( |
| GetItemRectOnCurrentPageAt(0, 1).CenterPoint())); |
| |
| // Move onto the folder and end the drag. |
| to = GetItemRectOnCurrentPageAt(0, 1).CenterPoint(); |
| gfx::Point translated_to = |
| gfx::PointAtOffsetFromOrigin(to - dragged_view->origin()); |
| ui::MouseEvent drag_event(ui::ET_MOUSE_DRAGGED, translated_to, to, |
| ui::EventTimeForNow(), 0, 0); |
| apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE, drag_event); |
| apps_grid_view_->EndDrag(false); |
| |
| // The item should not have moved into the folder. |
| EXPECT_EQ(2u, model_->top_level_item_list()->item_count()); |
| EXPECT_EQ(kMaxItems, folder_item->ChildItemCount()); |
| test_api_->LayoutToIdealBounds(); |
| } |
| |
| // Test reordering items via dragging. |
| TEST_P(AppsGridViewTest, MouseDragItemReorder) { |
| // The default layout is 5x4, populate 7 apps so that we have second row to |
| // test dragging item to second row. |
| model_->PopulateApps(7); |
| contents_view_->GetAppsContainerView()->Layout(); |
| EXPECT_EQ(7u, model_->top_level_item_list()->item_count()); |
| EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3,Item 4,Item 5,Item 6"), |
| model_->GetModelContent()); |
| |
| // Dragging an item towards its neighbours should not reorder until the drag |
| // is past the folder drop point. |
| gfx::Point top_right = GetItemRectOnCurrentPageAt(0, 1).CenterPoint(); |
| gfx::Vector2d drag_vector; |
| int half_tile_width = (GetItemRectOnCurrentPageAt(0, 1).x() - |
| GetItemRectOnCurrentPageAt(0, 0).x()) / |
| 2; |
| int tile_height = GetItemRectOnCurrentPageAt(1, 0).y() - |
| GetItemRectOnCurrentPageAt(0, 0).y(); |
| |
| // Drag left but stop before the folder dropping circle. |
| drag_vector.set_x(-half_tile_width - 4); |
| SimulateDrag(AppsGridView::MOUSE, top_right, top_right + drag_vector); |
| apps_grid_view_->EndDrag(false); |
| EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3,Item 4,Item 5,Item 6"), |
| model_->GetModelContent()); |
| TestAppListItemViewIndice(); |
| |
| // Drag left, past the folder dropping circle. |
| gfx::Vector2d last_drag_vector(drag_vector); |
| drag_vector.set_x(-2 * half_tile_width - |
| AppListConfig::instance().folder_dropping_circle_radius() - |
| 4); |
| SimulateDrag(AppsGridView::MOUSE, top_right + last_drag_vector, |
| top_right + drag_vector); |
| apps_grid_view_->EndDrag(false); |
| EXPECT_EQ(std::string("Item 1,Item 0,Item 2,Item 3,Item 4,Item 5,Item 6"), |
| model_->GetModelContent()); |
| TestAppListItemViewIndice(); |
| |
| // Drag down, between apps 5 and 6. The gap should open up, making space for |
| // app 1 in the bottom left. |
| last_drag_vector = drag_vector; |
| drag_vector.set_x(-half_tile_width); |
| drag_vector.set_y(tile_height); |
| SimulateDrag(AppsGridView::MOUSE, top_right + last_drag_vector, |
| top_right + drag_vector); |
| apps_grid_view_->EndDrag(false); |
| EXPECT_EQ(std::string("Item 0,Item 2,Item 3,Item 4,Item 5,Item 1,Item 6"), |
| model_->GetModelContent()); |
| TestAppListItemViewIndice(); |
| |
| // Drag up, between apps 0 and 2. The gap should open up, making space for app |
| // 1 in the top right. |
| last_drag_vector = drag_vector; |
| drag_vector.set_x(-half_tile_width); |
| drag_vector.set_y(0); |
| SimulateDrag(AppsGridView::MOUSE, top_right + last_drag_vector, |
| top_right + drag_vector); |
| apps_grid_view_->EndDrag(false); |
| EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3,Item 4,Item 5,Item 6"), |
| model_->GetModelContent()); |
| TestAppListItemViewIndice(); |
| |
| // Dragging down past the last app should reorder to the last position. |
| last_drag_vector = drag_vector; |
| drag_vector.set_x(half_tile_width); |
| drag_vector.set_y(2 * tile_height); |
| SimulateDrag(AppsGridView::MOUSE, top_right + last_drag_vector, |
| top_right + drag_vector); |
| apps_grid_view_->EndDrag(false); |
| EXPECT_EQ(std::string("Item 0,Item 2,Item 3,Item 4,Item 5,Item 6,Item 1"), |
| model_->GetModelContent()); |
| TestAppListItemViewIndice(); |
| } |
| |
| TEST_P(AppsGridViewTest, MouseDragFolderReorder) { |
| size_t kTotalItems = 2; |
| model_->CreateAndPopulateFolderWithApps(kTotalItems); |
| model_->PopulateAppWithId(kTotalItems); |
| EXPECT_EQ(2u, model_->top_level_item_list()->item_count()); |
| EXPECT_EQ(AppListFolderItem::kItemType, |
| model_->top_level_item_list()->item_at(0)->GetItemType()); |
| AppListFolderItem* folder_item = static_cast<AppListFolderItem*>( |
| model_->top_level_item_list()->item_at(0)); |
| EXPECT_EQ("Item 2", model_->top_level_item_list()->item_at(1)->id()); |
| |
| gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint(); |
| gfx::Point to = GetItemRectOnCurrentPageAt(0, 1).CenterPoint(); |
| |
| // Dragging folder over item_1 should leads to re-ordering these two |
| // items. |
| SimulateDrag(AppsGridView::MOUSE, from, to); |
| apps_grid_view_->EndDrag(false); |
| EXPECT_EQ(2u, model_->top_level_item_list()->item_count()); |
| EXPECT_EQ("Item 2", model_->top_level_item_list()->item_at(0)->id()); |
| EXPECT_EQ(folder_item->id(), model_->top_level_item_list()->item_at(1)->id()); |
| test_api_->LayoutToIdealBounds(); |
| TestAppListItemViewIndice(); |
| } |
| |
| TEST_P(AppsGridViewTest, MouseDragWithCancelDeleteAddItem) { |
| size_t kTotalItems = 4; |
| model_->PopulateApps(kTotalItems); |
| EXPECT_EQ(model_->top_level_item_list()->item_count(), kTotalItems); |
| EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"), |
| model_->GetModelContent()); |
| |
| gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint(); |
| gfx::Point to = GetItemRectOnCurrentPageAt(0, 1).CenterPoint(); |
| |
| // Canceling drag should keep existing order. |
| SimulateDrag(AppsGridView::MOUSE, from, to); |
| apps_grid_view_->EndDrag(true); |
| EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"), |
| model_->GetModelContent()); |
| test_api_->LayoutToIdealBounds(); |
| |
| // Deleting an item keeps remaining intact. |
| SimulateDrag(AppsGridView::MOUSE, from, to); |
| model_->DeleteItem(model_->GetItemName(2)); |
| apps_grid_view_->EndDrag(false); |
| EXPECT_EQ(std::string("Item 0,Item 1,Item 3"), model_->GetModelContent()); |
| test_api_->LayoutToIdealBounds(); |
| |
| // Adding a launcher item cancels the drag and respects the order. |
| SimulateDrag(AppsGridView::MOUSE, from, to); |
| model_->CreateAndAddItem("Extra"); |
| apps_grid_view_->EndDrag(false); |
| EXPECT_EQ(std::string("Item 0,Item 1,Item 3,Extra"), |
| model_->GetModelContent()); |
| test_api_->LayoutToIdealBounds(); |
| } |
| |
| // Test that control+arrow swaps app within the same page. |
| TEST_F(AppsGridViewTest, ControlArrowSwapsAppsWithinSamePage) { |
| model_->PopulateApps(GetTilesPerPage(0)); |
| |
| AppListItemView* moving_item = GetItemViewAt(0); |
| apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item); |
| |
| // Test that moving left from 0,0 does not move the app. |
| SimulateKeyPress(ui::VKEY_LEFT, ui::EF_CONTROL_DOWN); |
| |
| EXPECT_EQ(moving_item, test_api_->GetViewAtVisualIndex(0, 0)); |
| EXPECT_TRUE(apps_grid_view_->IsSelectedView(moving_item)); |
| |
| // Test that moving up from 0,0 does not move the app. |
| SimulateKeyPress(ui::VKEY_UP, ui::EF_CONTROL_DOWN); |
| |
| EXPECT_EQ(moving_item, GetItemViewAt(0)); |
| EXPECT_TRUE(apps_grid_view_->IsSelectedView(moving_item)); |
| |
| // Test that moving right from 0,0 results in a swap with the item adjacent. |
| AppListItemView* swapped_item = GetItemViewAt(1); |
| SimulateKeyPress(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN); |
| |
| EXPECT_EQ(moving_item, GetItemViewAt(1)); |
| EXPECT_EQ(swapped_item, GetItemViewAt(0)); |
| EXPECT_TRUE(apps_grid_view_->IsSelectedView(moving_item)); |
| |
| // Test that moving down from 0,1 results in a swap with the item at 1,1. |
| swapped_item = GetItemViewAt(apps_grid_view_->cols() + 1); |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| |
| EXPECT_EQ(moving_item, GetItemViewAt(apps_grid_view_->cols() + 1)); |
| EXPECT_EQ(swapped_item, GetItemViewAt(1)); |
| EXPECT_TRUE(apps_grid_view_->IsSelectedView(moving_item)); |
| |
| // Test that moving left from 1,1 results in a swap with the item at 1,0. |
| swapped_item = GetItemViewAt(apps_grid_view_->cols()); |
| SimulateKeyPress(ui::VKEY_LEFT, ui::EF_CONTROL_DOWN); |
| |
| EXPECT_EQ(moving_item, GetItemViewAt(apps_grid_view_->cols())); |
| EXPECT_EQ(swapped_item, GetItemViewAt(apps_grid_view_->cols() + 1)); |
| EXPECT_TRUE(apps_grid_view_->IsSelectedView(moving_item)); |
| |
| // Test that moving up from 1,0 results in a swap with the item at 0,0. |
| swapped_item = GetItemViewAt(0); |
| SimulateKeyPress(ui::VKEY_UP, ui::EF_CONTROL_DOWN); |
| |
| EXPECT_EQ(moving_item, GetItemViewAt(0)); |
| EXPECT_EQ(swapped_item, GetItemViewAt(apps_grid_view_->cols())); |
| EXPECT_TRUE(apps_grid_view_->IsSelectedView(moving_item)); |
| } |
| |
| // Tests that histograms are recorded when apps are moved with control+arrow. |
| TEST_F(AppsGridViewTest, ControlArrowRecordsHistogramBasic) { |
| base::HistogramTester histogram_tester; |
| model_->PopulateApps(GetTilesPerPage(0)); |
| |
| AppListItemView* moving_item = GetItemViewAt(0); |
| apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item); |
| |
| // Make one move right and expect a histogram is recorded. |
| SimulateKeyPress(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN); |
| SimulateKeyReleased(ui::VKEY_RIGHT, ui::EF_NONE); |
| |
| histogram_tester.ExpectBucketCount("Apps.AppListAppMovingType", 6, 1); |
| |
| // Make one move down and expect a histogram is recorded. |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| SimulateKeyReleased(ui::VKEY_DOWN, ui::EF_NONE); |
| |
| histogram_tester.ExpectBucketCount("Apps.AppListAppMovingType", 6, 2); |
| |
| // Make one move up and expect a histogram is recorded. |
| SimulateKeyPress(ui::VKEY_UP, ui::EF_CONTROL_DOWN); |
| SimulateKeyReleased(ui::VKEY_UP, ui::EF_NONE); |
| |
| histogram_tester.ExpectBucketCount("Apps.AppListAppMovingType", 6, 3); |
| |
| // Make one move left and expect a histogram is recorded. |
| SimulateKeyPress(ui::VKEY_LEFT, ui::EF_CONTROL_DOWN); |
| SimulateKeyReleased(ui::VKEY_LEFT, ui::EF_NONE); |
| |
| histogram_tester.ExpectBucketCount("Apps.AppListAppMovingType", 6, 4); |
| } |
| |
| // Test that histograms do not record when the keyboard move is a no-op. |
| TEST_F(AppsGridViewTest, ControlArrowDoesNotRecordHistogramWithNoOpMove) { |
| base::HistogramTester histogram_tester; |
| model_->PopulateApps(GetTilesPerPage(0)); |
| |
| AppListItemView* moving_item = GetItemViewAt(0); |
| apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item); |
| |
| // Make 2 no-op moves and one successful move from 0,0 ane expect a histogram |
| // is recorded only once. |
| SimulateKeyPress(ui::VKEY_LEFT, ui::EF_CONTROL_DOWN); |
| SimulateKeyReleased(ui::VKEY_LEFT, ui::EF_NONE); |
| |
| SimulateKeyPress(ui::VKEY_UP, ui::EF_CONTROL_DOWN); |
| SimulateKeyReleased(ui::VKEY_UP, ui::EF_NONE); |
| |
| SimulateKeyPress(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN); |
| SimulateKeyReleased(ui::VKEY_RIGHT, ui::EF_NONE); |
| |
| histogram_tester.ExpectBucketCount("Apps.AppListAppMovingType", 6, 1); |
| } |
| |
| // Tests that histograms only record once for a long move sequence. |
| TEST_F(AppsGridViewTest, ControlArrowRecordsHistogramOnceWithOneMoveSequence) { |
| base::HistogramTester histogram_tester; |
| model_->PopulateApps(GetTilesPerPage(1) * 2); |
| |
| AppListItemView* moving_item = GetItemViewAt(0); |
| apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item); |
| |
| // Make that a series of moves when the control key is left pressed and expect |
| // one histogram is recorded. |
| while (GetPaginationModel()->selected_page() != 1) { |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| SimulateKeyReleased(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| } |
| SimulateKeyReleased(ui::VKEY_DOWN, ui::EF_NONE); |
| |
| histogram_tester.ExpectBucketCount("Apps.AppListAppMovingType", 6, 1); |
| } |
| |
| // Tests that moving an app down when it is directly below a gap results in a |
| // swap with the closest item. |
| TEST_F(AppsGridViewTest, ControlArrowDownToGapOnSamePage) { |
| // Add two rows of apps, one full and one with just one app. |
| model_->PopulateApps(apps_grid_view_->cols() + 1); |
| |
| // Select the far right item. |
| AppListItemView* moving_item = GetItemViewAt(apps_grid_view_->cols() - 1); |
| AppListItemView* swapped_item = GetItemViewAt(apps_grid_view_->cols()); |
| apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item); |
| |
| // Press down to move the app to the next row. It should take the place of the |
| // app on the next row that is closes to the column of |moving_item|. |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| |
| EXPECT_EQ(moving_item, GetItemViewAt(apps_grid_view_->cols())); |
| EXPECT_EQ(swapped_item, GetItemViewAt(apps_grid_view_->cols() - 1)); |
| } |
| |
| // Tests that moving an app up/down/left/right to a full page results in the app |
| // at the destination slot moving to the source slot (ie. a swap). |
| TEST_F(AppsGridViewTest, ControlArrowSwapsBetweenFullPages) { |
| const int kPages = 3; |
| model_->PopulateApps(kPages * GetTilesPerPage(0)); |
| // For every item in the first row, ensure an upward move results in the item |
| // swapping places with the item directly above it. |
| for (int i = 0; i < apps_grid_view_->cols(); ++i) { |
| GetPaginationModel()->SelectPage(1, false /*animate*/); |
| const GridIndex moved_view_index(1, i); |
| apps_grid_view_->GetFocusManager()->SetFocusedView( |
| test_api_->GetViewAtIndex(moved_view_index)); |
| |
| const GridIndex swapped_view_index( |
| 0, |
| apps_grid_view_->cols() * (apps_grid_view_->rows_per_page() - 1) + i); |
| AppListItemView* moved_view = test_api_->GetViewAtIndex(moved_view_index); |
| AppListItemView* swapped_view = |
| test_api_->GetViewAtIndex(swapped_view_index); |
| |
| SimulateKeyPress(ui::VKEY_UP, ui::EF_CONTROL_DOWN); |
| |
| // |swapped_view| and |moved_view| should swap places when moving up to a |
| // full page. |
| EXPECT_EQ(swapped_view, test_api_->GetViewAtIndex(moved_view_index)); |
| EXPECT_EQ(moved_view, test_api_->GetViewAtIndex(swapped_view_index)); |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| } |
| |
| // For every item in the last row of a full page, ensure a downward move |
| // results in the item swapping places when the target position is occupied. |
| for (int i = 0; i < apps_grid_view_->cols(); ++i) { |
| GetPaginationModel()->SelectPage(1, false /*animate*/); |
| const GridIndex moved_view_index( |
| 0, |
| apps_grid_view_->cols() * (apps_grid_view_->rows_per_page() - 1) + i); |
| apps_grid_view_->GetFocusManager()->SetFocusedView( |
| test_api_->GetViewAtIndex(moved_view_index)); |
| |
| const GridIndex swapped_view_index(1, i); |
| AppListItemView* moved_view = test_api_->GetViewAtIndex(moved_view_index); |
| AppListItemView* swapped_view = |
| test_api_->GetViewAtIndex(swapped_view_index); |
| |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| |
| // |swapped_view| and |moved_view| should swap places when moving up to a |
| // full page. |
| EXPECT_EQ(swapped_view, test_api_->GetViewAtIndex(moved_view_index)); |
| EXPECT_EQ(moved_view, test_api_->GetViewAtIndex(swapped_view_index)); |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| } |
| |
| // For the final item on the first page, moving right to a full page should |
| // swap with the first item on the next page. |
| GetPaginationModel()->SelectPage(0, false /*animate*/); |
| GridIndex moved_view_index( |
| 0, apps_grid_view_->cols() * apps_grid_view_->rows_per_page() - 1); |
| GridIndex swapped_view_index(1, 0); |
| AppListItemView* moved_view = test_api_->GetViewAtIndex(moved_view_index); |
| AppListItemView* swapped_view = test_api_->GetViewAtIndex(swapped_view_index); |
| apps_grid_view_->GetFocusManager()->SetFocusedView( |
| test_api_->GetViewAtIndex(moved_view_index)); |
| |
| SimulateKeyPress(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN); |
| |
| EXPECT_EQ(swapped_view, test_api_->GetViewAtIndex(moved_view_index)); |
| EXPECT_EQ(moved_view, test_api_->GetViewAtIndex(swapped_view_index)); |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| |
| // For the first item on the second page, moving left to a full page should |
| // swap with the first item on the previous page. |
| swapped_view_index = moved_view_index; |
| moved_view_index = GridIndex(1, 0); |
| moved_view = test_api_->GetViewAtIndex(moved_view_index); |
| swapped_view = test_api_->GetViewAtIndex(swapped_view_index); |
| |
| SimulateKeyPress(ui::VKEY_LEFT, ui::EF_CONTROL_DOWN); |
| |
| EXPECT_EQ(swapped_view, test_api_->GetViewAtIndex(moved_view_index)); |
| EXPECT_EQ(moved_view, test_api_->GetViewAtIndex(swapped_view_index)); |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| } |
| |
| // Test that a page can be created while moving apps with the control+arrow. |
| TEST_F(AppsGridViewTest, ControlArrowDownAndRightCreatesNewPage) { |
| base::HistogramTester histogram_tester; |
| const int kTilesPerPageStart = GetTilesPerPage(0); |
| model_->PopulateApps(kTilesPerPageStart); |
| |
| // Focus the last item on the page. |
| AppListItemView* moving_item = GetItemViewAt(kTilesPerPageStart - 1); |
| apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item); |
| |
| // Test that pressing control-right creates a new page. |
| SimulateKeyPress(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN); |
| |
| histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7, 1); |
| EXPECT_EQ(moving_item, test_api_->GetViewAtIndex(GridIndex(1, 0))); |
| EXPECT_EQ(kTilesPerPageStart - 1, test_api_->AppsOnPage(0)); |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(2, GetPaginationModel()->total_pages()); |
| |
| // Reset by moving the app back to the previous page. |
| SimulateKeyPress(ui::VKEY_UP, ui::EF_CONTROL_DOWN); |
| |
| histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7, 2); |
| |
| // Test that control-down creates a new page. |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| |
| // The slot where |moving_item| originated should be empty because items get |
| // dumped on pages with room, and only swap if the destination page is full. |
| EXPECT_EQ( |
| nullptr, |
| test_api_->GetViewAtIndex(GridIndex( |
| 0, apps_grid_view_->cols() * apps_grid_view_->rows_per_page() - 1))); |
| EXPECT_EQ(moving_item, test_api_->GetViewAtIndex(GridIndex(1, 0))); |
| EXPECT_EQ(kTilesPerPageStart - 1, test_api_->AppsOnPage(0)); |
| EXPECT_EQ(1, test_api_->AppsOnPage(1)); |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(2, GetPaginationModel()->total_pages()); |
| histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7, 3); |
| } |
| |
| // Tests that a page can be deleted if a lonely app is moved down or right to |
| // another page. |
| TEST_F(AppsGridViewTest, ControlArrowUpOrLeftRemovesPage) { |
| base::HistogramTester histogram_tester; |
| // Move an app so it is by itself on page 1. |
| model_->PopulateApps(GetTilesPerPage(0)); |
| AppListItemView* moving_item = GetItemViewAt(GetTilesPerPage(0) - 1); |
| apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item); |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7, 1); |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(2, GetPaginationModel()->total_pages()); |
| |
| // Move the app up, test that the page is deleted. |
| SimulateKeyPress(ui::VKEY_UP, ui::EF_CONTROL_DOWN); |
| |
| histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7, 2); |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(1, GetPaginationModel()->total_pages()); |
| |
| // Move the app to be by itself again on page 1. |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7, 3); |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(2, GetPaginationModel()->total_pages()); |
| |
| // Move the app left, test that the page is deleted. |
| SimulateKeyPress(ui::VKEY_LEFT, ui::EF_CONTROL_DOWN); |
| |
| histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7, 4); |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(1, GetPaginationModel()->total_pages()); |
| } |
| |
| // Tests that moving a lonely app on the last page down is a no-op when there |
| // are no pages below. |
| TEST_F(AppsGridViewTest, ControlArrowDownOnLastAppOnLastPage) { |
| base::HistogramTester histogram_tester; |
| // Move an app so it is by itself on page 1. |
| model_->PopulateApps(GetTilesPerPage(0)); |
| AppListItemView* moving_item = GetItemViewAt(GetTilesPerPage(0) - 1); |
| apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item); |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| SimulateKeyReleased(ui::VKEY_DOWN, ui::EF_NONE); |
| histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7, 1); |
| histogram_tester.ExpectBucketCount("Apps.AppListAppMovingType", 6, 1); |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(2, GetPaginationModel()->total_pages()); |
| |
| // Move the app right, test that nothing changes and no histograms are |
| // recorded. |
| SimulateKeyPress(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN); |
| SimulateKeyReleased(ui::VKEY_RIGHT, ui::EF_NONE); |
| |
| histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7, 1); |
| histogram_tester.ExpectBucketCount("Apps.AppListAppMovingType", 6, 1); |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(2, GetPaginationModel()->total_pages()); |
| |
| // Move the app down, test that nothing changes and no histograms are |
| // recorded. |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| SimulateKeyReleased(ui::VKEY_DOWN, ui::EF_NONE); |
| |
| histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7, 1); |
| histogram_tester.ExpectBucketCount("Apps.AppListAppMovingType", 6, 1); |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(2, GetPaginationModel()->total_pages()); |
| } |
| |
| // Test that moving an item down or right when it is by itself on a page with a |
| // page below results in the page deletion. |
| TEST_F(AppsGridViewTest, ControlArrowDownOrRightRemovesPage) { |
| // Move an app so it is by itself on page 1, with another app on page 2. |
| model_->PopulateApps(GetTilesPerPage(0)); |
| AppListItemView* moving_item = GetItemViewAt(GetTilesPerPage(0) - 1); |
| apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item); |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| SimulateKeyPress(ui::VKEY_UP); |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| SimulateKeyPress(ui::VKEY_UP); |
| // The lonely app is selected on page 1, with a page below it containing one |
| // app. |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(3, GetPaginationModel()->total_pages()); |
| |
| // Test that moving the app on page 1 down, deletes the second page and |
| // creates a final page with 2 apps. |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(2, GetPaginationModel()->total_pages()); |
| EXPECT_EQ(2, test_api_->AppsOnPage(1)); |
| |
| // Create a third page, with an app by itself. |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| SimulateKeyPress(ui::VKEY_UP); |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(3, GetPaginationModel()->total_pages()); |
| |
| // Test that moving the app right moves the selected app to the third page, |
| // and the second page is deleted. |
| SimulateKeyPress(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN); |
| |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(2, GetPaginationModel()->total_pages()); |
| EXPECT_EQ(2, test_api_->AppsOnPage(1)); |
| } |
| |
| // Tests that control + shift + arrow puts |selected_item_| into a folder or |
| // creates a folder if one does not exist. |
| TEST_F(AppsGridViewTest, ControlShiftArrowFoldersItemBasic) { |
| base::HistogramTester histogram_tester; |
| model_->PopulateApps(GetTilesPerPage(0)); |
| // Select the first item in the grid, folder it with the item to the right. |
| AppListItemView* first_item = GetItemViewAt(0); |
| const std::string first_item_id = first_item->item()->id(); |
| const std::string second_item_id = GetItemViewAt(1)->item()->id(); |
| apps_grid_view_->GetFocusManager()->SetFocusedView(first_item); |
| |
| SimulateKeyPress(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| |
| // Test that the first item in the grid is now a folder with the first and |
| // second items, and that the folder is the selected view. |
| AppListItemView* new_folder = GetItemViewAt(0); |
| ASSERT_TRUE(apps_grid_view_->IsSelectedView(new_folder)); |
| AppListFolderItem* folder_item = |
| static_cast<AppListFolderItem*>(new_folder->item()); |
| EXPECT_TRUE(folder_item->is_folder()); |
| EXPECT_EQ(2u, folder_item->ChildItemCount()); |
| EXPECT_TRUE(folder_item->FindChildItem(first_item_id)); |
| EXPECT_TRUE(folder_item->FindChildItem(second_item_id)); |
| histogram_tester.ExpectBucketCount(kAppListAppMovingType, |
| kMoveByKeyboardIntoFolder, 1); |
| |
| // Test that, when a folder is selected, control+shift+arrow does nothing. |
| SimulateKeyPress(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| |
| EXPECT_TRUE(apps_grid_view_->IsSelectedView(new_folder)); |
| EXPECT_EQ(2u, folder_item->ChildItemCount()); |
| histogram_tester.ExpectBucketCount(kAppListAppMovingType, |
| kMoveByKeyboardIntoFolder, 1); |
| |
| // Move selection to the item to the right of the folder and put it in the |
| // folder. |
| apps_grid_view_->GetFocusManager()->SetFocusedView(GetItemViewAt(1)); |
| |
| SimulateKeyPress(ui::VKEY_LEFT, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| |
| EXPECT_TRUE(apps_grid_view_->IsSelectedView(new_folder)); |
| EXPECT_EQ(3u, folder_item->ChildItemCount()); |
| histogram_tester.ExpectBucketCount(kAppListAppMovingType, |
| kMoveByKeyboardIntoFolder, 2); |
| |
| // Move selection to the item below the folder and put it in the folder. |
| SimulateKeyPress(ui::VKEY_DOWN); |
| SimulateKeyPress(ui::VKEY_UP, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| |
| EXPECT_TRUE(apps_grid_view_->IsSelectedView(new_folder)); |
| EXPECT_EQ(4u, folder_item->ChildItemCount()); |
| histogram_tester.ExpectBucketCount(kAppListAppMovingType, |
| kMoveByKeyboardIntoFolder, 3); |
| |
| // Move the folder to the second row, then put the item above the folder in |
| // the folder. |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| SimulateKeyPress(ui::VKEY_UP); |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| |
| EXPECT_TRUE(apps_grid_view_->IsSelectedView(new_folder)); |
| EXPECT_EQ(5u, folder_item->ChildItemCount()); |
| histogram_tester.ExpectBucketCount(kAppListAppMovingType, |
| kMoveByKeyboardIntoFolder, 4); |
| } |
| |
| // Tests that foldering an item that is on a different page fails. |
| TEST_F(AppsGridViewTest, ControlShiftArrowFailsToFolderAcrossPages) { |
| model_->PopulateApps(2 * GetTilesPerPage(0)); |
| |
| // For every item on the last row of the first page, test that foldering to |
| // the next page fails. |
| for (int i = 0; i < apps_grid_view_->cols(); ++i) { |
| const GridIndex moved_view_index( |
| 0, |
| apps_grid_view_->cols() * (apps_grid_view_->rows_per_page() - 1) + i); |
| AppListItemView* attempted_folder_view = |
| test_api_->GetViewAtIndex(moved_view_index); |
| apps_grid_view_->GetFocusManager()->SetFocusedView(attempted_folder_view); |
| |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| |
| EXPECT_EQ(attempted_folder_view, |
| test_api_->GetViewAtIndex(moved_view_index)); |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| } |
| // The last item on the col is selected, try moving right and test that that |
| // fails as well. |
| GridIndex moved_view_index( |
| 0, apps_grid_view_->cols() * apps_grid_view_->rows_per_page() - 1); |
| AppListItemView* attempted_folder_view = |
| test_api_->GetViewAtIndex(moved_view_index); |
| |
| SimulateKeyPress(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| |
| EXPECT_EQ(attempted_folder_view, test_api_->GetViewAtIndex(moved_view_index)); |
| |
| // Move to the second page and test that foldering up to a new page fails. |
| SimulateKeyPress(ui::VKEY_DOWN); |
| |
| // Select the first item on the second page. |
| moved_view_index = GridIndex(1, 0); |
| attempted_folder_view = test_api_->GetViewAtIndex(moved_view_index); |
| |
| // Try to folder left to the previous page, it should fail. |
| SimulateKeyPress(ui::VKEY_LEFT, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| |
| EXPECT_EQ(attempted_folder_view, test_api_->GetViewAtIndex(moved_view_index)); |
| |
| // For every item on the first row of the second page, test that foldering to |
| // the next page fails. |
| for (int i = 0; i < apps_grid_view_->cols(); ++i) { |
| const GridIndex moved_view_index(1, i); |
| AppListItemView* attempted_folder_view = |
| test_api_->GetViewAtIndex(moved_view_index); |
| apps_grid_view_->GetFocusManager()->SetFocusedView(attempted_folder_view); |
| |
| SimulateKeyPress(ui::VKEY_UP, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| |
| EXPECT_EQ(attempted_folder_view, |
| test_api_->GetViewAtIndex(moved_view_index)); |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| } |
| } |
| |
| // Tests that foldering the item on the last slot of a page doesn't crash. |
| TEST_F(AppsGridViewTest, ControlShiftArrowFolderLastItemOnPage) { |
| const int kNumberOfApps = 4; |
| model_->PopulateApps(kNumberOfApps); |
| // Select the second to last item in the grid, folder it with the item to the |
| // right. |
| AppListItemView* moving_item = GetItemViewAt(kNumberOfApps - 2); |
| const std::string first_item_id = moving_item->item()->id(); |
| const std::string second_item_id = |
| GetItemViewAt(kNumberOfApps - 1)->item()->id(); |
| apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item); |
| |
| SimulateKeyPress(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| |
| // Test that the first item in the grid is now a folder with the first and |
| // second items, and that the folder is the selected view. |
| AppListItemView* new_folder = GetItemViewAt(kNumberOfApps - 2); |
| ASSERT_TRUE(apps_grid_view_->IsSelectedView(new_folder)); |
| AppListFolderItem* folder_item = |
| static_cast<AppListFolderItem*>(new_folder->item()); |
| EXPECT_TRUE(folder_item->is_folder()); |
| EXPECT_EQ(2u, folder_item->ChildItemCount()); |
| EXPECT_TRUE(folder_item->FindChildItem(first_item_id)); |
| EXPECT_TRUE(folder_item->FindChildItem(second_item_id)); |
| } |
| |
| TEST_P(AppsGridViewTest, MouseDragFlipPage) { |
| apps_grid_view_->set_page_flip_delay_in_ms_for_testing(10); |
| GetPaginationModel()->SetTransitionDurations( |
| base::TimeDelta::FromMilliseconds(10), |
| base::TimeDelta::FromMilliseconds(10)); |
| |
| PageFlipWaiter page_flip_waiter(GetPaginationModel()); |
| |
| const int kPages = 3; |
| model_->PopulateApps(GetTilesPerPage(0) + (kPages - 1) * GetTilesPerPage(1)); |
| EXPECT_EQ(kPages, GetPaginationModel()->total_pages()); |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| |
| gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint(); |
| gfx::Point to; |
| const gfx::Rect apps_grid_bounds = apps_grid_view_->GetLocalBounds(); |
| to = gfx::Point(apps_grid_bounds.width() / 2, apps_grid_bounds.bottom() + 1); |
| |
| // For fullscreen/bubble launcher, drag to the bottom/right of bounds. |
| page_flip_waiter.Reset(); |
| SimulateDrag(AppsGridView::MOUSE, from, to); |
| |
| EXPECT_EQ(to, GetDragViewCenter()); |
| |
| // Page should be flipped after sometime to hit page 1 and 2 then stop. |
| while (test_api_->HasPendingPageFlip()) { |
| page_flip_waiter.Wait(); |
| } |
| |
| // When apps grid gap is enabled, the user can drag an item to an extra page |
| // created at the end. |
| EXPECT_EQ("1,2,3", page_flip_waiter.selected_pages()); |
| EXPECT_EQ(3, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(to, GetDragViewCenter()); |
| |
| // Cancel drag and put the dragged view back to its ideal position so that |
| // the next drag would pick it up. |
| apps_grid_view_->EndDrag(true); |
| test_api_->LayoutToIdealBounds(); |
| |
| // Now drag to the top edge, and test the other direction. |
| to.set_y(apps_grid_bounds.y()); |
| |
| page_flip_waiter.Reset(); |
| SimulateDrag(AppsGridView::MOUSE, from, to); |
| |
| EXPECT_EQ(to, GetDragViewCenter()); |
| |
| while (test_api_->HasPendingPageFlip()) { |
| page_flip_waiter.Wait(); |
| } |
| |
| EXPECT_EQ("1,0", page_flip_waiter.selected_pages()); |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(to, GetDragViewCenter()); |
| |
| apps_grid_view_->EndDrag(true); |
| } |
| |
| TEST_F(AppsGridViewTest, UpdateFolderBackgroundOnCancelDrag) { |
| const int kTotalItems = 4; |
| TestAppsGridViewFolderDelegate folder_delegate; |
| apps_grid_view_->set_folder_delegate(&folder_delegate); |
| model_->PopulateApps(kTotalItems); |
| EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"), |
| model_->GetModelContent()); |
| |
| gfx::Point mouse_from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint(); |
| gfx::Point mouse_to = GetItemRectOnCurrentPageAt(0, 1).CenterPoint(); |
| |
| // Starts a mouse drag and then cancels it. |
| SimulateDrag(AppsGridView::MOUSE, mouse_from, mouse_to); |
| apps_grid_view_->EndDrag(true); |
| EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"), |
| model_->GetModelContent()); |
| } |
| |
| // Test focus change before and after dragging an item. (See |
| // https://crbug.com/834682) |
| TEST_F(AppsGridViewTest, FocusOfDraggedView) { |
| model_->PopulateApps(1); |
| contents_view_->GetAppsContainerView()->Layout(); |
| auto* search_box = contents_view_->GetSearchBoxView()->search_box(); |
| auto* item_view = apps_grid_view_->view_model()->view_at(0); |
| EXPECT_TRUE(search_box->HasFocus()); |
| EXPECT_FALSE(item_view->HasFocus()); |
| |
| // Dragging the item towards its right. |
| const gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint(); |
| const gfx::Point to = GetItemRectOnCurrentPageAt(0, 1).CenterPoint(); |
| SimulateDrag(AppsGridView::MOUSE, from, to); |
| EXPECT_FALSE(search_box->HasFocus()); |
| EXPECT_TRUE(item_view->HasFocus()); |
| |
| apps_grid_view_->EndDrag(false); |
| EXPECT_FALSE(search_box->HasFocus()); |
| EXPECT_TRUE(item_view->HasFocus()); |
| } |
| |
| class AppsGridViewTabletTest : public AppsGridViewTest { |
| public: |
| AppsGridViewTabletTest() : AppsGridViewTest(/*is_in_tablet=*/true) {} |
| ~AppsGridViewTabletTest() override = default; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AppsGridViewTabletTest); |
| }; |
| |
| TEST_P(AppsGridViewTabletTest, Basic) { |
| base::HistogramTester histogram_tester; |
| |
| model_->PopulateApps(GetTilesPerPage(0) + 1); |
| EXPECT_EQ(2, GetPaginationModel()->total_pages()); |
| |
| gfx::Point apps_grid_view_origin = |
| apps_grid_view_->GetBoundsInScreen().origin(); |
| ui::GestureEvent scroll_begin( |
| apps_grid_view_origin.x(), apps_grid_view_origin.y(), 0, |
| base::TimeTicks(), |
| ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, 0, -1)); |
| ui::GestureEvent scroll_update( |
| apps_grid_view_origin.x(), apps_grid_view_origin.y(), 0, |
| base::TimeTicks(), |
| ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, 0, -10)); |
| ui::GestureEvent scroll_end( |
| apps_grid_view_origin.x(), apps_grid_view_origin.y(), 0, |
| base::TimeTicks(), ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END)); |
| |
| // Drag up on the app grid when on page 1, this should move the AppsGridView |
| // but not the AppListView. |
| apps_grid_view_->OnGestureEvent(&scroll_begin); |
| EXPECT_TRUE(scroll_begin.handled()); |
| histogram_tester.ExpectTotalCount( |
| "Apps.PaginationTransition.DragScroll.PresentationTime.TabletMode", 0); |
| |
| apps_grid_view_->OnGestureEvent(&scroll_update); |
| EXPECT_TRUE(scroll_update.handled()); |
| ASSERT_FALSE(app_list_view_->is_in_drag()); |
| ASSERT_NE(0, GetPaginationModel()->transition().progress); |
| histogram_tester.ExpectTotalCount( |
| "Apps.PaginationTransition.DragScroll.PresentationTime.TabletMode", 1); |
| histogram_tester.ExpectTotalCount( |
| "Apps.PaginationTransition.DragScroll.PresentationTime.MaxLatency." |
| "TabletMode", |
| 0); |
| |
| apps_grid_view_->OnGestureEvent(&scroll_end); |
| |
| histogram_tester.ExpectTotalCount( |
| "Apps.PaginationTransition.DragScroll.PresentationTime.TabletMode", 1); |
| histogram_tester.ExpectTotalCount( |
| "Apps.PaginationTransition.DragScroll.PresentationTime.MaxLatency." |
| "TabletMode", |
| 1); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, AppsGridViewTabletTest, testing::Bool()); |
| |
| // Test various dragging behaviors only allowed when apps grid gap (part of |
| // home launcher feature) is enabled. |
| class AppsGridGapTest : public AppsGridViewTest { |
| public: |
| AppsGridGapTest() = default; |
| ~AppsGridGapTest() override = default; |
| |
| // testing::Test overrides: |
| void SetUp() override { |
| AppsGridViewTest::SetUp(); |
| apps_grid_view_->set_page_flip_delay_in_ms_for_testing(10); |
| GetPaginationModel()->SetTransitionDurations( |
| base::TimeDelta::FromMilliseconds(10), |
| base::TimeDelta::FromMilliseconds(10)); |
| page_flip_waiter_ = std::make_unique<PageFlipWaiter>(GetPaginationModel()); |
| } |
| |
| void TearDown() override { |
| page_flip_waiter_.reset(); |
| AppsGridViewTest::TearDown(); |
| } |
| |
| protected: |
| // Simulate drag from the |from| point to either next or previous page's |to| |
| // point. |
| void SimulateDragToNeighborPage(bool next_page, |
| const gfx::Point& from, |
| const gfx::Point& to) { |
| const int selected_page = GetPaginationModel()->selected_page(); |
| DCHECK(selected_page >= 0 && |
| selected_page <= GetPaginationModel()->total_pages()); |
| |
| // Calculate the point required to flip the page if an item is dragged to |
| // it. |
| const gfx::Rect apps_grid_bounds = apps_grid_view_->GetLocalBounds(); |
| gfx::Point point_in_page_flip_buffer = |
| gfx::Point(apps_grid_bounds.width() / 2, |
| next_page ? apps_grid_bounds.bottom() + 1 : 0); |
| |
| // Build the drag event which will be triggered after page flip. |
| gfx::Point root_to(to); |
| views::View::ConvertPointToWidget(apps_grid_view_, &root_to); |
| gfx::NativeWindow window = app_list_view_->GetWidget()->GetNativeWindow(); |
| aura::Window::ConvertPointToTarget(window, window->GetRootWindow(), |
| &root_to); |
| root_to.set_x(apps_grid_view_->GetMirroredXInView(root_to.x())); |
| ui::MouseEvent drag_event(ui::ET_MOUSE_DRAGGED, to, root_to, |
| ui::EventTimeForNow(), 0, 0); |
| |
| // Start dragging and relayout apps grid view after drag ends. |
| DragAfterPageFlipTask task(GetPaginationModel(), apps_grid_view_, |
| drag_event); |
| page_flip_waiter_->Reset(); |
| SimulateDrag(AppsGridView::MOUSE, from, point_in_page_flip_buffer); |
| while (test_api_->HasPendingPageFlip()) { |
| page_flip_waiter_->Wait(); |
| } |
| apps_grid_view_->EndDrag(false); |
| test_api_->LayoutToIdealBounds(); |
| } |
| |
| std::unique_ptr<PageFlipWaiter> page_flip_waiter_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AppsGridGapTest); |
| }; |
| |
| TEST_P(AppsGridGapTest, MoveAnItemToNewEmptyPage) { |
| const int kApps = 2; |
| model_->PopulateApps(kApps); |
| |
| // There's only one page and both items are in that page. |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(1, GetPaginationModel()->total_pages()); |
| TestAppListItemViewIndice(); |
| const views::ViewModelT<AppListItemView>* view_model = |
| apps_grid_view_->view_model(); |
| EXPECT_EQ(2, view_model->view_size()); |
| EXPECT_EQ(view_model->view_at(0), |
| test_api_->GetViewAtVisualIndex(0 /* page */, 0 /* slot */)); |
| EXPECT_EQ("Item 0", view_model->view_at(0)->item()->id()); |
| EXPECT_EQ(view_model->view_at(1), |
| test_api_->GetViewAtVisualIndex(0 /* page */, 1 /* slot */)); |
| EXPECT_EQ("Item 1", view_model->view_at(1)->item()->id()); |
| EXPECT_EQ(std::string("Item 0,Item 1"), model_->GetModelContent()); |
| |
| // Drag the first item to the page bottom. |
| gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint(); |
| gfx::Point to_in_next_page = |
| test_api_->GetItemTileRectAtVisualIndex(1, 0).CenterPoint(); |
| SimulateDragToNeighborPage(true /* next_page */, from, to_in_next_page); |
| |
| // A new second page is created and the first item is put on it. |
| EXPECT_EQ("1", page_flip_waiter_->selected_pages()); |
| EXPECT_EQ(1, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(2, GetPaginationModel()->total_pages()); |
| TestAppListItemViewIndice(); |
| EXPECT_EQ(2, view_model->view_size()); |
| EXPECT_EQ(view_model->view_at(0), |
| test_api_->GetViewAtVisualIndex(0 /* page */, 0 /* slot */)); |
| EXPECT_EQ("Item 1", view_model->view_at(0)->item()->id()); |
| EXPECT_EQ(view_model->view_at(1), |
| test_api_->GetViewAtVisualIndex(1 /* page */, 0 /* slot */)); |
| EXPECT_EQ("Item 0", view_model->view_at(1)->item()->id()); |
| EXPECT_EQ(std::string("Item 1,PageBreakItem,Item 0"), |
| model_->GetModelContent()); |
| } |
| |
| TEST_P(AppsGridGapTest, MoveLastItemToCreateFolderInNextPage) { |
| const int kApps = 2; |
| model_->PopulateApps(kApps); |
| |
| // There's only one page and both items are in that page. |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(1, GetPaginationModel()->total_pages()); |
| TestAppListItemViewIndice(); |
| const views::ViewModelT<AppListItemView>* view_model = |
| apps_grid_view_->view_model(); |
| EXPECT_EQ(2, view_model->view_size()); |
| EXPECT_EQ(view_model->view_at(0), |
| test_api_->GetViewAtVisualIndex(0 /* page */, 0 /* slot */)); |
| EXPECT_EQ("Item 0", view_model->view_at(0)->item()->id()); |
| EXPECT_EQ(view_model->view_at(1), |
| test_api_->GetViewAtVisualIndex(0 /* page */, 1 /* slot */)); |
| EXPECT_EQ("Item 1", view_model->view_at(1)->item()->id()); |
| EXPECT_EQ(std::string("Item 0,Item 1"), model_->GetModelContent()); |
| |
| // Drag the first item to next page and drag the second item to overlap with |
| // the first item. |
| gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint(); |
| gfx::Point to_in_next_page = |
| test_api_->GetItemTileRectAtVisualIndex(1, 0).CenterPoint(); |
| SimulateDragToNeighborPage(true /* next_page */, from, to_in_next_page); |
| GetPaginationModel()->SelectPage(0, false); |
| SimulateDragToNeighborPage(true /* next_page */, from, to_in_next_page); |
| |
| // A new folder is created on second page, but since the first page is empty, |
| // the page is removed and the new folder ends up on first page. |
| EXPECT_EQ("1,0", page_flip_waiter_->selected_pages()); |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| TestAppListItemViewIndice(); |
| EXPECT_EQ(1, view_model->view_size()); |
| EXPECT_EQ(view_model->view_at(0), |
| test_api_->GetViewAtVisualIndex(0 /* page */, 0 /* slot */)); |
| const AppListItem* folder_item = view_model->view_at(0)->item(); |
| EXPECT_TRUE(folder_item->is_folder()); |
| |
| // The "page break" item remains, but it will be removed later in |
| // AppListSyncableService. |
| EXPECT_EQ(std::string("PageBreakItem," + folder_item->id()), |
| model_->GetModelContent()); |
| } |
| |
| TEST_P(AppsGridGapTest, MoveLastItemForReorderInNextPage) { |
| const int kApps = 2; |
| model_->PopulateApps(kApps); |
| |
| // There's only one page and both items are in that page. |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(1, GetPaginationModel()->total_pages()); |
| TestAppListItemViewIndice(); |
| const views::ViewModelT<AppListItemView>* view_model = |
| apps_grid_view_->view_model(); |
| EXPECT_EQ(2, view_model->view_size()); |
| EXPECT_EQ(view_model->view_at(0), |
| test_api_->GetViewAtVisualIndex(0 /* page */, 0 /* slot */)); |
| EXPECT_EQ("Item 0", view_model->view_at(0)->item()->id()); |
| EXPECT_EQ(view_model->view_at(1), |
| test_api_->GetViewAtVisualIndex(0 /* page */, 1 /* slot */)); |
| EXPECT_EQ("Item 1", view_model->view_at(1)->item()->id()); |
| EXPECT_EQ(std::string("Item 0,Item 1"), model_->GetModelContent()); |
| |
| // Drag the first item to next page and drag the second item to the left of |
| // the first item. |
| gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint(); |
| gfx::Rect tile_rect = test_api_->GetItemTileRectAtVisualIndex(1, 0); |
| gfx::Point to_in_next_page = tile_rect.CenterPoint(); |
| to_in_next_page.set_x(tile_rect.x()); |
| SimulateDragToNeighborPage(true /* next_page */, from, to_in_next_page); |
| GetPaginationModel()->SelectPage(0, false); |
| SimulateDragToNeighborPage(true /* next_page */, from, to_in_next_page); |
| |
| // The second item is put on the left of the first item, but since the first |
| // page is empty, the page is removed and both items end up on first page. |
| EXPECT_EQ("1,0", page_flip_waiter_->selected_pages()); |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| TestAppListItemViewIndice(); |
| EXPECT_EQ(2, view_model->view_size()); |
| EXPECT_EQ(view_model->view_at(0), |
| test_api_->GetViewAtVisualIndex(0 /* page */, 0 /* slot */)); |
| EXPECT_EQ("Item 1", view_model->view_at(0)->item()->id()); |
| EXPECT_EQ(view_model->view_at(1), |
| test_api_->GetViewAtVisualIndex(0 /* page */, 1 /* slot */)); |
| EXPECT_EQ("Item 0", view_model->view_at(1)->item()->id()); |
| |
| // The "page break" item remains, but it will be removed later in |
| // AppListSyncableService. |
| EXPECT_EQ(std::string("PageBreakItem,Item 1,Item 0"), |
| model_->GetModelContent()); |
| } |
| |
| TEST_P(AppsGridGapTest, MoveLastItemToNewEmptyPage) { |
| const int kApps = 1; |
| model_->PopulateApps(kApps); |
| |
| // There's only one page and only one item in that page. |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(1, GetPaginationModel()->total_pages()); |
| TestAppListItemViewIndice(); |
| const views::ViewModelT<AppListItemView>* view_model = |
| apps_grid_view_->view_model(); |
| EXPECT_EQ(1, view_model->view_size()); |
| EXPECT_EQ(view_model->view_at(0), |
| test_api_->GetViewAtVisualIndex(0 /* page */, 0 /* slot */)); |
| EXPECT_EQ("Item 0", view_model->view_at(0)->item()->id()); |
| EXPECT_EQ(std::string("Item 0"), model_->GetModelContent()); |
| |
| // Drag the item to next page. |
| gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint(); |
| gfx::Point to_in_next_page = |
| test_api_->GetItemTileRectAtVisualIndex(1, 0).CenterPoint(); |
| SimulateDragToNeighborPage(true /* next_page */, from, to_in_next_page); |
| GetPaginationModel()->SelectPage(0, false); |
| SimulateDragToNeighborPage(true /* next_page */, from, to_in_next_page); |
| |
| // The item is put on second page, but since the first page is empty, |
| // the page is removed and the item ends up on first page. |
| EXPECT_EQ("1,0", page_flip_waiter_->selected_pages()); |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| TestAppListItemViewIndice(); |
| EXPECT_EQ(1, view_model->view_size()); |
| EXPECT_EQ(view_model->view_at(0), |
| test_api_->GetViewAtVisualIndex(0 /* page */, 0 /* slot */)); |
| EXPECT_EQ("Item 0", view_model->view_at(0)->item()->id()); |
| EXPECT_EQ(std::string("Item 0"), model_->GetModelContent()); |
| } |
| |
| TEST_P(AppsGridGapTest, MoveItemToPreviousFullPage) { |
| const int kApps = 2 + GetTilesPerPage(0); |
| model_->PopulateApps(kApps); |
| |
| // There are two pages and last item is on second page. |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| EXPECT_EQ(2, GetPaginationModel()->total_pages()); |
| TestAppListItemViewIndice(); |
| const views::ViewModelT<AppListItemView>* view_model = |
| apps_grid_view_->view_model(); |
| EXPECT_EQ(kApps, view_model->view_size()); |
| for (int i = 0; i < kApps; ++i) { |
| EXPECT_EQ(view_model->view_at(i), test_api_->GetViewAtVisualIndex( |
| i / GetTilesPerPage(0) /* page */, |
| i % GetTilesPerPage(0) /* slot */)); |
| EXPECT_EQ("Item " + base::NumberToString(i), |
| view_model->view_at(i)->item()->id()); |
| } |
| |
| // There's no "page break" item at the end of first page, although there are |
| // two pages. It will only be added after user operations. |
| std::string model_content = "Item 0"; |
| for (int i = 1; i < kApps; ++i) |
| model_content.append(",Item " + base::NumberToString(i)); |
| EXPECT_EQ(model_content, model_->GetModelContent()); |
| |
| // Drag the last item to the first item's left position in previous page. |
| gfx::Point from = test_api_->GetItemTileRectAtVisualIndex(1, 1).CenterPoint(); |
| gfx::Rect tile_rect = test_api_->GetItemTileRectAtVisualIndex(0, 0); |
| gfx::Point to_in_previous_page = tile_rect.CenterPoint(); |
| to_in_previous_page.set_x(tile_rect.x()); |
| GetPaginationModel()->SelectPage(1, false); |
| SimulateDragToNeighborPage(false /* next_page */, from, to_in_previous_page); |
| |
| // The dragging is successful, the last item becomes the first item. |
| EXPECT_EQ("0", page_flip_waiter_->selected_pages()); |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| TestAppListItemViewIndice(); |
| EXPECT_EQ(kApps, view_model->view_size()); |
| for (int i = 0; i < kApps; ++i) { |
| EXPECT_EQ(view_model->view_at(i), test_api_->GetViewAtVisualIndex( |
| i / GetTilesPerPage(0) /* page */, |
| i % GetTilesPerPage(0) /* slot */)); |
| EXPECT_EQ("Item " + base::NumberToString((i + kApps - 1) % kApps), |
| view_model->view_at(i)->item()->id()); |
| } |
| |
| // A "page break" item is added to split the pages. |
| model_content = "Item " + base::NumberToString(kApps - 1); |
| for (int i = 1; i < kApps; ++i) { |
| model_content.append(",Item " + base::NumberToString(i - 1)); |
| if (i == GetTilesPerPage(0) - 1) |
| model_content.append(",PageBreakItem"); |
| } |
| EXPECT_EQ(model_content, model_->GetModelContent()); |
| |
| // Again drag the last item to the first item's left position in previous |
| // page. |
| GetPaginationModel()->SelectPage(1, false); |
| SimulateDragToNeighborPage(false /* next_page */, from, to_in_previous_page); |
| |
| // The dragging is successful, the last item becomes the first item again. |
| EXPECT_EQ("0", page_flip_waiter_->selected_pages()); |
| EXPECT_EQ(0, GetPaginationModel()->selected_page()); |
| TestAppListItemViewIndice(); |
| EXPECT_EQ(kApps, view_model->view_size()); |
| for (int i = 0; i < kApps; ++i) { |
| EXPECT_EQ(view_model->view_at(i), test_api_->GetViewAtVisualIndex( |
| i / GetTilesPerPage(0) /* page */, |
| i % GetTilesPerPage(0) /* slot */)); |
| EXPECT_EQ("Item " + base::NumberToString((i + kApps - 2) % kApps), |
| view_model->view_at(i)->item()->id()); |
| } |
| |
| // A "page break" item still exists. |
| model_content = "Item " + base::NumberToString(kApps - 2) + ",Item " + |
| base::NumberToString(kApps - 1); |
| for (int i = 2; i < kApps; ++i) { |
| model_content.append(",Item " + base::NumberToString(i - 2)); |
| if (i == GetTilesPerPage(0) - 1) |
| model_content.append(",PageBreakItem"); |
| } |
| EXPECT_EQ(model_content, model_->GetModelContent()); |
| } |
| |
| TEST_F(AppsGridViewTest, CreateANewPageWithKeyboardLogsMetrics) { |
| base::HistogramTester histogram_tester; |
| model_->PopulateApps(2); |
| |
| // Select first app and move it with the keyboard down to create a new page. |
| AppListItemView* moving_item = GetItemViewAt(0); |
| apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item); |
| SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN); |
| SimulateKeyReleased(ui::VKEY_DOWN, ui::EF_NONE); |
| |
| ASSERT_EQ(apps_grid_view_->pagination_model()->total_pages(), 2); |
| histogram_tester.ExpectBucketCount( |
| "Apps.AppList.AppsGridAddPage", |
| AppListPageCreationType::kMovingAppWithKeyboard, 1); |
| } |
| |
| TEST_F(AppsGridViewTest, CreateANewPageByDraggingLogsMetrics) { |
| base::HistogramTester histogram_tester; |
| model_->PopulateApps(2); |
| |
| PageFlipWaiter page_flip_waiter(GetPaginationModel()); |
| // Drag down the first item until a new page is created. |
| gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint(); |
| const gfx::Rect apps_grid_bounds = apps_grid_view_->GetLocalBounds(); |
| gfx::Point to = |
| gfx::Point(apps_grid_bounds.width() / 2, apps_grid_bounds.bottom() + 1); |
| |
| // For fullscreen, drag to the bottom/right of bounds. |
| page_flip_waiter.Reset(); |
| SimulateDrag(AppsGridView::MOUSE, from, to); |
| |
| EXPECT_EQ(to, GetDragViewCenter()); |
| |
| while (test_api_->HasPendingPageFlip()) |
| page_flip_waiter.Wait(); |
| |
| apps_grid_view_->EndDrag(false /*cancel*/); |
| |
| ASSERT_EQ(apps_grid_view_->pagination_model()->total_pages(), 2); |
| histogram_tester.ExpectBucketCount("Apps.AppList.AppsGridAddPage", |
| AppListPageCreationType::kDraggingApp, 1); |
| } |
| |
| TEST_F(AppsGridViewTest, CreateANewPageByAddingAppLogsMetrics) { |
| base::HistogramTester histogram_tester; |
| model_->PopulateApps(GetTilesPerPage(0)); |
| |
| // Add an item to simulate installing or syncing, the metric should be |
| // recorded. |
| model_->CreateAndAddItem("Extra App"); |
| |
| ASSERT_EQ(apps_grid_view_->pagination_model()->total_pages(), 2); |
| histogram_tester.ExpectBucketCount("Apps.AppList.AppsGridAddPage", |
| AppListPageCreationType::kSyncOrInstall, |
| 1); |
| } |
| |
| } // namespace test |
| } // namespace ash |