blob: 36f708b00ad1e7cbe40a0eb22e5ad999fac6b81f [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 <vector>
#include "ash/app_list/app_list_metrics.h"
#include "ash/app_list/app_list_test_view_delegate.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/model/app_list_test_model.h"
#include "ash/app_list/model/search/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/apps_grid_view_test_api.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/paged_apps_grid_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/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 "ash/public/cpp/test/test_app_list_color_provider.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/compositor/layer.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->GetType() == 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::kChip);
set_is_recommendation(true);
}
~TestSuggestedSearchResult() override = default;
private:
DISALLOW_COPY_AND_ASSIGN(TestSuggestedSearchResult);
};
} // namespace
class TestAppsGridViewFolderDelegate : public AppsGridViewFolderDelegate {
public:
TestAppsGridViewFolderDelegate() = default;
TestAppsGridViewFolderDelegate(const TestAppsGridViewFolderDelegate&) =
delete;
TestAppsGridViewFolderDelegate& operator=(
const TestAppsGridViewFolderDelegate&) = delete;
~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 IsViewOutsideOfFolder(AppListItemView* view) override { return false; }
bool IsOEMFolder() const override { return false; }
void SetRootLevelDragViewVisible(bool visible) override {}
void HandleKeyboardReparent(AppListItemView* reparented_item,
ui::KeyboardCode key_code) override {}
void UpdateFolderBounds() override {}
};
class AppsGridViewTest : public views::ViewsTestBase {
public:
AppsGridViewTest() = default;
AppsGridViewTest(bool is_rtl,
bool is_pagination_preview_active,
bool create_as_tablet_mode)
: is_rtl_(is_rtl),
is_pagination_preview_active_(is_pagination_preview_active),
create_as_tablet_mode_(create_as_tablet_mode) {}
AppsGridViewTest(const AppsGridViewTest&) = delete;
AppsGridViewTest& operator=(const AppsGridViewTest&) = delete;
~AppsGridViewTest() override = default;
// testing::Test overrides:
void SetUp() override {
AppListView::SetShortAnimationForTesting(true);
if (is_rtl_)
base::i18n::SetICUDefaultLocale("he");
feature_list.InitWithFeatureState(app_list_features::kNewDragSpecInLauncher,
is_pagination_preview_active_);
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>();
delegate_->SetIsTabletModeEnabled(create_as_tablet_mode_);
app_list_view_ = new AppListView(delegate_.get());
app_list_view_->InitView(parent);
app_list_view_->Show(AppListViewState::kFullscreenAllApps,
false /*is_side_shelf*/);
contents_view_ = app_list_view_->app_list_main_view()->contents_view();
apps_grid_view_ = contents_view_->apps_container_view()->apps_grid_view();
app_list_view_->GetWidget()->Show();
model_ = delegate_->GetTestModel();
search_model_ = delegate_->GetSearchModel();
suggestions_container_ = contents_view_->apps_container_view()
->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();
test_api_ = std::make_unique<AppsGridViewTestApi>(apps_grid_view_);
ash::PresentationTimeRecorder::SetReportPresentationTimeImmediatelyForTest(
true);
// In production, page flip duration > page transition > overscroll.
SetPageFlipDurationForTest(apps_grid_view_);
page_flip_waiter_ = std::make_unique<PageFlipWaiter>(GetPaginationModel());
}
void TearDown() override {
page_flip_waiter_.reset();
ash::PresentationTimeRecorder::SetReportPresentationTimeImmediatelyForTest(
false);
app_list_view_->GetWidget()->Close();
views::ViewsTestBase::TearDown();
AppListView::SetShortAnimationForTesting(false);
}
protected:
void AnimateFolderViewPageFlip(int target_page) {
DCHECK(folder_apps_grid_view()->pagination_model()->total_pages() >
target_page);
AppsGridViewTestApi folder_grid_test_api(folder_apps_grid_view());
SetPageFlipDurationForTest(folder_apps_grid_view());
PageFlipWaiter page_flip_waiter(
folder_apps_grid_view()->pagination_model());
folder_apps_grid_view()->pagination_model()->SelectPage(target_page,
true /*animate*/);
while (HasPendingPageFlip(folder_apps_grid_view())) {
page_flip_waiter.Wait();
}
folder_grid_test_api.LayoutToIdealBounds();
}
void SetPageFlipDurationForTest(PagedAppsGridView* apps_grid_view) {
apps_grid_view->set_page_flip_delay_for_testing(
base::TimeDelta::FromMilliseconds(3));
apps_grid_view->pagination_model()->SetTransitionDurations(
base::TimeDelta::FromMilliseconds(2),
base::TimeDelta::FromMilliseconds(1));
}
bool HasPendingPageFlip(PagedAppsGridView* apps_grid_view) {
return apps_grid_view->page_flip_timer_.IsRunning() ||
apps_grid_view->pagination_model()->has_transition();
}
const AppListConfig& GetAppListConfig() const {
return contents_view_->GetAppListConfig();
}
AppListItemView* GetItemViewInAppsGridAt(int index,
AppsGridView* grid_view) const {
return grid_view->view_model()->view_at(index);
}
AppListItemView* GetItemViewInTopLevelGrid(int index) const {
return GetItemViewInAppsGridAt(index, apps_grid_view_);
}
AppListItemView* GetItemViewInAppsGridForPoint(
const gfx::Point& point,
AppsGridView* grid_view) const {
std::unique_ptr<AppsGridViewTestApi> temp_test_api =
std::make_unique<AppsGridViewTestApi>(grid_view);
const int selected_page = grid_view->pagination_model()->selected_page();
for (int i = 0; i < temp_test_api->AppsOnPage(selected_page); ++i) {
GridIndex index(selected_page, i);
AppListItemView* view = grid_view->GetViewAtIndex(index);
gfx::Point view_origin = view->origin();
// AppListItemViews on non-zero'th pages of AppsGridViews have offset
// coordinates that need to be converted to target.
if (selected_page != 0) {
views::View::ConvertPointToTarget(view->parent(), grid_view,
&view_origin);
// AppListItemViews that belong to a folder views' AppsGridView also
// need to have their x coordinate set for RTL.
if (grid_view->IsInFolder())
view_origin.set_x(grid_view->GetMirroredXInView(view_origin.x()));
}
if (gfx::Rect(view_origin, view->size()).Contains(point))
return view;
}
return nullptr;
}
AppListItemView* GetItemViewForPoint(const gfx::Point& point) const {
return GetItemViewInAppsGridForPoint(point, apps_grid_view_);
}
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() const { return test_api_->TilesPerPage(); }
ash::PaginationModel* GetPaginationModel() const {
return apps_grid_view_->pagination_model();
}
AppListFolderView* app_list_folder_view() const {
return contents_view_->apps_container_view()->app_list_folder_view();
}
PagedAppsGridView* folder_apps_grid_view() const {
return app_list_folder_view()->items_grid_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);
}
}
// Calls the private method.
static void DeleteItemAt(AppListItemList* item_list, size_t index) {
item_list->DeleteItemAt(index);
}
// Calls the private method.
void MoveItemInModel(AppListItemView* item_view, const GridIndex& target) {
apps_grid_view_->MoveItemInModel(item_view, target);
}
TestAppListColorProvider color_provider_; // Needed by AppListView.
AppListView* app_list_view_ = nullptr; // Owned by native widget.
PagedAppsGridView* 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_;
// True if the test screen is configured to work with RTL locale.
bool is_rtl_ = false;
// True if the launcher pagination preview is enabled for the test.
bool is_pagination_preview_active_ = false;
// True if the test shoould run with fullscreen.
bool test_with_fullscreen_ = true;
// True if we set the test on tablet mode.
bool create_as_tablet_mode_ = false;
std::unique_ptr<PageFlipWaiter> page_flip_waiter_;
private:
// Restores the locale to default when destructor is called.
base::test::ScopedRestoreICUDefaultLocale restore_locale_;
// Used by AppListFolderView::UpdatePreferredBounds.
keyboard::KeyboardUIController keyboard_ui_controller_;
base::test::ScopedFeatureList feature_list;
};
// Tests suite for verifying UI behaviour on RTL.
class AppsGridViewRTLTest : public AppsGridViewTest,
public testing::WithParamInterface<bool> {
public:
AppsGridViewRTLTest()
: AppsGridViewTest(/*is_rtl=*/GetParam(),
/*is_pagination_preview_active_=*/false,
/*create_as_tablet_mode=*/false) {}
explicit AppsGridViewRTLTest(bool create_as_tablet_mode)
: AppsGridViewTest(/*is_rtl=*/GetParam(),
/*is_pagination_preview_active_=*/false,
create_as_tablet_mode) {}
AppsGridViewRTLTest(const AppsGridViewRTLTest&) = delete;
AppsGridViewRTLTest& operator=(const AppsGridViewRTLTest&) = delete;
~AppsGridViewRTLTest() override = default;
};
INSTANTIATE_TEST_SUITE_P(All, AppsGridViewRTLTest, testing::Bool());
// Test base for app list items drag and drop tests. The suite contains helper
// methods to deal with drag and drop logic.
class AppsGridViewDragAndDropTestBase : public AppsGridViewTest {
public:
AppsGridViewDragAndDropTestBase(bool is_rtl,
bool is_pagination_preview_active)
: AppsGridViewTest(is_rtl,
is_pagination_preview_active,
/*create_as_tablet_mode=*/false) {}
AppsGridViewDragAndDropTestBase(const AppsGridViewDragAndDropTestBase&) =
delete;
AppsGridViewDragAndDropTestBase& operator=(
const AppsGridViewDragAndDropTestBase&) = delete;
~AppsGridViewDragAndDropTestBase() override = default;
AppListItemView* InitiateDrag(AppsGridView::Pointer pointer,
const gfx::Point& from,
AppsGridView* apps_grid_view) {
AppListItemView* view = GetItemViewInAppsGridForPoint(from, apps_grid_view);
DCHECK(view);
gfx::Point root_from(from);
gfx::NativeWindow window = app_list_view_->GetWidget()->GetNativeWindow();
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()));
apps_grid_view->InitiateDrag(view, root_from, root_from);
current_drag_location_ = root_from;
// Call UpdateDrag to trigger |apps_grid_view| change to cardified_state.
UpdateDrag(pointer, from, apps_grid_view);
return view;
}
// Updates the drag from the current drag location to the destination point
// |to|. These coordinates are relative the |apps_grid_view| which may belong
// to either the app list or an open folder view.
void UpdateDrag(AppsGridView::Pointer pointer,
const gfx::Point& to,
AppsGridView* apps_grid_view,
int steps = 1) {
// Check that the drag has been initialized.
DCHECK(current_drag_location_);
gfx::Point root_to(to);
gfx::NativeWindow window = apps_grid_view->GetWidget()->GetNativeWindow();
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()));
for (int step = 1; step <= steps; step += 1) {
gfx::Point drag_increment_point(*current_drag_location_);
drag_increment_point += gfx::Vector2d(
(root_to.x() - current_drag_location_->x()) * step / steps,
(root_to.y() - current_drag_location_->y()) * step / steps);
ui::MouseEvent drag_event(ui::ET_MOUSE_DRAGGED, to, drag_increment_point,
ui::EventTimeForNow(), 0, 0);
apps_grid_view->UpdateDragFromItem(pointer, drag_event);
}
current_drag_location_ = root_to;
}
void EndDrag(AppsGridView* grid_view, bool cancel) {
grid_view->EndDrag(cancel);
current_drag_location_ = absl::nullopt;
}
// 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 = InitiateDrag(pointer, from, apps_grid_view_);
UpdateDrag(pointer, to, apps_grid_view_);
return view;
}
// Simulate drag from the |from| point to either next or previous page's |to|
// point.
// Update drag to either next or previous page's |to| point.
void UpdateDragToNeighborPage(bool next_page, 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);
// Update dragging and relayout apps grid view after drag ends.
DragAfterPageFlipTask task(GetPaginationModel(), apps_grid_view_,
drag_event);
page_flip_waiter_->Reset();
UpdateDrag(AppsGridView::MOUSE, point_in_page_flip_buffer, apps_grid_view_,
/*steps=*/10);
while (HasPendingPageFlip(apps_grid_view_)) {
page_flip_waiter_->Wait();
}
EndDrag(apps_grid_view_, false /*cancel*/);
test_api_->LayoutToIdealBounds();
}
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;
}
private:
absl::optional<gfx::Point> current_drag_location_;
};
// Tests suite for app list items drag and drop tests. These tests are
// paramerized to cover both RTL locale and pagination previews behaviour.
class AppsGridViewDragAndDropTest
: public AppsGridViewDragAndDropTestBase,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
AppsGridViewDragAndDropTest()
: AppsGridViewDragAndDropTestBase(
/*is_rtl=*/std::get<0>(GetParam()),
/*is_pagination_preview_active_=*/std::get<1>(GetParam())) {}
AppsGridViewDragAndDropTest(const AppsGridViewDragAndDropTest&) = delete;
AppsGridViewDragAndDropTest& operator=(const AppsGridViewDragAndDropTest&) =
delete;
~AppsGridViewDragAndDropTest() override = default;
};
INSTANTIATE_TEST_SUITE_P(All,
AppsGridViewDragAndDropTest,
testing::Combine(testing::Bool(), testing::Bool()));
// Tests suite for app list items drag and drop tests. These tests are not
// proved to work with a cardified state.
// TODO(anasalazar): Fix tests under this suite to work with a cardified state
class AppsGridViewDragAndDropTestNoCardifiedState
: public AppsGridViewDragAndDropTestBase,
public testing::WithParamInterface<bool> {
public:
AppsGridViewDragAndDropTestNoCardifiedState()
: AppsGridViewDragAndDropTestBase(
/*is_rtl=*/GetParam(),
/*is_pagination_preview_active_=*/false) {}
AppsGridViewDragAndDropTestNoCardifiedState(
const AppsGridViewDragAndDropTestNoCardifiedState&) = delete;
AppsGridViewDragAndDropTestNoCardifiedState& operator=(
const AppsGridViewDragAndDropTestNoCardifiedState&) = delete;
~AppsGridViewDragAndDropTestNoCardifiedState() override = default;
};
INSTANTIATE_TEST_SUITE_P(All,
AppsGridViewDragAndDropTestNoCardifiedState,
testing::Bool());
// Tests suite to verify behaviour exclusively to cardified state.
class AppsGridViewCardifiedStateTest
: public AppsGridViewDragAndDropTestBase,
public testing::WithParamInterface<bool> {
public:
AppsGridViewCardifiedStateTest()
: AppsGridViewDragAndDropTestBase(
/*is_rtl=*/GetParam(),
/*is_pagination_preview_active_=*/true) {}
AppsGridViewCardifiedStateTest(const AppsGridViewCardifiedStateTest&) =
delete;
AppsGridViewCardifiedStateTest& operator=(
const AppsGridViewCardifiedStateTest&) = delete;
~AppsGridViewCardifiedStateTest() override = default;
};
INSTANTIATE_TEST_SUITE_P(All, AppsGridViewCardifiedStateTest, testing::Bool());
// Test suite for verifying tablet mode apps grid behaviour.
class AppsGridViewTabletTest : public AppsGridViewRTLTest {
public:
AppsGridViewTabletTest()
: AppsGridViewRTLTest(/*create_as_tablet_mode=*/true) {}
AppsGridViewTabletTest(const AppsGridViewTabletTest&) = delete;
AppsGridViewTabletTest& operator=(const AppsGridViewTabletTest&) = delete;
~AppsGridViewTabletTest() override = default;
};
INSTANTIATE_TEST_SUITE_P(All, AppsGridViewTabletTest, testing::Bool());
TEST_F(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());
model_->PopulateApps(kPages * GetTilesPerPage());
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, RemoveSelectedLastApp) {
const int kTotalItems = 2;
const int kLastItemIndex = kTotalItems - 1;
model_->PopulateApps(kTotalItems);
AppListItemView* last_view = GetItemViewInTopLevelGrid(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 = GetItemViewInTopLevelGrid(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(
GetItemViewInTopLevelGrid(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 = GetItemViewInTopLevelGrid(0);
model_->top_level_item_list()->MoveItem(0, cols + 2);
// Make sure the logical location of the view.
EXPECT_NE(view0, GetItemViewInTopLevelGrid(0));
EXPECT_EQ(view0, GetItemViewInTopLevelGrid(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 = GetItemViewInTopLevelGrid(0);
model_->top_level_item_list()->MoveItem(0, cols + 2);
// Make sure the logical location of the view.
EXPECT_NE(view0, GetItemViewInTopLevelGrid(0));
EXPECT_EQ(view0, GetItemViewInTopLevelGrid(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 = GetItemViewInTopLevelGrid(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 = GetItemViewInTopLevelGrid(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(AppsGridViewRTLTest, ScrollSequenceHandledByAppListView) {
base::HistogramTester histogram_tester;
model_->PopulateApps(GetTilesPerPage() + 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_P(AppsGridViewRTLTest, MouseScrollSequenceHandledByAppListView) {
base::HistogramTester histogram_tester;
model_->PopulateApps(GetTilesPerPage() + 1);
EXPECT_EQ(2, GetPaginationModel()->total_pages());
const gfx::Point apps_grid_view_origin =
apps_grid_view_->GetBoundsInScreen().origin();
// Pick a point inside the `AppsGridView`, as well as arbitrary points below
// and above it to drag to.
gfx::Point drag_start_point = apps_grid_view_origin + gfx::Vector2d(0, 10);
gfx::Point below_point = apps_grid_view_origin + gfx::Vector2d(0, 20);
gfx::Point above_point = apps_grid_view_origin + gfx::Vector2d(0, -20);
ui::MouseEvent press_event(ui::ET_MOUSE_PRESSED, apps_grid_view_origin,
apps_grid_view_origin, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_LEFT_MOUSE_BUTTON);
ui::MouseEvent drag_start(ui::ET_MOUSE_DRAGGED, drag_start_point,
drag_start_point, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
ui::MouseEvent drag_below(ui::ET_MOUSE_DRAGGED, below_point, below_point,
ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_LEFT_MOUSE_BUTTON);
ui::MouseEvent drag_above(ui::ET_MOUSE_DRAGGED, above_point, above_point,
ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_LEFT_MOUSE_BUTTON);
// Send a press event to set the `mouse_drag_start_point_` for scrolling
apps_grid_view_->OnMouseEvent(&press_event);
EXPECT_TRUE(press_event.handled());
// Drag down on the app grid when on page 1, this should move the
// `AppListView` and not move the `AppsGridView`. We have to send two drag
// down events, `drag_start` and `drag_below`, to make sure `AppListView` has
// its anchor point and starts dragging down. Event is manually passed to
// `AppListview` in `AppsGridView::OnMouseEvent`
apps_grid_view_->OnMouseEvent(&drag_start);
EXPECT_TRUE(drag_start.handled());
histogram_tester.ExpectTotalCount(
"Apps.StateTransition.Drag.PresentationTime.ClamshellMode", 0);
apps_grid_view_->OnMouseEvent(&drag_below);
EXPECT_TRUE(drag_below.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);
// Now drag back up. Because we have dragged the launcher down, we want it to
// continue dragging to allow the user to fully expand it. If we don't, the
// launcher can end up in a "expanded" state with the launcher not reaching
// the top of the screen and the app list scrolling.
apps_grid_view_->OnMouseEvent(&drag_above);
EXPECT_TRUE(drag_above.handled());
ASSERT_TRUE(app_list_view_->is_in_drag());
ASSERT_EQ(0, GetPaginationModel()->transition().progress);
histogram_tester.ExpectTotalCount(
"Apps.StateTransition.Drag.PresentationTime.ClamshellMode", 2);
histogram_tester.ExpectTotalCount(
"Apps.StateTransition.Drag.PresentationTime.MaxLatency.ClamshellMode", 0);
}
TEST_P(AppsGridViewRTLTest,
OnGestureEventScrollSequenceHandleByPaginationController) {
base::HistogramTester histogram_tester;
model_->PopulateApps(GetTilesPerPage() + 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_->apps_container_view();
const size_t kTotalItems = GetAppListConfig().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(AppsGridViewRTLTest, 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 = GetAppListConfig().max_folder_pages() *
GetAppListConfig().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_F(AppsGridViewTest, RemoveItemsInFolderShouldUpdateBounds) {
// Populate two folders with different number of apps.
model_->CreateAndPopulateFolderWithApps(2);
AppListFolderItem* folder_2 = model_->CreateAndPopulateFolderWithApps(4);
// Record the bounds of the folder view with 2 items in it.
AppsGridView* items_grid_view = app_list_folder_view()->items_grid_view();
test_api_->PressItemAt(0);
EXPECT_TRUE(contents_view_->apps_container_view()->IsInFolderView());
gfx::Rect one_row_folder_view = items_grid_view->GetBoundsInScreen();
app_list_folder_view()->CloseFolderPage();
// Record the bounds of the folder view with 4 items in it and keep the folder
// view open for further testing.
test_api_->PressItemAt(1);
EXPECT_TRUE(contents_view_->apps_container_view()->IsInFolderView());
gfx::Rect two_rows_folder_view = items_grid_view->GetBoundsInScreen();
EXPECT_NE(one_row_folder_view.size(), two_rows_folder_view.size());
// Remove one item from the folder with 4 items. The bound should stay the
// same as there are still two rows in the folder view.
model_->DeleteItem(folder_2->item_list()->item_at(0)->id());
EXPECT_TRUE(contents_view_->apps_container_view()->IsInFolderView());
items_grid_view->GetWidget()->LayoutRootViewIfNecessary();
EXPECT_EQ(items_grid_view->GetBoundsInScreen().size(),
two_rows_folder_view.size());
// Remove another item from the folder. The bound should update and become the
// folder view with one row.
model_->DeleteItem(folder_2->item_list()->item_at(0)->id());
EXPECT_TRUE(contents_view_->apps_container_view()->IsInFolderView());
items_grid_view->GetWidget()->LayoutRootViewIfNecessary();
EXPECT_EQ(items_grid_view->GetBoundsInScreen().size(),
one_row_folder_view.size());
}
TEST_F(AppsGridViewTest, AddItemsToFolderShouldUpdateBounds) {
// Populate two folders with different number of apps.
AppListFolderItem* folder_1 = model_->CreateAndPopulateFolderWithApps(2);
model_->CreateAndPopulateFolderWithApps(4);
// Record the bounds of the folder view with 4 items in it.
AppsGridView* items_grid_view = app_list_folder_view()->items_grid_view();
test_api_->PressItemAt(1);
EXPECT_TRUE(contents_view_->apps_container_view()->IsInFolderView());
gfx::Rect two_rows_folder_view = items_grid_view->GetBoundsInScreen();
app_list_folder_view()->CloseFolderPage();
// Record the bounds of the folder view with 2 items in it and keep the folder
// view open for further testing.
test_api_->PressItemAt(0);
EXPECT_TRUE(contents_view_->apps_container_view()->IsInFolderView());
gfx::Rect one_row_folder_view = items_grid_view->GetBoundsInScreen();
EXPECT_NE(one_row_folder_view.size(), two_rows_folder_view.size());
// Add an item to the folder so that there are two rows in the folder view.
model_->AddItemToFolder(model_->CreateItem("Extra 1"), folder_1->id());
EXPECT_TRUE(contents_view_->apps_container_view()->IsInFolderView());
items_grid_view->GetWidget()->LayoutRootViewIfNecessary();
EXPECT_EQ(items_grid_view->GetBoundsInScreen().size(),
two_rows_folder_view.size());
app_list_folder_view()->CloseFolderPage();
// Create a folder with a full page of apps. Add an item to the folder should
// not change the size of the folder view.
AppListFolderItem* folder_full = model_->CreateAndPopulateFolderWithApps(
GetAppListConfig().max_folder_items_per_page());
test_api_->PressItemAt(2);
EXPECT_TRUE(contents_view_->apps_container_view()->IsInFolderView());
gfx::Rect full_folder_view = items_grid_view->GetBoundsInScreen();
model_->AddItemToFolder(model_->CreateItem("Extra 2"), folder_full->id());
EXPECT_EQ(items_grid_view->GetBoundsInScreen().size(),
full_folder_view.size());
app_list_folder_view()->CloseFolderPage();
}
TEST_P(AppsGridViewRTLTest, ScrollDownShouldNotExitFolder) {
const size_t kTotalItems = GetAppListConfig().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_->apps_container_view()->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_->apps_container_view()->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 = GetItemViewInTopLevelGrid(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(AppsGridViewRTLTest, 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) {
GetPaginationModel()->SelectPage(i, /*animate=*/false);
for (size_t j = 0; j < kItemsInPage; ++j) {
const size_t idx = kItemsInPage * i + j;
AppListItemView* item_view = GetItemViewInTopLevelGrid(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_F(AppsGridViewTest, ItemViewsDontHaveLayer) {
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(GetItemViewInTopLevelGrid(i)->layer());
}
TEST_P(AppsGridViewDragAndDropTest, ItemViewsHaveLayerDuringDrag) {
size_t kTotalItems = 3;
model_->PopulateApps(kTotalItems);
gfx::Point from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
// Dragging item_1 over item_0 creates a folder.
gfx::Point to = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_, 10 /*steps*/);
// 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(GetItemViewInTopLevelGrid(i)->layer());
EndDrag(apps_grid_view_, false /*cancel*/);
}
TEST_P(AppsGridViewDragAndDropTest, ItemViewsDontHaveLayerAfterDrag) {
size_t kTotalItems = 3;
model_->PopulateApps(kTotalItems);
gfx::Point from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
// Dragging item_1 over item_0 creates a folder.
gfx::Point to = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_, 10 /*steps*/);
EndDrag(apps_grid_view_, false /*cancel*/);
test_api_->WaitForItemMoveAnimationDone();
// The layer should be destroyed after the dragging.
for (size_t i = 0; i < model_->top_level_item_list()->item_count(); ++i)
EXPECT_FALSE(GetItemViewInTopLevelGrid(i)->layer());
}
TEST_P(AppsGridViewDragAndDropTest, MouseDragItemIntoFolder) {
size_t kTotalItems = 3;
model_->PopulateApps(kTotalItems);
gfx::Point from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
// Dragging item_1 over item_0 creates a folder.
gfx::Point to = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_, 10 /*steps*/);
EndDrag(apps_grid_view_, false /*cancel*/);
test_api_->WaitForItemMoveAnimationDone();
test_api_->LayoutToIdealBounds();
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_P(AppsGridViewDragAndDropTest, MouseDragSecondItemIntoFolder) {
AppListFolderItem* folder_item = model_->CreateAndPopulateFolderWithApps(2);
model_->PopulateApps(1);
gfx::Point from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
// Dragging item_2 to the folder adds Item_2 to the folder.
gfx::Point to = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_, 10 /*steps*/);
EndDrag(apps_grid_view_, false /*cancel*/);
test_api_->WaitForItemMoveAnimationDone();
test_api_->LayoutToIdealBounds();
EXPECT_EQ(1u, model_->top_level_item_list()->item_count());
EXPECT_EQ(folder_item->id(), model_->GetModelContent());
EXPECT_EQ(3u, 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());
AppListItem* item_2 = model_->FindItem("Item 2");
EXPECT_TRUE(item_2->IsInFolder());
EXPECT_EQ(folder_item->id(), item_2->folder_id());
}
TEST_F(AppsGridViewTest, CheckFolderWithMultiplePagesContents) {
// Creates a folder item.
const size_t kTotalItems = GetAppListConfig().max_folder_items_per_page();
AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(kTotalItems);
// Open the folder and check it's contents.
test_api_->Update();
test_api_->PressItemAt(0);
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());
EXPECT_EQ(4, folder_apps_grid_view()->cols());
EXPECT_EQ(4, folder_apps_grid_view()->rows_per_page());
ash::PaginationModel* folder_pagination_model =
folder_apps_grid_view()->pagination_model();
EXPECT_EQ(1, folder_pagination_model->total_pages());
EXPECT_EQ(0, folder_pagination_model->selected_page());
EXPECT_TRUE(folder_apps_grid_view()->IsInFolder());
}
TEST_P(AppsGridViewDragAndDropTest, MouseDragItemOutOfFolderFirstPage) {
// Creates a folder item.
const size_t kTotalItems = GetAppListConfig().max_folder_items_per_page();
AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(kTotalItems);
test_api_->Update();
test_api_->PressItemAt(0);
AppsGridViewTestApi folder_grid_test_api(folder_apps_grid_view());
gfx::Point from =
folder_grid_test_api.GetItemTileRectOnCurrentPageAt(0, 0).CenterPoint();
// Calculate the coordinates for the drop point. Note that we we are dropping
// into the app list view not the folder view. The (0,1) spot is empty.
gfx::Point drop_point = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
drop_point.set_x(apps_grid_view_->GetMirroredXInView(drop_point.x()));
// Drag the first folder child out of the folder.
AppListItemView* drag_view =
InitiateDrag(AppsGridView::MOUSE, from, folder_apps_grid_view());
gfx::Point empty_space =
app_list_folder_view()->GetLocalBounds().bottom_center() +
gfx::Vector2d(0, drag_view->height()
/*padding to completely exit folder view*/);
UpdateDrag(AppsGridView::MOUSE, empty_space, folder_apps_grid_view(),
10 /*steps*/);
// Fire the reparent timer that should be started when an item is dragged out
// of folder bounds.
ASSERT_TRUE(folder_apps_grid_view()->FireFolderItemReparentTimerForTest());
views::View::ConvertPointToTarget(apps_grid_view_, folder_apps_grid_view(),
&drop_point);
UpdateDrag(AppsGridView::MOUSE, drop_point, folder_apps_grid_view(),
5 /*steps*/);
EndDrag(folder_apps_grid_view(), false /*cancel*/);
AppListItem* item_0 = model_->FindItem("Item 0");
AppListItem* item_1 = model_->FindItem("Item 1");
EXPECT_FALSE(item_0->IsInFolder());
EXPECT_TRUE(item_1->IsInFolder());
EXPECT_EQ(folder_item->id(), item_1->folder_id());
EXPECT_EQ(std::string(folder_item->id() + ",Item 0"),
model_->GetModelContent());
EXPECT_EQ(kTotalItems - 1, folder_item->ChildItemCount());
}
TEST_F(AppsGridViewTest, SwitchPageFolderItem) {
// Creates a folder item with enough views to have a second page.
const size_t kTotalItems = GetAppListConfig().max_folder_items_per_page() + 1;
model_->CreateAndPopulateFolderWithApps(kTotalItems);
// Switch to second page and check it's contents.
test_api_->Update();
test_api_->PressItemAt(0);
AnimateFolderViewPageFlip(1);
EXPECT_EQ(1, folder_apps_grid_view()->pagination_model()->selected_page());
EXPECT_EQ(4, folder_apps_grid_view()->cols());
EXPECT_EQ(4, folder_apps_grid_view()->rows_per_page());
EXPECT_TRUE(folder_apps_grid_view()->IsInFolder());
}
TEST_P(AppsGridViewDragAndDropTest, MouseDragItemOutOfFolderSecondPage) {
// Creates a folder item with enough views to have a second page.
const size_t kTotalItems = GetAppListConfig().max_folder_items_per_page() + 1;
AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(kTotalItems);
test_api_->Update();
test_api_->PressItemAt(0);
AppsGridViewTestApi folder_grid_test_api(folder_apps_grid_view());
// Switch to second page.
AnimateFolderViewPageFlip(1);
gfx::Point from =
folder_grid_test_api.GetItemTileRectOnCurrentPageAt(0, 0).CenterPoint();
// Calculate the coordinates for the drop point. Note that we we are dropping
// into the app list view not the folder view. The (0,1) spot is empty.
gfx::Point drop_point = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
// Drag the first folder child on the second page out of the folder.
AppListItemView* drag_view =
InitiateDrag(AppsGridView::MOUSE, from, folder_apps_grid_view());
// Calculate the target destination for our drag and update the drag to that
// location.
gfx::Point empty_space =
app_list_folder_view()->GetLocalBounds().bottom_center() +
gfx::Vector2d(0, drag_view->height()
/*padding to completely exit folder view*/);
UpdateDrag(AppsGridView::MOUSE, empty_space, folder_apps_grid_view(),
10 /*steps*/);
// Fire the reparent timer that should be started when an item is dragged out
// of folder bounds.
ASSERT_TRUE(folder_apps_grid_view()->FireFolderItemReparentTimerForTest());
drop_point.set_x(apps_grid_view_->GetMirroredXInView(drop_point.x()));
views::View::ConvertPointToTarget(apps_grid_view_, folder_apps_grid_view(),
&drop_point);
UpdateDrag(AppsGridView::MOUSE, drop_point, folder_apps_grid_view(),
5 /*steps*/);
// End the drag and assert that the item has been dragged out of the folder
// and the app list's grid view has been updated accordingly.
EndDrag(folder_apps_grid_view(), false /*cancel*/);
AppListItem* item_0 = model_->FindItem("Item 0");
AppListItem* item_16 = model_->FindItem("Item 16");
EXPECT_TRUE(item_0->IsInFolder());
EXPECT_FALSE(item_16->IsInFolder());
EXPECT_EQ(folder_item->id(), item_0->folder_id());
EXPECT_EQ(std::string(folder_item->id() + ",Item 16"),
model_->GetModelContent());
EXPECT_EQ(kTotalItems - 1, folder_item->ChildItemCount());
}
TEST_P(AppsGridViewDragAndDropTestNoCardifiedState,
MouseDropItemFromFolderSecondPage) {
// Creates a folder item with enough views to have a second page.
const size_t kTotalItems = GetAppListConfig().max_folder_items_per_page() + 1;
AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(kTotalItems);
test_api_->Update();
test_api_->PressItemAt(0);
ASSERT_TRUE(folder_apps_grid_view()->IsInFolder());
// Switch to second page.
AnimateFolderViewPageFlip(1);
// Fill the rest of the root grid view with new app list items. Leave 1 slot
// open so dropping an item from a folder to the root level apps grid does not
// cause a page overflow.
model_->PopulateApps(GetTilesPerPage() - 2);
AppsGridViewTestApi folder_grid_test_api(folder_apps_grid_view());
gfx::Point from =
folder_grid_test_api.GetItemTileRectOnCurrentPageAt(0, 0).CenterPoint();
// Drag the first folder child on the second page out of the folder.
AppListItemView* drag_view =
InitiateDrag(AppsGridView::MOUSE, from, folder_apps_grid_view());
// Calculate the target destination for our drag and update the drag to that
// location.
gfx::Point empty_space =
app_list_folder_view()->GetLocalBounds().bottom_center() +
gfx::Vector2d(0, drag_view->height()
/*padding to completely exit folder view*/);
UpdateDrag(AppsGridView::MOUSE, empty_space, folder_apps_grid_view(),
10 /*steps*/);
// Fire the reparent timer that should be started when an item is dragged out
// of folder bounds.
ASSERT_TRUE(folder_apps_grid_view()->FireFolderItemReparentTimerForTest());
// Calculate the coordinates for the drop point. Note that we we are dropping
// into the app list view not the folder view. We will drop between the folder
// and the rest of the app list items in the root level apps grid view.
gfx::Point drop_point = is_rtl_
? GetItemRectOnCurrentPageAt(0, 2).right_center()
: GetItemRectOnCurrentPageAt(0, 0).right_center();
drop_point.set_x(apps_grid_view_->GetMirroredXInView(drop_point.x()));
views::View::ConvertPointToTarget(apps_grid_view_, folder_apps_grid_view(),
&drop_point);
UpdateDrag(AppsGridView::MOUSE, drop_point, folder_apps_grid_view(),
5 /*steps*/);
// End the drag and assert that the item has been dragged out of the folder
// and the app list's grid view has been updated accordingly.
EndDrag(folder_apps_grid_view(), false /*cancel*/);
AppListItem* item_0 = model_->FindItem("Item 0");
AppListItem* item_16 = model_->FindItem("Item 16");
EXPECT_TRUE(item_0->IsInFolder());
EXPECT_FALSE(item_16->IsInFolder());
EXPECT_EQ(folder_item->id(), item_0->folder_id());
EXPECT_EQ(std::string(folder_item->id() +
",Item 16,Item 17,Item 18,Item 19,Item 20,Item 21,Item "
"22,Item 23,Item 24,Item 25,Item 26,Item 27,Item "
"28,Item 29,Item 30,Item 31,Item 32,Item 33,Item 34"),
model_->GetModelContent());
EXPECT_EQ(kTotalItems - 1, folder_item->ChildItemCount());
}
TEST_P(AppsGridViewDragAndDropTest, MouseDragMaxItemsInFolder) {
// Create and add a folder with |kMaxFolderItemsFullscreen - 1| items.
const size_t kMaxItems = GetAppListConfig().max_folder_items_per_page() *
GetAppListConfig().max_folder_pages();
const size_t kTotalItems = kMaxItems - 1;
AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(kTotalItems);
// Create and add another item.
model_->PopulateAppWithId(kTotalItems);
gfx::Point from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
// Dragging one item into the folder, the folder should accept the item.
gfx::Point to = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_, 10 /*steps*/);
EndDrag(apps_grid_view_, false /*cancel*/);
test_api_->LayoutToIdealBounds();
EXPECT_EQ(1u, 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());
}
TEST_P(AppsGridViewDragAndDropTest, MouseDragExceedMaxItemsInFolder) {
// Create and add a folder with |kMaxFolderItemsFullscreen - 1| items.
const size_t kMaxItems = GetAppListConfig().max_folder_items_per_page() *
GetAppListConfig().max_folder_pages();
AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(kMaxItems);
// Create and add another 2 item.
model_->PopulateAppWithId(kMaxItems + 1);
gfx::Point from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
// Dragging the last item over the folder, the folder won't accept the new
// item.
gfx::Point to = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_, 10 /*steps*/);
EndDrag(apps_grid_view_, false /*cancel*/);
test_api_->LayoutToIdealBounds();
EXPECT_EQ(2u, model_->top_level_item_list()->item_count());
EXPECT_EQ(kMaxItems, folder_item->ChildItemCount());
}
TEST_P(AppsGridViewDragAndDropTest, MouseDragMovement) {
// Create and add a folder with |kMaxFolderItemsFullscreen| in it.
const size_t kMaxItems = GetAppListConfig().max_folder_items_per_page() *
GetAppListConfig().max_folder_pages();
model_->CreateAndPopulateFolderWithApps(kMaxItems);
// Create and add another item.
model_->PopulateAppWithId(kMaxItems);
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();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
gfx::Point to = GetItemRectOnCurrentPageAt(0, 0).bottom_left();
to.Offset(0, -1); // Get a point inside the rect.
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_, 10 /*steps*/);
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()));
EndDrag(apps_grid_view_, false /*cancel*/);
}
// Check that moving items around doesn't allow a drop to happen into a full
// folder.
TEST_P(AppsGridViewDragAndDropTest, MouseDragMaxItemsInFolderWithMovement) {
// Create and add a folder with |kMaxFolderItemsFullscreen| in it.
const size_t kMaxItems = GetAppListConfig().max_folder_items_per_page() *
GetAppListConfig().max_folder_pages();
model_->CreateAndPopulateFolderWithApps(kMaxItems);
AppListFolderItem* folder_item = static_cast<AppListFolderItem*>(
model_->top_level_item_list()->item_at(0));
// Create and add another item.
model_->PopulateAppWithId(kMaxItems);
// Drag the new item to the left so that the grid reorders.
gfx::Point from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
AppListItemView* dragged_view =
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
gfx::Point to = GetItemRectOnCurrentPageAt(0, 0).bottom_left();
to.Offset(0, -1); // Get a point inside the rect.
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_, 10 /*steps*/);
gfx::Point folder_in_second_slot =
GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
gfx::Point translated_destination = gfx::PointAtOffsetFromOrigin(
folder_in_second_slot - dragged_view->origin());
ui::MouseEvent drag_event(ui::ET_MOUSE_DRAGGED, translated_destination,
folder_in_second_slot, ui::EventTimeForNow(), 0, 0);
apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE, drag_event);
EndDrag(apps_grid_view_, false /*cancel*/);
// 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();
}
// Dragging an item towards its neighbours should not reorder until the drag is
// past the folder drop point.
TEST_P(AppsGridViewDragAndDropTest, MouseDragItemReorderBeforeFolderDropPoint) {
model_->PopulateApps(2);
contents_view_->apps_container_view()->Layout();
gfx::Point from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
// Item's starting point has to be recelculated because drag state may change
// item coordinates within the apps grid.
from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
int half_tile_width = (GetItemRectOnCurrentPageAt(0, 1).x() -
GetItemRectOnCurrentPageAt(0, 0).x()) /
2;
gfx::Vector2d drag_vector(-half_tile_width - 4, 0);
// Drag left but stop before the folder dropping circle.
UpdateDrag(AppsGridView::MOUSE, from + drag_vector, apps_grid_view_,
10 /*steps*/);
EndDrag(apps_grid_view_, false /*cancel*/);
EXPECT_EQ(std::string("Item 0,Item 1"), model_->GetModelContent());
TestAppListItemViewIndice();
}
TEST_P(AppsGridViewDragAndDropTest, MouseDragItemReorderAfterFolderDropPoint) {
model_->PopulateApps(2);
contents_view_->apps_container_view()->Layout();
gfx::Point from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
// Item's starting point has to be recelculated because drag state may change
// item coordinates within the apps grid.
from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
int half_tile_width = (GetItemRectOnCurrentPageAt(0, 1).x() -
GetItemRectOnCurrentPageAt(0, 0).x()) /
2;
gfx::Vector2d drag_vector(
-2 * half_tile_width -
GetAppListConfig().folder_dropping_circle_radius() - 4,
0);
// Drag left, past the folder dropping circle.
UpdateDrag(AppsGridView::MOUSE, from + drag_vector, apps_grid_view_,
10 /*steps*/);
EndDrag(apps_grid_view_, false /*cancel*/);
EXPECT_EQ(std::string("Item 1,Item 0"), model_->GetModelContent());
TestAppListItemViewIndice();
}
TEST_P(AppsGridViewDragAndDropTest, MouseDragItemReorderDragDownOneRow) {
// 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_->apps_container_view()->Layout();
gfx::Point from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
// Item's starting point has to be recelculated because drag state may change
// item coordinates within the apps grid.
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
int half_tile_width = (GetItemRectOnCurrentPageAt(0, 1).x() -
GetItemRectOnCurrentPageAt(0, 0).x()) /
2;
int tile_height = GetItemRectOnCurrentPageAt(1, 0).y() -
GetItemRectOnCurrentPageAt(0, 0).y();
gfx::Vector2d drag_vector(-half_tile_width, tile_height);
// Drag down, between apps 5 and 6. The gap should open up, making space for
// app 1 in the bottom left.
UpdateDrag(AppsGridView::MOUSE, from + drag_vector, apps_grid_view_,
10 /*steps*/);
EndDrag(apps_grid_view_, false /*cancel*/);
EXPECT_EQ(std::string("Item 0,Item 2,Item 3,Item 4,Item 5,Item 1,Item 6"),
model_->GetModelContent());
TestAppListItemViewIndice();
}
TEST_P(AppsGridViewDragAndDropTest, MouseDragItemReorderDragUpOneRow) {
// 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_->apps_container_view()->Layout();
gfx::Point from = GetItemRectOnCurrentPageAt(1, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
// Item's starting point has to be recelculated because drag state may change
// item coordinates within the apps grid.
from = GetItemRectOnCurrentPageAt(1, 0).CenterPoint();
int half_tile_width = (GetItemRectOnCurrentPageAt(0, 1).x() -
GetItemRectOnCurrentPageAt(0, 0).x()) /
2;
int tile_height = GetItemRectOnCurrentPageAt(1, 0).y() -
GetItemRectOnCurrentPageAt(0, 0).y();
gfx::Vector2d drag_vector(half_tile_width, -tile_height);
// Drag up, between apps 0 and 2. The gap should open up, making space for app
// 1 in the top right.
UpdateDrag(AppsGridView::MOUSE, from + drag_vector, apps_grid_view_,
10 /*steps*/);
EndDrag(apps_grid_view_, false /*cancel*/);
test_api_->LayoutToIdealBounds();
EXPECT_EQ(std::string("Item 0,Item 5,Item 1,Item 2,Item 3,Item 4,Item 6"),
model_->GetModelContent());
TestAppListItemViewIndice();
}
TEST_P(AppsGridViewDragAndDropTest, MouseDragItemReorderDragPastLastApp) {
// 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_->apps_container_view()->Layout();
gfx::Point from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
// Item's starting point has to be recelculated because drag state may change
// item coordinates within the apps grid.
from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
int half_tile_width = (GetItemRectOnCurrentPageAt(0, 1).x() -
GetItemRectOnCurrentPageAt(0, 0).x()) /
2;
int tile_height = GetItemRectOnCurrentPageAt(1, 0).y() -
GetItemRectOnCurrentPageAt(0, 0).y();
gfx::Vector2d drag_vector(half_tile_width, 2 * tile_height);
// Drag up, between apps 0 and 2. The gap should open up, making space for app
// 1 in the top right.
UpdateDrag(AppsGridView::MOUSE, from + drag_vector, apps_grid_view_,
10 /*steps*/);
EndDrag(apps_grid_view_, false /*cancel*/);
EXPECT_EQ(std::string("Item 0,Item 2,Item 3,Item 4,Item 5,Item 6,Item 1"),
model_->GetModelContent());
TestAppListItemViewIndice();
}
// Dragging folder over item_2 should leads to re-ordering these two items.
TEST_P(AppsGridViewDragAndDropTest, MouseDragFolderOverItemReorder) {
size_t kTotalItems = 2;
AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(kTotalItems);
model_->PopulateAppWithId(kTotalItems);
gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
gfx::Point to = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_);
EndDrag(apps_grid_view_, false /*cancel*/);
test_api_->LayoutToIdealBounds();
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());
TestAppListItemViewIndice();
}
// Canceling drag should keep existing order.
TEST_P(AppsGridViewDragAndDropTest, MouseDragWithCancelKeepsOrder) {
size_t kTotalItems = 2;
model_->PopulateApps(kTotalItems);
gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
gfx::Point to = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_, 10 /*steps*/);
EndDrag(apps_grid_view_, true /*cancel*/);
EXPECT_EQ(std::string("Item 0,Item 1"), model_->GetModelContent());
test_api_->LayoutToIdealBounds();
}
// Deleting an item keeps remaining intact.
TEST_P(AppsGridViewDragAndDropTest, MouseDragWithDeleteItemKeepsOrder) {
size_t kTotalItems = 3;
model_->PopulateApps(kTotalItems);
gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
gfx::Point to = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_, 10 /*steps*/);
model_->DeleteItem(model_->GetItemName(2));
EndDrag(apps_grid_view_, false /*cancel*/);
EXPECT_EQ(std::string("Item 0,Item 1"), model_->GetModelContent());
test_api_->LayoutToIdealBounds();
}
// Adding a launcher item cancels the drag and respects the order.
TEST_P(AppsGridViewDragAndDropTest, MouseDragWithAddItemKeepsOrder) {
size_t kTotalItems = 2;
model_->PopulateApps(kTotalItems);
gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
gfx::Point to = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_, 10 /*steps*/);
model_->CreateAndAddItem("Extra");
EndDrag(apps_grid_view_, false /*cancel*/);
EXPECT_EQ(std::string("Item 0,Item 1,Extra"), model_->GetModelContent());
test_api_->LayoutToIdealBounds();
}
// Regression test for crash bug. https://crbug.com/1166011.
TEST_F(AppsGridViewTest, MoveItemInModelPastEndOfList) {
model_->PopulateApps(20);
// I speculate that the item list is missing an item, but PagedViewStructure
// doesn't know about it. This could happen if an item was deleted during a
// period that AppsGridView was not observing the list.
AppListItemList* item_list = model_->top_level_item_list();
item_list->RemoveObserver(apps_grid_view_);
DeleteItemAt(item_list, 19);
item_list->AddObserver(apps_grid_view_);
ASSERT_EQ(19u, item_list->item_count());
// Try to move the first item to the end of the page. PagedViewStructure is
// out of sync with AppListItemList, so it will return a target item list
// index off the end of the list.
AppListItemView* first_item = GetItemViewInTopLevelGrid(0);
MoveItemInModel(first_item, GridIndex(0, 19));
// No crash. Item moved to the end.
ASSERT_EQ(19u, item_list->item_count());
EXPECT_EQ("Item 0", item_list->item_at(18)->id());
}
// Test that control+arrow swaps app within the same page.
TEST_F(AppsGridViewTest, ControlArrowSwapsAppsWithinSamePage) {
model_->PopulateApps(GetTilesPerPage());
AppListItemView* moving_item = GetItemViewInTopLevelGrid(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, GetItemViewInTopLevelGrid(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 = GetItemViewInTopLevelGrid(1);
SimulateKeyPress(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN);
EXPECT_EQ(moving_item, GetItemViewInTopLevelGrid(1));
EXPECT_EQ(swapped_item, GetItemViewInTopLevelGrid(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 = GetItemViewInTopLevelGrid(apps_grid_view_->cols() + 1);
SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN);
EXPECT_EQ(moving_item,
GetItemViewInTopLevelGrid(apps_grid_view_->cols() + 1));
EXPECT_EQ(swapped_item, GetItemViewInTopLevelGrid(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 = GetItemViewInTopLevelGrid(apps_grid_view_->cols());
SimulateKeyPress(ui::VKEY_LEFT, ui::EF_CONTROL_DOWN);
EXPECT_EQ(moving_item, GetItemViewInTopLevelGrid(apps_grid_view_->cols()));
EXPECT_EQ(swapped_item,
GetItemViewInTopLevelGrid(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 = GetItemViewInTopLevelGrid(0);
SimulateKeyPress(ui::VKEY_UP, ui::EF_CONTROL_DOWN);
EXPECT_EQ(moving_item, GetItemViewInTopLevelGrid(0));
EXPECT_EQ(swapped_item, GetItemViewInTopLevelGrid(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());
AppListItemView* moving_item = GetItemViewInTopLevelGrid(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());
AppListItemView* moving_item = GetItemViewInTopLevelGrid(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() * 2);
AppListItemView* moving_item = GetItemViewInTopLevelGrid(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 =
GetItemViewInTopLevelGrid(apps_grid_view_->cols() - 1);
AppListItemView* swapped_item =
GetItemViewInTopLevelGrid(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, GetItemViewInTopLevelGrid(apps_grid_view_->cols()));
EXPECT_EQ(swapped_item,
GetItemViewInTopLevelGrid(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());
// 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();
model_->PopulateApps(kTilesPerPageStart);
// Focus the last item on the page.
AppListItemView* moving_item =
GetItemViewInTopLevelGrid(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());
AppListItemView* moving_item =
GetItemViewInTopLevelGrid(GetTilesPerPage() - 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());
AppListItemView* moving_item =
GetItemViewInTopLevelGrid(GetTilesPerPage() - 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());
AppListItemView* moving_item =
GetItemViewInTopLevelGrid(GetTilesPerPage() - 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());
// Select the first item in the grid, folder it with the item to the right.
AppListItemView* first_item = GetItemViewInTopLevelGrid(0);
const std::string first_item_id = first_item->item()->id();
const std::string second_item_id = GetItemViewInTopLevelGrid(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 = GetItemViewInTopLevelGrid(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("Apps.AppListAppMovingType",
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("Apps.AppListAppMovingType",
kMoveByKeyboardIntoFolder, 1);
// Move selection to the item to the right of the folder and put it in the
// folder.
apps_grid_view_->GetFocusManager()->SetFocusedView(
GetItemViewInTopLevelGrid(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("Apps.AppListAppMovingType",
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("Apps.AppListAppMovingType",
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("Apps.AppListAppMovingType",
kMoveByKeyboardIntoFolder, 4);
}
// Tests that foldering an item that is on a different page fails.
TEST_F(AppsGridViewTest, ControlShiftArrowFailsToFolderAcrossPages) {
model_->PopulateApps(2 * GetTilesPerPage());
// 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 = GetItemViewInTopLevelGrid(kNumberOfApps - 2);
const std::string first_item_id = moving_item->item()->id();
const std::string second_item_id =
GetItemViewInTopLevelGrid(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 = GetItemViewInTopLevelGrid(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(AppsGridViewDragAndDropTest, MouseDragFlipToNextPage) {
// Create 3 full pages of apps.
model_->PopulateApps(3 * GetTilesPerPage());
gfx::Point item_center = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
const gfx::Rect apps_grid_bounds = apps_grid_view_->GetLocalBounds();
// Drag an item to the bottom to start flipping pages.
page_flip_waiter_->Reset();
InitiateDrag(AppsGridView::MOUSE, item_center, apps_grid_view_);
gfx::Point apps_grid_bottom_center =
gfx::Point(apps_grid_bounds.width() / 2, apps_grid_bounds.bottom() + 1);
UpdateDrag(AppsGridView::MOUSE, apps_grid_bottom_center, apps_grid_view_,
5 /*steps*/);
while (HasPendingPageFlip(apps_grid_view_)) {
page_flip_waiter_->Wait();
}
// We flip 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(apps_grid_bottom_center, GetDragViewCenter());
// End the drag to satisfy checks in AppsGridView destructor.
EndDrag(apps_grid_view_, /*cancel=*/true);
}
TEST_P(AppsGridViewDragAndDropTest, MouseDragFlipToPreviousPage) {
// Create 3 full pages of apps.
model_->PopulateApps(3 * GetTilesPerPage());
// Select the last page.
GetPaginationModel()->SelectPage(2, /*animate=*/false);
gfx::Point item_center = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
// Drag an item to the top to start flipping pages.
page_flip_waiter_->Reset();
InitiateDrag(AppsGridView::MOUSE, item_center, apps_grid_view_);
gfx::Point apps_grid_top_center(apps_grid_view_->GetLocalBounds().width() / 2,
0);
UpdateDrag(AppsGridView::MOUSE, apps_grid_top_center, apps_grid_view_,
5 /*steps*/);
while (HasPendingPageFlip(apps_grid_view_)) {
page_flip_waiter_->Wait();
}
// We flipped back to the first page.
EXPECT_EQ("1,0", page_flip_waiter_->selected_pages());
EXPECT_EQ(0, GetPaginationModel()->selected_page());
EXPECT_EQ(apps_grid_top_center, GetDragViewCenter());
// End the drag to satisfy checks in AppsGridView destructor.
EndDrag(apps_grid_view_, /*cancel=*/true);
}
TEST_P(AppsGridViewDragAndDropTest, UpdateFolderBackgroundOnCancelDrag) {
const int kTotalItems = 4;
TestAppsGridViewFolderDelegate folder_delegate;
apps_grid_view_->set_folder_delegate(&folder_delegate);
model_->PopulateApps(kTotalItems);
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);
EndDrag(apps_grid_view_, true /*cancel*/);
EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"),
model_->GetModelContent());
}
// Test focus change before dragging an item. (See https://crbug.com/834682)
TEST_F(AppsGridViewTest, FocusOfDraggedViewBeforeDrag) {
model_->PopulateApps(1);
contents_view_->apps_container_view()->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());
}
// Test focus change during dragging an item. (See https://crbug.com/834682)
TEST_P(AppsGridViewDragAndDropTest, FocusOfDraggedViewDuringDrag) {
model_->PopulateApps(1);
contents_view_->apps_container_view()->Layout();
auto* search_box = contents_view_->GetSearchBoxView()->search_box();
auto* item_view = apps_grid_view_->view_model()->view_at(0);
const gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
const gfx::Point to = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
// Dragging the item towards its right.
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_, 10 /*steps*/);
EXPECT_FALSE(search_box->HasFocus());
EXPECT_TRUE(item_view->HasFocus());
EndDrag(apps_grid_view_, false /*cancel*/);
}
// Test focus change after dragging an item. (See https://crbug.com/834682)
TEST_P(AppsGridViewDragAndDropTest, FocusOfDraggedViewAfterDrag) {
model_->PopulateApps(1);
contents_view_->apps_container_view()->Layout();
auto* search_box = contents_view_->GetSearchBoxView()->search_box();
auto* item_view = apps_grid_view_->view_model()->view_at(0);
const gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
const gfx::Point to = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_, 10 /*steps*/);
EndDrag(apps_grid_view_, false /*cancel*/);
EXPECT_FALSE(search_box->HasFocus());
EXPECT_TRUE(item_view->HasFocus());
}
TEST_P(AppsGridViewTabletTest, Basic) {
base::HistogramTester histogram_tester;
model_->PopulateApps(GetTilesPerPage() + 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);
}
// Make sure that a folder icon resets background blur after scrolling the
// apps grid without completing any transition (See
// https://crbug.com/1049275). The background blur is masked by the apps
// grid's layer mask.
TEST_P(AppsGridViewTabletTest, EnsureBlurAfterScrollingWithoutTransition) {
// Create a folder with 2 apps. Then add apps until a second page is
// created.
model_->CreateAndPopulateFolderWithApps(2);
model_->PopulateApps(GetTilesPerPage());
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_upwards(
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_update_downwards(
apps_grid_view_origin.x(), apps_grid_view_origin.y(), 0,
base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, 0, 15));
ui::GestureEvent scroll_end(
apps_grid_view_origin.x(), apps_grid_view_origin.y(), 0,
base::TimeTicks(), ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END));
AppListItemView* folder_view = GetItemViewInTopLevelGrid(0);
ASSERT_TRUE(folder_view->is_folder());
ASSERT_FALSE(apps_grid_view_->layer()->layer_mask_layer());
// On the first page drag upwards, there should not be a page switch and the
// layer mask should make the folder lose blur.
ASSERT_EQ(0, GetPaginationModel()->selected_page());
apps_grid_view_->OnGestureEvent(&scroll_begin);
EXPECT_TRUE(scroll_begin.handled());
apps_grid_view_->OnGestureEvent(&scroll_update_upwards);
EXPECT_TRUE(scroll_update_upwards.handled());
ASSERT_EQ(0, GetPaginationModel()->selected_page());
ASSERT_TRUE(apps_grid_view_->layer()->layer_mask_layer());
// Continue drag, now switching directions and release. There shouldn't be
// any transition and the mask layer should've been reset.
apps_grid_view_->OnGestureEvent(&scroll_update_downwards);
EXPECT_TRUE(scroll_update_downwards.handled());
apps_grid_view_->OnGestureEvent(&scroll_end);
EXPECT_TRUE(scroll_end.handled());
EXPECT_FALSE(GetPaginationModel()->has_transition());
EXPECT_FALSE(apps_grid_view_->layer()->layer_mask_layer());
}
TEST_F(AppsGridViewTest, PopulateAppsGridWithTwoApps) {
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());
}
TEST_F(AppsGridViewTest, PopulateAppsGridWithAFolder) {
// Creates a folder item.
const size_t kTotalItems = GetAppListConfig().max_folder_items_per_page();
AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(kTotalItems);
// Open the folder and check it's contents.
test_api_->Update();
test_api_->PressItemAt(0);
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());
EXPECT_EQ(4, folder_apps_grid_view()->cols());
EXPECT_EQ(4, folder_apps_grid_view()->rows_per_page());
ash::PaginationModel* folder_pagination_model =
folder_apps_grid_view()->pagination_model();
EXPECT_EQ(1, folder_pagination_model->total_pages());
EXPECT_EQ(0, folder_pagination_model->selected_page());
EXPECT_TRUE(folder_apps_grid_view()->IsInFolder());
}
TEST_P(AppsGridViewDragAndDropTest, MoveAnItemToNewEmptyPage) {
const int kApps = 2;
model_->PopulateApps(kApps);
const views::ViewModelT<AppListItemView>* view_model =
apps_grid_view_->view_model();
gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
gfx::Point to_in_next_page =
test_api_->GetItemTileRectAtVisualIndex(1, 0).CenterPoint();
// Drag the first item to the page bottom.
UpdateDragToNeighborPage(true /* next_page */, 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(AppsGridViewDragAndDropTest, MoveLastItemToCreateFolderInNextPage) {
const int kApps = 2;
model_->PopulateApps(kApps);
const views::ViewModelT<AppListItemView>* view_model =
apps_grid_view_->view_model();
gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
gfx::Point to_in_next_page =
test_api_->GetItemTileRectAtVisualIndex(1, 0).CenterPoint();
// Drag the first item to next page and drag the second item to overlap with
// the first item.
UpdateDragToNeighborPage(true /* next_page */, to_in_next_page);
GetPaginationModel()->SelectPage(0, false);
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
UpdateDragToNeighborPage(true /* next_page */, 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, GetPaginationModel()->total_pages());
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(AppsGridViewDragAndDropTest, MoveLastItemForReorderInNextPage) {
const int kApps = 2;
model_->PopulateApps(kApps);
const views::ViewModelT<AppListItemView>* view_model =
apps_grid_view_->view_model();
gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
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());
// Drag the first item to next page and drag the second item to the left of
// the first item.
UpdateDragToNeighborPage(true /* next_page */, to_in_next_page);
GetPaginationModel()->SelectPage(0, false);
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
UpdateDragToNeighborPage(true /* next_page */, 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(AppsGridViewDragAndDropTest, MoveLastItemToNewEmptyPage) {
const int kApps = 1;
model_->PopulateApps(kApps);
const views::ViewModelT<AppListItemView>* view_model =
apps_grid_view_->view_model();
gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
gfx::Point to_in_next_page =
test_api_->GetItemTileRectAtVisualIndex(1, 0).CenterPoint();
// Drag the item to next page.
UpdateDragToNeighborPage(true /* next_page */, to_in_next_page);
GetPaginationModel()->SelectPage(0, false);
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
UpdateDragToNeighborPage(true /* next_page */, 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());
}
// There's no "page break" item at the end of first page with full grid.
TEST_F(AppsGridViewTest, NoPageBreakItemWithFullGrid) {
// There are two pages and last item is on second page.
const int kApps = 2 + GetTilesPerPage();
model_->PopulateApps(kApps);
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());
}
TEST_P(AppsGridViewDragAndDropTest, PageBreakItemAddedAfterDrag) {
// There are two pages and last item is on second page.
const int kApps = 2 + GetTilesPerPage();
model_->PopulateApps(kApps);
gfx::Point from = test_api_->GetItemTileRectAtVisualIndex(1, 1).CenterPoint();
GetPaginationModel()->SelectPage(1, false);
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
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());
// Drag the last item to the first item's left position in previous page.
UpdateDragToNeighborPage(false /* next_page */, to_in_previous_page);
// A "page break" item is added to split the pages.
std::string 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() - 1)
model_content.append(",PageBreakItem");
}
EXPECT_EQ(model_content, model_->GetModelContent());
}
TEST_P(AppsGridViewDragAndDropTest, MoveItemToPreviousFullPage) {
// There are two pages and last item is on second page.
const int kApps = 2 + GetTilesPerPage();
model_->PopulateApps(kApps);
const views::ViewModelT<AppListItemView>* view_model =
apps_grid_view_->view_model();
gfx::Point from = test_api_->GetItemTileRectAtVisualIndex(1, 1).CenterPoint();
GetPaginationModel()->SelectPage(1, false);
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
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());
// Drag the last item to the first item's left position in previous
// page.
UpdateDragToNeighborPage(false /* next_page */, 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) {
int page = i / GetTilesPerPage();
int slot = i % GetTilesPerPage();
EXPECT_EQ(view_model->view_at(i),
test_api_->GetViewAtVisualIndex(page, slot));
EXPECT_EQ("Item " + base::NumberToString((i + kApps - 1) % kApps),
view_model->view_at(i)->item()->id());
}
}
TEST_P(AppsGridViewDragAndDropTest, MoveItemSubsequentDragKeepPageBreak) {
// There are two pages and last item is on second page.
const int kApps = 2 + GetTilesPerPage();
model_->PopulateApps(kApps);
const views::ViewModelT<AppListItemView>* view_model =
apps_grid_view_->view_model();
gfx::Point from = test_api_->GetItemTileRectAtVisualIndex(1, 1).CenterPoint();
GetPaginationModel()->SelectPage(1, false);
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
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());
// Drag the last item to the first item's left position in previous
// page twice.
UpdateDragToNeighborPage(false /* next_page */, to_in_previous_page);
// Again drag the last item to the first item's left position in previous
// page.
GetPaginationModel()->SelectPage(1, false);
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
UpdateDragToNeighborPage(false /* next_page */, 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) {
int page = i / GetTilesPerPage();
int slot = i % GetTilesPerPage();
EXPECT_EQ(view_model->view_at(i),
test_api_->GetViewAtVisualIndex(page, slot));
EXPECT_EQ("Item " + base::NumberToString((i + kApps - 2) % kApps),
view_model->view_at(i)->item()->id());
}
// A "page break" item still exists.
std::string 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() - 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 = GetItemViewInTopLevelGrid(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(GetPaginationModel()->total_pages(), 2);
histogram_tester.ExpectBucketCount(
"Apps.AppList.AppsGridAddPage",
AppListPageCreationType::kMovingAppWithKeyboard, 1);
}
TEST_P(AppsGridViewDragAndDropTest, CreateANewPageByDraggingLogsMetrics) {
base::HistogramTester histogram_tester;
model_->PopulateApps(2);
gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
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);
// Drag down the first item until a new page is created.
// For fullscreen, drag to the bottom/right of bounds.
page_flip_waiter_->Reset();
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_);
while (HasPendingPageFlip(apps_grid_view_))
page_flip_waiter_->Wait();
EndDrag(apps_grid_view_, false /*cancel*/);
ASSERT_EQ(GetPaginationModel()->total_pages(), 2);
histogram_tester.ExpectBucketCount("Apps.AppList.AppsGridAddPage",
AppListPageCreationType::kDraggingApp, 1);
}
TEST_F(AppsGridViewTest, CreateANewPageByAddingAppLogsMetrics) {
base::HistogramTester histogram_tester;
model_->PopulateApps(GetTilesPerPage());
// Add an item to simulate installing or syncing, the metric should be
// recorded.
model_->CreateAndAddItem("Extra App");
ASSERT_EQ(GetPaginationModel()->total_pages(), 2);
histogram_tester.ExpectBucketCount("Apps.AppList.AppsGridAddPage",
AppListPageCreationType::kSyncOrInstall,
1);
}
TEST_P(AppsGridViewCardifiedStateTest, PeekingCardOnLastPage) {
// Create only one page with two apps.
model_->PopulateApps(2);
// Start cardified apps grid.
const gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
EXPECT_TRUE(apps_grid_view_->cardified_state_for_testing());
EXPECT_EQ(2, apps_grid_view_->BackgroundCardCountForTesting());
EndDrag(apps_grid_view_, false /*cancel*/);
}
TEST_P(AppsGridViewCardifiedStateTest, BackgroundCardBounds) {
model_->PopulateApps(30);
gfx::NativeView parent = GetContext();
parent->SetBounds(gfx::Rect(gfx::Point(0, 0), gfx::Size(1024, 768)));
app_list_view_->OnParentWindowBoundsChanged();
apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
// Enter cardified state.
const gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
ASSERT_TRUE(apps_grid_view_->cardified_state_for_testing());
ASSERT_EQ(3, apps_grid_view_->BackgroundCardCountForTesting());
// Verify that all items in the current page fit within the background card.
gfx::Rect background_card_bounds =
apps_grid_view_->GetBackgroundCardBoundsForTesting(0);
gfx::Rect clip_rect = apps_grid_view_->layer()->clip_rect();
gfx::Rect first_item_bounds = GetItemRectOnCurrentPageAt(0, 0);
EXPECT_TRUE(background_card_bounds.Contains(first_item_bounds))
<< " background card bounds " << background_card_bounds.ToString()
<< " item bounds " << first_item_bounds.ToString();
EXPECT_TRUE(clip_rect.Contains(first_item_bounds))
<< " clip rect " << clip_rect.ToString() << " item bounds "
<< first_item_bounds.ToString();
gfx::Rect last_item_bounds = GetItemRectOnCurrentPageAt(3, 4);
EXPECT_TRUE(background_card_bounds.Contains(last_item_bounds))
<< " background card bounds " << background_card_bounds.ToString()
<< " item bounds " << last_item_bounds.ToString();
EXPECT_TRUE(clip_rect.Contains(last_item_bounds))
<< " clip rect " << clip_rect.ToString() << " item bounds "
<< last_item_bounds.ToString();
// Simulate rotation by updating the bounds of the widget in which the app
// list was shown.
parent->SetBounds(gfx::Rect(gfx::Point(0, 0), gfx::Size(768, 1024)));
app_list_view_->OnParentWindowBoundsChanged();
apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
ASSERT_TRUE(apps_grid_view_->cardified_state_for_testing());
ASSERT_EQ(3, apps_grid_view_->BackgroundCardCountForTesting());
// Verify that all items in the current page fit within the background card.
background_card_bounds =
apps_grid_view_->GetBackgroundCardBoundsForTesting(0);
clip_rect = apps_grid_view_->layer()->clip_rect();
first_item_bounds = GetItemRectOnCurrentPageAt(0, 0);
EXPECT_TRUE(background_card_bounds.Contains(first_item_bounds))
<< " background card bounds " << background_card_bounds.ToString()
<< " item bounds " << first_item_bounds.ToString();
EXPECT_TRUE(clip_rect.Contains(first_item_bounds))
<< " clip rect " << clip_rect.ToString() << " item bounds "
<< first_item_bounds.ToString();
last_item_bounds = GetItemRectOnCurrentPageAt(4, 3);
EXPECT_TRUE(background_card_bounds.Contains(last_item_bounds))
<< " background card bounds " << background_card_bounds.ToString()
<< " item bounds " << last_item_bounds.ToString();
EXPECT_TRUE(clip_rect.Contains(last_item_bounds))
<< " clip rect " << clip_rect.ToString() << " item bounds "
<< last_item_bounds.ToString();
EndDrag(apps_grid_view_, false /*cancel*/);
EXPECT_EQ(gfx::Rect(), apps_grid_view_->layer()->clip_rect());
EXPECT_FALSE(apps_grid_view_->cardified_state_for_testing());
EXPECT_EQ(0, apps_grid_view_->BackgroundCardCountForTesting());
}
TEST_P(AppsGridViewCardifiedStateTest, BackgroundCardBoundsOnSecondPage) {
model_->PopulateApps(30);
gfx::NativeView parent = GetContext();
parent->SetBounds(gfx::Rect(gfx::Point(0, 0), gfx::Size(1024, 768)));
app_list_view_->OnParentWindowBoundsChanged();
apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
// Enter cardified state, and drag the item to the second apps grid page.
const gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
const gfx::Point to_in_next_page =
test_api_->GetItemTileRectAtVisualIndex(1, 0).CenterPoint();
// Drag the first item to the next page to create another page.
UpdateDragToNeighborPage(true /* next_page */, to_in_next_page);
// Trigger cardified state again.
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
ASSERT_TRUE(apps_grid_view_->cardified_state_for_testing());
ASSERT_EQ(3, apps_grid_view_->BackgroundCardCountForTesting());
// Verify that all items in the current page fit within the background card.
gfx::Rect background_card_bounds =
apps_grid_view_->GetBackgroundCardBoundsForTesting(1);
gfx::Rect clip_rect = apps_grid_view_->layer()->clip_rect();
gfx::Rect first_item_bounds = GetItemRectOnCurrentPageAt(0, 0);
EXPECT_TRUE(background_card_bounds.Contains(first_item_bounds))
<< " background card bounds " << background_card_bounds.ToString()
<< " item bounds " << first_item_bounds.ToString();
EXPECT_TRUE(clip_rect.Contains(first_item_bounds))
<< " clip rect " << clip_rect.ToString() << " item bounds "
<< first_item_bounds.ToString();
gfx::Rect last_item_bounds = GetItemRectOnCurrentPageAt(3, 4);
EXPECT_TRUE(background_card_bounds.Contains(last_item_bounds))
<< " background card bounds " << background_card_bounds.ToString()
<< " item bounds " << last_item_bounds.ToString();
EXPECT_TRUE(clip_rect.Contains(last_item_bounds))
<< " clip rect " << clip_rect.ToString() << " item bounds "
<< last_item_bounds.ToString();
// Simulate rotation by updating the bounds of the widget in which the app
// list was shown.
parent->SetBounds(gfx::Rect(gfx::Point(0, 0), gfx::Size(768, 1024)));
app_list_view_->OnParentWindowBoundsChanged();
apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
ASSERT_TRUE(apps_grid_view_->cardified_state_for_testing());
ASSERT_EQ(3, apps_grid_view_->BackgroundCardCountForTesting());
// Verify that all items in the current page fit within the background card.
background_card_bounds =
apps_grid_view_->GetBackgroundCardBoundsForTesting(1);
clip_rect = apps_grid_view_->layer()->clip_rect();
first_item_bounds = GetItemRectOnCurrentPageAt(0, 0);
EXPECT_TRUE(background_card_bounds.Contains(first_item_bounds))
<< " background card bounds " << background_card_bounds.ToString()
<< " item bounds " << first_item_bounds.ToString();
EXPECT_TRUE(clip_rect.Contains(first_item_bounds))
<< " clip rect " << clip_rect.ToString() << " item bounds "
<< first_item_bounds.ToString();
last_item_bounds = GetItemRectOnCurrentPageAt(4, 3);
EXPECT_TRUE(background_card_bounds.Contains(last_item_bounds))
<< " background card bounds " << background_card_bounds.ToString()
<< " item bounds " << last_item_bounds.ToString();
EXPECT_TRUE(clip_rect.Contains(last_item_bounds))
<< " clip rect " << clip_rect.ToString() << " item bounds "
<< last_item_bounds.ToString();
EndDrag(apps_grid_view_, false /*cancel*/);
EXPECT_EQ(gfx::Rect(), apps_grid_view_->layer()->clip_rect());
EXPECT_FALSE(apps_grid_view_->cardified_state_for_testing());
EXPECT_EQ(0, apps_grid_view_->BackgroundCardCountForTesting());
}
TEST_P(AppsGridViewCardifiedStateTest,
PeekingCardOnLastPageAfterCreatingNewPage) {
// Create only one page with two apps.
model_->PopulateApps(2);
const gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
const gfx::Point to_in_next_page =
test_api_->GetItemTileRectAtVisualIndex(1, 0).CenterPoint();
// Drag the first item to the next page to create another page.
UpdateDragToNeighborPage(true /* next_page */, to_in_next_page);
// Trigger cardified state again.
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
EXPECT_TRUE(apps_grid_view_->cardified_state_for_testing());
EXPECT_EQ(3, apps_grid_view_->BackgroundCardCountForTesting());
EndDrag(apps_grid_view_, false /*cancel*/);
}
TEST_P(AppsGridViewCardifiedStateTest, AppsGridIsCardifiedDuringDrag) {
// Create only one page with two apps.
model_->PopulateApps(2);
const gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
EXPECT_TRUE(apps_grid_view_->cardified_state_for_testing());
EndDrag(apps_grid_view_, false /*cancel*/);
EXPECT_FALSE(apps_grid_view_->cardified_state_for_testing());
}
TEST_P(AppsGridViewCardifiedStateTest,
DragWithinFolderDoesNotEnterCardifiedState) {
// Creates a folder item and open it.
const size_t kTotalItems = GetAppListConfig().max_folder_items_per_page();
model_->CreateAndPopulateFolderWithApps(kTotalItems);
test_api_->Update();
test_api_->PressItemAt(0);
AppsGridViewTestApi folder_grid_test_api(folder_apps_grid_view());
const gfx::Point from =
folder_grid_test_api.GetItemTileRectOnCurrentPageAt(0, 0).CenterPoint();
// Drag the first folder child within the folder.
InitiateDrag(AppsGridView::MOUSE, from, folder_apps_grid_view());
const gfx::Point to =
folder_grid_test_api.GetItemTileRectOnCurrentPageAt(0, 1).CenterPoint();
UpdateDrag(AppsGridView::MOUSE, to, folder_apps_grid_view(), 10 /*steps*/);
// The folder item reparent timer should not be triggered.
ASSERT_FALSE(folder_apps_grid_view()->FireFolderItemReparentTimerForTest());
EXPECT_FALSE(apps_grid_view_->cardified_state_for_testing());
EndDrag(folder_apps_grid_view(), false /*cancel*/);
}
TEST_P(AppsGridViewCardifiedStateTest, DragOutsideFolderEntersCardifiedState) {
// Create a folder item with some apps and open it.
model_->CreateAndPopulateFolderWithApps(3);
test_api_->Update();
test_api_->PressItemAt(0);
AppsGridViewTestApi folder_grid_test_api(folder_apps_grid_view());
const gfx::Point from =
folder_grid_test_api.GetItemTileRectOnCurrentPageAt(0, 0).CenterPoint();
// Drag the first folder child out of the folder.
AppListItemView* drag_view =
InitiateDrag(AppsGridView::MOUSE, from, folder_apps_grid_view());
const gfx::Point to =
app_list_folder_view()->GetLocalBounds().bottom_center() +
gfx::Vector2d(0, drag_view->height()
/*padding to completely exit folder view*/);
UpdateDrag(AppsGridView::MOUSE, to, folder_apps_grid_view(), 10 /*steps*/);
// Fire the reparent timer that should be started when an item is dragged out
// of folder bounds.
ASSERT_TRUE(folder_apps_grid_view()->FireFolderItemReparentTimerForTest());
EXPECT_TRUE(apps_grid_view_->cardified_state_for_testing());
EndDrag(folder_apps_grid_view(), false /*cancel*/);
EXPECT_FALSE(apps_grid_view_->cardified_state_for_testing());
}
TEST_P(AppsGridViewCardifiedStateTest,
DragItemIntoFolderStaysCaInCardifiedState) {
// Create a folder item with some apps. Add another app to the main grid.
model_->CreateAndPopulateFolderWithApps(2);
model_->PopulateApps(1);
const gfx::Point from = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
InitiateDrag(AppsGridView::MOUSE, from, apps_grid_view_);
// Dragging item_1 over folder to expand it.
const gfx::Point to = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_, 10 /*steps*/);
EXPECT_TRUE(apps_grid_view_->cardified_state_for_testing());
EndDrag(apps_grid_view_, false /*cancel*/);
EXPECT_FALSE(apps_grid_view_->cardified_state_for_testing());
test_api_->WaitForItemMoveAnimationDone();
test_api_->LayoutToIdealBounds();
}
} // namespace test
} // namespace ash