blob: 0f0361c2158273239cfc7b9d755f166a0de0fe5b [file] [log] [blame]
// 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/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_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 app_list {
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 TotalPagesChanged() override {}
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();
}
void TransitionStarted() override {}
void TransitionChanged() override {}
void TransitionEnded() override {}
std::unique_ptr<base::RunLoop> ui_run_loop_;
ash::PaginationModel* model_ = nullptr;
bool wait_ = false;
std::string selected_pages_;
DISALLOW_COPY_AND_ASSIGN(PageFlipWaiter);
};
// 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() 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::kRecommendation);
}
~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);
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);
if (view->bounds().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);
}
// 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);
auto app_iter = apps_grid_view_->FindChild(view_model->view_at(0));
DCHECK(app_iter != apps_grid_view_->children().cend());
for (int i = 1; i < view_model->view_size(); ++i) {
++app_iter;
ASSERT_NE(apps_grid_view_->children().cend(), app_iter);
EXPECT_EQ(view_model->view_at(i), *app_iter);
}
}
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::KeyboardController keyboard_controller_;
DISALLOW_COPY_AND_ASSIGN(AppsGridViewTest);
};
INSTANTIATE_TEST_SUITE_P(, 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 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());
}
TEST_F(AppsGridViewTest, TapsBetweenAppsWontCloseAppList) {
model_->PopulateApps(2);
gfx::Point between_apps = GetItemRectOnCurrentPageAt(0, 0).right_center();
gfx::Point empty_space = GetItemRectOnCurrentPageAt(0, 2).CenterPoint();
ui::GestureEvent tap_between(between_apps.x(), between_apps.y(), 0,
base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_TAP));
ui::GestureEvent tap_outside(empty_space.x(), empty_space.y(), 0,
base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_TAP));
// Taps between apps should be handled to prevent them from going into
// app_list
apps_grid_view_->OnGestureEvent(&tap_between);
EXPECT_TRUE(tap_between.handled());
// Taps outside of occupied tiles should not be handled, that they may close
// the app_list
apps_grid_view_->OnGestureEvent(&tap_outside);
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));
}
TEST_P(AppsGridViewTest, MouseDragItemIntoFolder) {
size_t kTotalItems = 3;
model_->PopulateApps(kTotalItems);
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);
apps_grid_view_->EndDrag(false);
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(10, 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);
// 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());
// 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);
while (test_api_->HasPendingPageFlip()) {
page_flip_waiter.Wait();
}
EXPECT_EQ("1,0", page_flip_waiter.selected_pages());
EXPECT_EQ(0, GetPaginationModel()->selected_page());
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(, 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(10, 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());
}
} // namespace test
} // namespace app_list