blob: b1c536152ce855296e971611138ed269e821479d [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 <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/app_list_metrics.h"
#include "ash/app_list/model/app_list_folder_item.h"
#include "ash/app_list/model/app_list_item.h"
#include "ash/app_list/model/app_list_model.h"
#include "ash/app_list/model/app_list_test_model.h"
#include "ash/app_list/model/search/test_search_result.h"
#include "ash/app_list/test/app_list_test_helper.h"
#include "ash/app_list/views/app_list_bubble_search_page.h"
#include "ash/app_list/views/app_list_bubble_view.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_context_menu.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/paged_apps_grid_view.h"
#include "ash/app_list/views/scrollable_apps_grid_view.h"
#include "ash/app_list/views/search_box_view.h"
#include "ash/app_list/views/search_result_page_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/constants/ash_features.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/shelf_item_delegate.h"
#include "ash/public/cpp/shelf_model.h"
#include "ash/public/cpp/test/test_shelf_item_delegate.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/layer_animation_stopped_waiter.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.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/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/controls/textfield/textfield.h"
namespace ash {
namespace test {
namespace {
constexpr int kNumOfSuggestedApps = 3;
constexpr size_t kMaxItemsPerFolderPage =
AppListFolderView::kMaxFolderColumns *
AppListFolderView::kMaxPagedFolderRows;
constexpr size_t kMaxItemsInFolder = 48;
class ShelfItemFactoryFake : public ShelfModel::ShelfItemFactory {
public:
virtual ~ShelfItemFactoryFake() = default;
bool CreateShelfItemForAppId(
const std::string& app_id,
ShelfItem* item,
std::unique_ptr<ShelfItemDelegate>* delegate) override {
*item = ShelfItem();
item->id = ShelfID(app_id);
*delegate = std::make_unique<TestShelfItemDelegate>(item->id);
return true;
}
};
class PageFlipWaiter : public PaginationModelObserver {
public:
explicit PageFlipWaiter(PaginationModel* model) : model_(model) {
model_->AddObserver(this);
}
PageFlipWaiter(const PageFlipWaiter&) = delete;
PageFlipWaiter& operator=(const PageFlipWaiter&) = delete;
~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_;
PaginationModel* model_ = nullptr;
bool wait_ = false;
std::string selected_pages_;
};
// 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(const WindowDeletionWaiter&) = delete;
WindowDeletionWaiter& operator=(const WindowDeletionWaiter&) = delete;
~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_;
};
// 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 PostPageFlipTask : public PaginationModelObserver {
public:
PostPageFlipTask(PaginationModel* model, base::OnceClosure task)
: model_(model), task_(std::move(task)) {
model_->AddObserver(this);
}
PostPageFlipTask(const PostPageFlipTask&) = delete;
PostPageFlipTask& operator=(const PostPageFlipTask&) = delete;
~PostPageFlipTask() 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 {
if (task_)
std::move(task_).Run();
}
void TransitionStarted() override {}
void TransitionChanged() override {}
void TransitionEnded() override {}
PaginationModel* model_;
base::OnceClosure task_;
};
class TestSuggestedSearchResult : public TestSearchResult {
public:
TestSuggestedSearchResult() {
set_display_type(SearchResultDisplayType::kChip);
set_is_recommendation(true);
}
TestSuggestedSearchResult(const TestSuggestedSearchResult&) = delete;
TestSuggestedSearchResult& operator=(const TestSuggestedSearchResult&) =
delete;
~TestSuggestedSearchResult() override = default;
};
// Counts when the observed view's bounds change.
class BoundsChangeCounter : public views::ViewObserver {
public:
explicit BoundsChangeCounter(views::View* observed_view)
: observed_view_(observed_view) {
observed_view->AddObserver(this);
}
BoundsChangeCounter(const BoundsChangeCounter&) = delete;
BoundsChangeCounter& operator=(const BoundsChangeCounter&) = delete;
~BoundsChangeCounter() override { observed_view_->RemoveObserver(this); }
// views::ViewObserver:
void OnViewBoundsChanged(views::View* observed_view) override {
++bounds_change_count_;
}
int bounds_change_count() const { return bounds_change_count_; }
private:
views::View* const observed_view_;
int bounds_change_count_ = 0;
};
} // namespace
// Subclasses should set `is_rtl_`, `create_as_tablet_mode_`, etc. in their
// constructors to indicate which mode to test.
class AppsGridViewTest : public AshTestBase {
public:
AppsGridViewTest() = default;
AppsGridViewTest(const AppsGridViewTest&) = delete;
AppsGridViewTest& operator=(const AppsGridViewTest&) = delete;
~AppsGridViewTest() override = default;
// testing::Test overrides:
void SetUp() override {
if (is_rtl_)
base::i18n::SetICUDefaultLocale("he");
std::vector<base::Feature> enabled_features;
std::vector<base::Feature> disabled_features;
if (is_productivity_launcher_enabled_) {
enabled_features.push_back(features::kProductivityLauncher);
} else {
disabled_features.push_back(features::kProductivityLauncher);
}
if (is_app_sort_enabled_) {
enabled_features.push_back(features::kLauncherAppSort);
} else {
disabled_features.push_back(features::kLauncherAppSort);
}
feature_list_.InitWithFeatures(enabled_features, disabled_features);
AshTestBase::SetUp();
// Make the display big enough to hold the app list.
UpdateDisplay("1024x768");
// Populate some suggested apps.
search_model_ = std::make_unique<SearchModel>();
for (size_t i = 0; i < kNumOfSuggestedApps; ++i) {
search_model_->results()->Add(
std::make_unique<TestSuggestedSearchResult>());
}
// Replace the model before the app list views are created, because some
// views cache pointers to the model.
model_ = std::make_unique<test::AppListTestModel>();
Shell::Get()->app_list_controller()->SetActiveModel(
/*profile_id=*/1, model_.get(), search_model_.get());
// Show the app list.
auto* helper = GetAppListTestHelper();
if (create_as_tablet_mode_) {
// The app list will be shown automatically when tablet mode is enabled.
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
} else if (features::IsProductivityLauncherEnabled()) {
helper->ShowAppList();
} else {
// Show fullscreen so folders are available.
helper->Show(GetPrimaryDisplay().id());
helper->GetAppListView()->SetState(AppListViewState::kFullscreenAllApps);
}
// Wait for any show animations to complete.
base::RunLoop().RunUntilIdle();
// Cache view pointers to make tests more concise.
if (!create_as_tablet_mode_ && features::IsProductivityLauncherEnabled()) {
// AppsGridView is scrollable in clamshell mode with AppListBubble.
apps_grid_view_ = helper->GetScrollableAppsGridView();
app_list_folder_view_ = helper->GetBubbleFolderView();
search_box_view_ = helper->GetBubbleSearchBoxView();
} else {
app_list_view_ = helper->GetAppListView();
app_list_folder_view_ = helper->GetFullscreenFolderView();
auto* contents_view =
app_list_view_->app_list_main_view()->contents_view();
search_box_view_ = contents_view->GetSearchBoxView();
// AppsGridView is paged in tablet mode and without AppListBubble.
paged_apps_grid_view_ =
contents_view->apps_container_view()->apps_grid_view();
apps_grid_view_ = paged_apps_grid_view_;
suggestions_container_ = contents_view->apps_container_view()
->suggestion_chip_container_view_for_test();
expand_arrow_view_ = contents_view->expand_arrow_view();
// In production, page flip duration > page transition > overscroll.
SetPageFlipDurationForTest(paged_apps_grid_view_);
page_flip_waiter_ =
std::make_unique<PageFlipWaiter>(GetPaginationModel());
}
test_api_ = std::make_unique<AppsGridViewTestApi>(apps_grid_view_);
PresentationTimeRecorder::SetReportPresentationTimeImmediatelyForTest(true);
}
void TearDown() override {
page_flip_waiter_.reset();
PresentationTimeRecorder::SetReportPresentationTimeImmediatelyForTest(
false);
AshTestBase::TearDown();
}
protected:
void AnimateFolderViewPageFlip(int target_page) {
// Folders are only paged without productivity launcher enabled.
DCHECK(!features::IsProductivityLauncherEnabled());
PagedAppsGridView* paged_folder_apps_grid_view =
static_cast<PagedAppsGridView*>(folder_apps_grid_view());
DCHECK(paged_folder_apps_grid_view->pagination_model()->total_pages() >
target_page);
AppsGridViewTestApi folder_grid_test_api(paged_folder_apps_grid_view);
SetPageFlipDurationForTest(paged_folder_apps_grid_view);
PageFlipWaiter page_flip_waiter(
paged_folder_apps_grid_view->pagination_model());
paged_folder_apps_grid_view->pagination_model()->SelectPage(
target_page, true /*animate*/);
while (HasPendingPageFlip(paged_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::Milliseconds(3));
apps_grid_view->pagination_model()->SetTransitionDurations(
base::Milliseconds(2), base::Milliseconds(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 apps_grid_view_->app_list_config();
}
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 {
AppsGridViewTestApi temp_test_api(grid_view);
const int selected_page = GetSelectedPage(grid_view);
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();
views::View::ConvertPointToTarget(view->parent(), grid_view,
&view_origin);
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(int page) const { return test_api_->TilesPerPage(page); }
PaginationModel* GetPaginationModel() const {
DCHECK(paged_apps_grid_view_) << "Only available in tablet mode or when "
"ProductivityLauncher is disabled.";
return paged_apps_grid_view_->pagination_model();
}
int GetSelectedPage(AppsGridView* grid_view) const {
return grid_view->GetSelectedPage();
}
int GetTotalPages(AppsGridView* grid_view) const {
return grid_view->GetTotalPages();
}
AppListFolderView* app_list_folder_view() const {
return app_list_folder_view_;
}
AppsGridView* 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(location.x(), location.y(), 0,
base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_TAP));
apps_grid_view_->OnGestureEvent(&gesture_event);
return gesture_event;
}
// Simulates a tap on the point `location` if the test is in tablet mode.
// Simulates a left click on the point otherwise.
void SimulateLeftClickOrTapAt(const gfx::Point& location) {
auto* event_generator = GetEventGenerator();
if (create_as_tablet_mode_) {
event_generator->GestureTapAt(location);
return;
}
event_generator->MoveMouseTo(location);
event_generator->ClickLeftButton();
}
// Simulates a long press on the point `location` if the test is in tablet
// mode. Simulates a right click on the point otherwise. This function can be
// used to open the context menu.
void SimulateRightClickOrLongPressAt(const gfx::Point& location) {
auto* event_generator = GetEventGenerator();
if (create_as_tablet_mode_) {
ui::GestureEvent gesture_event(
location.x(), location.y(), 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
event_generator->Dispatch(&gesture_event);
return;
}
event_generator->MoveMouseTo(location);
event_generator->ClickRightButton();
}
// 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->item(), target);
}
// Updates the layout of the container for the apps grid. Useful when the
// test has added apps to the data model and is about to do an operation that
// depends on item positions.
void UpdateLayout() {
if (!create_as_tablet_mode_ && features::IsProductivityLauncherEnabled())
GetAppListTestHelper()->GetBubbleView()->Layout();
else
app_list_view_->Layout();
}
AppListItemView* InitiateDragForItemAtCurrentPageAt(
AppsGridView::Pointer pointer,
int row,
int column,
AppsGridView* apps_grid_view) {
AppsGridViewTestApi test_api(apps_grid_view);
const int selected_page = GetSelectedPage(apps_grid_view);
GridIndex index(selected_page, row * apps_grid_view->cols() + column);
AppListItemView* view = test_api.GetViewAtIndex(index);
DCHECK(view);
gfx::Point from = view->GetLocalBounds().CenterPoint();
gfx::Point root_from = from;
gfx::NativeWindow window = apps_grid_view->GetWidget()->GetNativeWindow();
views::View::ConvertPointToWidget(view, &root_from);
aura::Window::ConvertPointToTarget(window, window->GetRootWindow(),
&root_from);
view->InitiateDrag(from, root_from);
current_drag_location_ = root_from;
// Call UpdateDrag to trigger |apps_grid_view| change to cardified_state -
// the cardified state starts only once the drag distance exceeds a drag
// threshold, so the pointer has to sufficiently move from the original
// position.
gfx::Point from_in_grid = from;
views::View::ConvertPointToTarget(view, apps_grid_view, &from_in_grid);
UpdateDrag(pointer, from_in_grid + gfx::Vector2d(10, 10), 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);
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;
}
// 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) {
ASSERT_TRUE(paged_apps_grid_view_)
<< "Only available in tablet mode or when ProductivityLauncher is "
"disabled.";
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 = paged_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(paged_apps_grid_view_, &root_to);
gfx::NativeWindow window = app_list_view_->GetWidget()->GetNativeWindow();
aura::Window::ConvertPointToTarget(window, window->GetRootWindow(),
&root_to);
// Update dragging and relayout apps grid view after drag ends.
PostPageFlipTask task(
GetPaginationModel(), base::BindLambdaForTesting([&]() {
ui::MouseEvent drag_event(ui::ET_MOUSE_DRAGGED, to, root_to,
ui::EventTimeForNow(), 0, 0);
paged_apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE,
drag_event);
}));
page_flip_waiter_->Reset();
UpdateDrag(AppsGridView::MOUSE, point_in_page_flip_buffer,
paged_apps_grid_view_,
/*steps=*/10);
while (HasPendingPageFlip(paged_apps_grid_view_)) {
page_flip_waiter_->Wait();
}
EndDrag(paged_apps_grid_view_, false /*cancel*/);
test_api_->LayoutToIdealBounds();
}
gfx::Point GetDragIconCenter() {
return test_api_->GetDragIconBoundsInAppsGridView().CenterPoint();
}
std::string GetItemMoveTypeHistogramName() {
return paged_apps_grid_view_ ? "Apps.AppListAppMovingType"
: "Apps.AppListBubbleAppMovingType";
}
// May be a PagedAppsGridView or a ScrollableAppsGridView depending on the
// ProductivityLauncher flag and tablet mode.
AppsGridView* apps_grid_view_ = nullptr;
// May be owned by different parent views depending on the
// ProductivityLauncher flag and tablet mode.
AppListFolderView* app_list_folder_view_ = nullptr;
SearchBoxView* search_box_view_ = nullptr;
// These views exist in tablet mode and when ProductivityLauncher is disabled.
PagedAppsGridView* paged_apps_grid_view_ = nullptr;
AppListView* app_list_view_ = nullptr; // Owned by native widget.
SearchResultContainerView* suggestions_container_ =
nullptr; // Owned by |apps_grid_view_|.
ExpandArrowView* expand_arrow_view_ = nullptr; // Owned by |apps_grid_view_|.
std::unique_ptr<AppListTestModel> model_;
std::unique_ptr<SearchModel> search_model_;
std::unique_ptr<AppsGridViewTestApi> test_api_;
// True if the test screen is configured to work with RTL locale.
bool is_rtl_ = false;
// True if feature ProductivityLauncher should be enabled.
bool is_productivity_launcher_enabled_ = false;
// True if feature LauncherAppSort should be enabled.
bool is_app_sort_enabled_ = false;
// 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_;
base::test::ScopedFeatureList feature_list_;
absl::optional<gfx::Point> current_drag_location_;
};
// Tests that only run with ProductivityLauncher disabled, which disables the
// bubble launcher. These can be deleted when ProductivityLauncher is the
// default.
class AppsGridViewNonBubbleTest : public AppsGridViewTest {
public:
AppsGridViewNonBubbleTest() { is_productivity_launcher_enabled_ = false; }
};
// Test suite for clamshell mode, parameterized by feature ProductivityLauncher.
class AppsGridViewClamshellTest : public AppsGridViewTest,
public testing::WithParamInterface<bool> {
public:
AppsGridViewClamshellTest() {
is_productivity_launcher_enabled_ = GetParam();
}
};
INSTANTIATE_TEST_SUITE_P(All, AppsGridViewClamshellTest, testing::Bool());
// Tests suite to test both tablet and clamshell mode behavior, additionally
// parameterized by feature ProductivityLauncher.
class AppsGridViewClamshellAndTabletTest
: public AppsGridViewTest,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
AppsGridViewClamshellAndTabletTest() {
create_as_tablet_mode_ = std::get<0>(GetParam());
is_productivity_launcher_enabled_ = std::get<1>(GetParam());
}
};
INSTANTIATE_TEST_SUITE_P(All,
AppsGridViewClamshellAndTabletTest,
testing::Combine(testing::Bool(), testing::Bool()));
// Tests suite parameterized by RTL locale.
class AppsGridViewRTLTest : public AppsGridViewTest,
public testing::WithParamInterface<bool> {
public:
AppsGridViewRTLTest() { is_rtl_ = GetParam(); }
};
INSTANTIATE_TEST_SUITE_P(All, AppsGridViewRTLTest, testing::Bool());
// Tests suite for app list items drag and drop tests. These tests are
// parameterized by RTL locale and feature ProductivityLauncher.
class AppsGridViewDragTest
: public AppsGridViewTest,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
AppsGridViewDragTest() {
is_rtl_ = std::get<0>(GetParam());
is_productivity_launcher_enabled_ = std::get<1>(GetParam());
}
// AppsGridViewTest:
void SetUp() override {
AppsGridViewTest::SetUp();
ShelfModel::Get()->SetShelfItemFactory(&shelf_item_factory_);
}
void TearDown() override {
ShelfModel::Get()->SetShelfItemFactory(nullptr);
AppsGridViewTest::TearDown();
}
private:
// Shelf item factory required for test that drag from apps grid to shelf.
ShelfItemFactoryFake shelf_item_factory_;
};
INSTANTIATE_TEST_SUITE_P(All,
AppsGridViewDragTest,
testing::Combine(testing::Bool(), testing::Bool()));
// Drag and drop tests that are not expected to work with AppListBubble.
// Parameterized by RTL locale.
class AppsGridViewDragNonBubbleTest : public AppsGridViewTest,
public testing::WithParamInterface<bool> {
public:
AppsGridViewDragNonBubbleTest() {
is_rtl_ = GetParam();
is_productivity_launcher_enabled_ = false;
}
};
INSTANTIATE_TEST_SUITE_P(All, AppsGridViewDragNonBubbleTest, testing::Bool());
// Tests suite to verify behaviour exclusively to cardified state, parameterized
// by RTL locale and AppListBubble.
class AppsGridViewCardifiedStateTest
: public AppsGridViewTest,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
AppsGridViewCardifiedStateTest() {
is_rtl_ = std::get<0>(GetParam());
is_productivity_launcher_enabled_ = std::get<1>(GetParam());
// The productivity launcher in clamshell mode does not use pages / cards,
// so use tablet mode instead.
create_as_tablet_mode_ = is_productivity_launcher_enabled_;
}
};
INSTANTIATE_TEST_SUITE_P(All,
AppsGridViewCardifiedStateTest,
testing::Combine(testing::Bool(), testing::Bool()));
// Test suite for verifying tablet mode apps grid behaviour, parameterized by
// RTL locale and ProductivityLauncher feature.
class AppsGridViewTabletTest
: public AppsGridViewTest,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
AppsGridViewTabletTest() {
is_rtl_ = std::get<0>(GetParam());
is_productivity_launcher_enabled_ = std::get<1>(GetParam());
create_as_tablet_mode_ = true;
}
};
INSTANTIATE_TEST_SUITE_P(All,
AppsGridViewTabletTest,
testing::Combine(testing::Bool(), testing::Bool()));
// Test suite that tests apps sort works on all apps grid, parameterized by
// RTL locale and clamshell/tablet mode.
class AppsGridViewAppSortTest
: public AppsGridViewTest,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
AppsGridViewAppSortTest() {
is_rtl_ = std::get<0>(GetParam());
is_productivity_launcher_enabled_ = true;
is_app_sort_enabled_ = true;
create_as_tablet_mode_ = std::get<1>(GetParam());
}
};
INSTANTIATE_TEST_SUITE_P(All,
AppsGridViewAppSortTest,
testing::Combine(testing::Bool(), testing::Bool()));
// This does not test the font name or weight because ash_unittests returns
// different font lists than chrome (e.g. "DejaVu Sans" instead of "Roboto").
TEST_P(AppsGridViewClamshellTest, AppListItemViewFont) {
model_->PopulateApps(1);
AppListItemView* item_view = GetItemViewInTopLevelGrid(0);
EXPECT_EQ(12, item_view->title()->font_list().GetFontSize());
}
// This does not test the font name or weight because ash_unittests returns
// different font lists than chrome (e.g. "DejaVu Sans" instead of "Roboto").
TEST_P(AppsGridViewTabletTest, AppListItemViewFont) {
model_->PopulateApps(1);
AppListItemView* item_view = GetItemViewInTopLevelGrid(0);
if (is_productivity_launcher_enabled_)
EXPECT_EQ(13, item_view->title()->font_list().GetFontSize());
else
EXPECT_EQ(12, item_view->title()->font_list().GetFontSize());
}
TEST_P(AppsGridViewClamshellTest, 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. Bubble launcher uses different metric names and does not use
// suggestion chips.
TEST_F(AppsGridViewNonBubbleTest, UMATestForLaunchingApps) {
base::HistogramTester histogram_tester;
model_->PopulateApps(5);
UpdateLayout();
// Select the first app in grid and launch it.
LeftClickOn(GetItemViewInTopLevelGrid(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.
// TODO(jamescook): Investigate why this fails for bubble launcher.
TEST_F(AppsGridViewTest, MoveItemAcrossRowDoesNotCauseCrash) {
const int cols = apps_grid_view_->cols();
ASSERT_LE(0, cols);
model_->PopulateApps(cols * 2);
UpdateLayout();
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());
EXPECT_TRUE(apps_grid_view_->IsAnimatingView(view0));
test_api_->WaitForItemMoveAnimationDone();
// |view0| layer should be cleared after the animation.
EXPECT_FALSE(view0->layer());
EXPECT_EQ(view0->bounds(), GetItemRectOnCurrentPageAt(1, 2));
}
// TODO(jamescook): Investigate why this fails for bubble launcher.
TEST_F(AppsGridViewTest, MoveItemAcrossRowDoesNotCauseAnimation) {
const int cols = apps_grid_view_->cols();
ASSERT_LE(0, cols);
model_->PopulateApps(cols * 2);
UpdateLayout();
// NOTE: Dismissing the app list creates layers for item views as part of the
// opacity animations, even in tests. https://crbug.com/1246567
GetAppListTestHelper()->DismissAndRunLoop();
ASSERT_FALSE(apps_grid_view_->GetWidget()->IsVisible());
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(apps_grid_view_->IsAnimatingView(view0));
EXPECT_EQ(view0->bounds(), GetItemRectOnCurrentPageAt(1, 2));
}
// Tests that control + arrow while a suggested chip is focused does not crash.
// Productivity launcher does not use suggestion chips.
TEST_F(AppsGridViewNonBubbleTest, ControlArrowOnSuggestedChip) {
model_->PopulateApps(5);
UpdateLayout();
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_P(AppsGridViewClamshellTest, ItemTooltip) {
std::string title("a");
AppListItem* item = model_->CreateAndAddItem(title);
model_->SetItemName(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(0) + 1);
UpdateLayout();
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(0) + 1);
UpdateLayout();
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(0) + 1);
UpdateLayout();
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);
}
// Tests that taps between apps within the AppsGridView does not result in the
// AppList closing.
TEST_F(AppsGridViewTest, TapsBetweenAppsWontCloseAppList) {
model_->PopulateApps(2);
UpdateLayout();
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());
}
// The bubble launcher uses scrollable folders, so this test disables
// ProductivityLauncher.
TEST_F(AppsGridViewNonBubbleTest, PageResetAfterOpenFolder) {
model_->CreateAndPopulateFolderWithApps(kMaxItemsInFolder);
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);
ASSERT_FALSE(features::IsProductivityLauncherEnabled());
PaginationModel* pagination_model =
static_cast<PagedAppsGridView*>(folder_apps_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_P(AppsGridViewClamshellTest, 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();
AppsGridViewTestApi folder_grid_test_api(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(2, folder_grid_test_api.TilesPerPage(0));
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(6, folder_grid_test_api.TilesPerPage(0));
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(9, folder_grid_test_api.TilesPerPage(0));
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(16, folder_grid_test_api.TilesPerPage(0));
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(features::IsProductivityLauncherEnabled() ? 20 : 16,
folder_grid_test_api.TilesPerPage(0));
app_list_folder_view()->CloseFolderPage();
}
TEST_P(AppsGridViewClamshellTest, 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(GetAppListTestHelper()->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(GetAppListTestHelper()->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(GetAppListTestHelper()->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(GetAppListTestHelper()->IsInFolderView());
items_grid_view->GetWidget()->LayoutRootViewIfNecessary();
EXPECT_EQ(items_grid_view->GetBoundsInScreen().size(),
one_row_folder_view.size());
}
TEST_P(AppsGridViewClamshellTest, 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(GetAppListTestHelper()->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(GetAppListTestHelper()->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(GetAppListTestHelper()->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(kMaxItemsPerFolderPage);
test_api_->PressItemAt(2);
EXPECT_TRUE(GetAppListTestHelper()->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 = kMaxItemsPerFolderPage;
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(GetAppListTestHelper()->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(GetAppListTestHelper()->IsInFolderView());
}
// Tests that an app icon is selected when a menu is shown by click.
// TODO(jamescook): Investigate why this is broken with AppListBubble.
TEST_F(AppsGridViewTest, AppIconSelectedWhenMenuIsShown) {
model_->PopulateApps(1);
UpdateLayout();
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.
auto* generator = GetEventGenerator();
generator->MoveMouseTo(app->GetBoundsInScreen().CenterPoint());
generator->PressRightButton();
EXPECT_TRUE(apps_grid_view_->IsSelectedView(app));
generator->ReleaseRightButton();
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 = GetTilesPerPage(0);
const size_t kPages = 2;
model_->PopulateApps(kItemsInPage * kPages);
UpdateLayout();
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_P(AppsGridViewClamshellTest, ItemViewsDontHaveLayer) {
size_t kTotalItems = 3;
model_->PopulateApps(kTotalItems);
UpdateLayout();
// 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(AppsGridViewDragTest, DismissWhileDraggingDoesNotCrash) {
model_->PopulateApps(2);
UpdateLayout();
AppListItemView* const item_view = GetItemViewInTopLevelGrid(1);
// Non-zero animation durations are necessary to make sure we don't miss
// crashes involving animation delegates. Specifically, `bounds_animator_` had
// a use after free problem in the past.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
GetEventGenerator()->MoveMouseTo(
item_view->GetBoundsInScreen().CenterPoint());
GetEventGenerator()->PressLeftButton();
item_view->FireMouseDragTimerForTest();
GetEventGenerator()->MoveMouseBy(20, 20);
ASSERT_TRUE(apps_grid_view_->drag_item());
ASSERT_TRUE(apps_grid_view_->IsDragging());
ASSERT_EQ(item_view->item(), apps_grid_view_->drag_item());
GetAppListTestHelper()->Dismiss();
// No crash
}
TEST_P(AppsGridViewDragTest, DismissWhileDraggingInFolderDoesNotCrash) {
model_->CreateAndPopulateFolderWithApps(2);
test_api_->Update();
test_api_->PressItemAt(0);
AppListItemView* const item_view =
GetItemViewInAppsGridAt(1, folder_apps_grid_view());
// Non-zero animation durations are necessary to make sure we don't miss
// crashes involving animation delegates. Specifically, `bounds_animator_` had
// a use after free problem in the past.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
GetEventGenerator()->MoveMouseTo(
item_view->GetBoundsInScreen().CenterPoint());
GetEventGenerator()->PressLeftButton();
item_view->FireMouseDragTimerForTest();
GetEventGenerator()->MoveMouseBy(20, 20);
ASSERT_TRUE(folder_apps_grid_view()->drag_item());
ASSERT_TRUE(folder_apps_grid_view()->IsDragging());
ASSERT_EQ(item_view->item(), folder_apps_grid_view()->drag_item());
GetAppListTestHelper()->Dismiss();
// No crash
}
TEST_P(AppsGridViewDragTest, ItemViewsHaveLayerDuringDrag) {
size_t kTotalItems = 3;
model_->PopulateApps(kTotalItems);
UpdateLayout();
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
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(AppsGridViewDragTest, ItemViewsDontHaveLayerAfterDrag) {
size_t kTotalItems = 3;
model_->PopulateApps(kTotalItems);
UpdateLayout();
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
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(AppsGridViewDragTest, MouseDragItemIntoFolder) {
size_t kTotalItems = 3;
model_->PopulateApps(kTotalItems);
UpdateLayout();
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
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());
EXPECT_EQ(is_productivity_launcher_enabled_,
GetAppListTestHelper()->IsInFolderView());
if (is_productivity_launcher_enabled_) {
EXPECT_EQ(folder_item, app_list_folder_view_->folder_item());
EXPECT_TRUE(app_list_folder_view_->folder_header_view()
->GetFolderNameViewForTest()
->HasFocus());
}
}
TEST_P(AppsGridViewDragTest, MouseDragSecondItemIntoFolder) {
AppListFolderItem* folder_item = model_->CreateAndPopulateFolderWithApps(2);
model_->PopulateApps(1);
UpdateLayout();
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
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());
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
}
TEST_P(AppsGridViewDragTest, DragIconAnimatesAfterDragToFolder) {
model_->CreateAndPopulateFolderWithApps(2);
model_->PopulateApps(1);
UpdateLayout();
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
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*/);
ui::Layer* drag_icon_layer = test_api_->GetDragIconLayer();
ASSERT_TRUE(drag_icon_layer);
ASSERT_TRUE(drag_icon_layer->GetAnimator()->is_animating());
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
LayerAnimationStoppedWaiter animation_waiter;
animation_waiter.Wait(drag_icon_layer);
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
}
TEST_P(AppsGridViewDragTest, DragIconHiddenImmediatelyWhenGridHides) {
model_->CreateAndPopulateFolderWithApps(2);
model_->PopulateApps(1);
UpdateLayout();
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
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*/);
// Start typing to close the apps page, and open search results.
GetEventGenerator()->GestureTapAt(
search_box_view_->GetBoundsInScreen().CenterPoint());
GetEventGenerator()->PressAndReleaseKey(ui::VKEY_A);
if (is_productivity_launcher_enabled_) {
ASSERT_TRUE(GetAppListTestHelper()->GetBubbleSearchPage()->GetVisible());
} else {
ASSERT_TRUE(GetAppListTestHelper()
->GetFullscreenSearchResultPageView()
->GetVisible());
}
// Verify the drag icon is hidden immediately.
EXPECT_FALSE(test_api_->GetDragIconLayer());
EXPECT_FALSE(apps_grid_view_->drag_item());
EXPECT_FALSE(apps_grid_view_->IsDragging());
}
TEST_P(AppsGridViewDragTest, DragIconAnimatesAfterDragToCreateFolder) {
model_->PopulateApps(3);
UpdateLayout();
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
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*/);
ui::Layer* drag_icon_layer = test_api_->GetDragIconLayer();
ASSERT_TRUE(drag_icon_layer);
ASSERT_TRUE(drag_icon_layer->GetAnimator()->is_animating());
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
LayerAnimationStoppedWaiter animation_waiter;
animation_waiter.Wait(drag_icon_layer);
EXPECT_EQ(is_productivity_launcher_enabled_,
GetAppListTestHelper()->IsInFolderView());
}
TEST_P(AppsGridViewDragTest, FolderNotOpenedIfGridHidesDuringIconDrop) {
model_->PopulateApps(3);
UpdateLayout();
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
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*/);
ui::Layer* drag_icon_layer = test_api_->GetDragIconLayer();
ASSERT_TRUE(drag_icon_layer);
ASSERT_TRUE(drag_icon_layer->GetAnimator()->is_animating());
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
// Start typing to close the apps page, and open search results - verify the
// folder does not get opened, and that the icon drop animation gets canceled.
GetEventGenerator()->GestureTapAt(
search_box_view_->GetBoundsInScreen().CenterPoint());
GetEventGenerator()->PressAndReleaseKey(ui::VKEY_A);
if (is_productivity_launcher_enabled_) {
ASSERT_TRUE(GetAppListTestHelper()->GetBubbleSearchPage()->GetVisible());
} else {
ASSERT_TRUE(GetAppListTestHelper()
->GetFullscreenSearchResultPageView()
->GetVisible());
}
EXPECT_FALSE(test_api_->GetDragIconLayer());
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
}
TEST_P(AppsGridViewClamshellTest, CheckFolderWithMultiplePagesContents) {
// Creates a folder item.
const size_t kTotalItems = kMaxItemsPerFolderPage;
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());
// Productivity launcher uses scrollable folders, not paged.
if (!features::IsProductivityLauncherEnabled()) {
EXPECT_EQ(16, AppsGridViewTestApi(folder_apps_grid_view()).TilesPerPage(0));
EXPECT_EQ(1, GetTotalPages(folder_apps_grid_view()));
EXPECT_EQ(0, GetSelectedPage(folder_apps_grid_view()));
}
EXPECT_TRUE(folder_apps_grid_view()->IsInFolder());
}
TEST_P(AppsGridViewDragTest, MouseDragItemOutOfFolder) {
// Creates a folder item.
const size_t kTotalItems = kMaxItemsPerFolderPage;
AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(kTotalItems);
test_api_->Update();
test_api_->PressItemAt(0);
AppsGridViewTestApi folder_grid_test_api(folder_apps_grid_view());
// Drag the first folder child out of the folder.
AppListItemView* drag_view = InitiateDragForItemAtCurrentPageAt(
AppsGridView::MOUSE, 0, 0, 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());
// 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();
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());
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
}
TEST_P(AppsGridViewDragTest, DragIconAnimatesAfterDragOutOfFolder) {
model_->CreateAndPopulateFolderWithApps(5);
test_api_->Update();
test_api_->PressItemAt(0);
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
// Drag the first folder child out of the folder.
AppListItemView* drag_view = InitiateDragForItemAtCurrentPageAt(
AppsGridView::MOUSE, 0, 0, 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());
// 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();
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*/);
ui::Layer* drag_icon_layer = test_api_->GetDragIconLayer();
ASSERT_TRUE(drag_icon_layer);
EXPECT_TRUE(drag_icon_layer->GetAnimator()->is_animating());
}
TEST_P(AppsGridViewDragTest, DragIconAnimatesAfterDragToAnotherFolder) {
model_->CreateAndPopulateFolderWithApps(5);
model_->CreateAndPopulateFolderWithApps(5);
test_api_->Update();
test_api_->PressItemAt(0);
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
// Drag the first folder child out of the folder.
AppListItemView* drag_view = InitiateDragForItemAtCurrentPageAt(
AppsGridView::MOUSE, 0, 0, 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());
// Calculate the coordinates for the drop point.
gfx::Point drop_point = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
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*/);
ui::Layer* drag_icon_layer = test_api_->GetDragIconLayer();
ASSERT_TRUE(drag_icon_layer);
ASSERT_TRUE(drag_icon_layer->GetAnimator()->is_animating());
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
LayerAnimationStoppedWaiter animation_waiter;
animation_waiter.Wait(drag_icon_layer);
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
}
TEST_P(AppsGridViewDragTest,
DragIconAnimatesAfterDragThatDeletesOriginalFolder) {
model_->PopulateApps(2);
model_->CreateSingleItemFolder("folder_id", "item_id");
test_api_->Update();
test_api_->PressItemAt(2);
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
// Drag the only folder child out of the folder.
AppListItemView* drag_view = InitiateDragForItemAtCurrentPageAt(
AppsGridView::MOUSE, 0, 0, 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());
// Calculate the coordinates for the drop point. Note that we we are dropping
// into the app list view not the folder view. The (0,3) spot is empty.
gfx::Point drop_point = GetItemRectOnCurrentPageAt(0, 3).CenterPoint();
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*/);
ui::Layer* drag_icon_layer = test_api_->GetDragIconLayer();
ASSERT_TRUE(drag_icon_layer);
EXPECT_TRUE(drag_icon_layer->GetAnimator()->is_animating());
}
TEST_P(AppsGridViewDragTest, DragIconAnimatesAfterReorderDrag) {
model_->PopulateApps(3);
test_api_->Update();
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
// Drag the first item to an empty slot in the grid.
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
apps_grid_view_);
gfx::Point drop_point = GetItemRectOnCurrentPageAt(0, 3).CenterPoint();
UpdateDrag(AppsGridView::MOUSE, drop_point, apps_grid_view_, 5 /*steps*/);
EndDrag(apps_grid_view_, false /*cancel*/);
ui::Layer* drag_icon_layer = test_api_->GetDragIconLayer();
ASSERT_TRUE(drag_icon_layer);
EXPECT_TRUE(drag_icon_layer->GetAnimator()->is_animating());
}
TEST_F(AppsGridViewNonBubbleTest, SwitchPageFolderItem) {
// ProductivityLauncher does not use paged folders.
ASSERT_FALSE(features::IsProductivityLauncherEnabled());
// Creates a folder item with enough views to have a second page.
const size_t kTotalItems = kMaxItemsPerFolderPage + 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, GetSelectedPage(folder_apps_grid_view()));
EXPECT_EQ(4, folder_apps_grid_view()->cols());
EXPECT_EQ(16, AppsGridViewTestApi(folder_apps_grid_view()).TilesPerPage(0));
EXPECT_TRUE(folder_apps_grid_view()->IsInFolder());
}
TEST_P(AppsGridViewDragNonBubbleTest, MouseDragItemOutOfFolderSecondPage) {
// ProductivityLauncher does not use paged folders.
ASSERT_FALSE(features::IsProductivityLauncherEnabled());
// Creates a folder item with enough views to have a second page.
const size_t kTotalItems = kMaxItemsPerFolderPage + 1;
AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(kTotalItems);
test_api_->Update();
test_api_->PressItemAt(0);
// Switch to second page.
AnimateFolderViewPageFlip(1);
// Drag the first folder child on the second page out of the folder.
AppListItemView* drag_view = InitiateDragForItemAtCurrentPageAt(
AppsGridView::MOUSE, 0, 0, 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. The (0,1) spot is empty.
gfx::Point drop_point = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
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());
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
}
TEST_P(AppsGridViewDragNonBubbleTest, MouseDropItemFromFolderSecondPage) {
// ProductivityLauncher does not use paged folders.
ASSERT_FALSE(features::IsProductivityLauncherEnabled());
// Creates a folder item with enough views to have a second page.
const size_t kTotalItems = kMaxItemsPerFolderPage + 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(0) - 2);
AppsGridViewTestApi folder_grid_test_api(folder_apps_grid_view());
// Drag the first folder child on the second page out of the folder.
AppListItemView* drag_view = InitiateDragForItemAtCurrentPageAt(
AppsGridView::MOUSE, 0, 0, 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::Rect drop_tile_bounds = GetItemRectOnCurrentPageAt(0, 0);
gfx::Point drop_point = is_rtl_ ? drop_tile_bounds.left_center()
: drop_tile_bounds.right_center();
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());
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
}
TEST_P(AppsGridViewDragTest, MouseDragMaxItemsInFolder) {
// Create and add an almost full folder.
const size_t kTotalItems = kMaxItemsInFolder - 1;
AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(kTotalItems);
ASSERT_FALSE(folder_item->IsFolderFull());
// Create and add another item.
model_->PopulateAppWithId(kTotalItems);
UpdateLayout();
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
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(kTotalItems + 1, folder_item->ChildItemCount());
EXPECT_TRUE(folder_item->IsFolderFull());
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
}
TEST_P(AppsGridViewDragTest, MouseDragExceedMaxItemsInFolder) {
// Create and add a full folder.
AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(kMaxItemsInFolder);
EXPECT_TRUE(folder_item->IsFolderFull());
// Create and add another 2 item.
model_->PopulateAppWithId(kMaxItemsInFolder + 1);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
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(kMaxItemsInFolder, folder_item->ChildItemCount());
EXPECT_TRUE(folder_item->IsFolderFull());
}
TEST_P(AppsGridViewDragTest, MouseDragMovement) {
// Create and add a full folder.
model_->CreateAndPopulateFolderWithApps(kMaxItemsInFolder);
// Create and add another item.
model_->PopulateAppWithId(kMaxItemsInFolder);
UpdateLayout();
AppListItemView* folder_view =
GetItemViewForPoint(GetItemRectOnCurrentPageAt(0, 0).CenterPoint());
// Drag the new item to the left so that the grid reorders.
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
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(AppsGridViewDragTest, MouseDragMaxItemsInFolderWithMovement) {
// Create and add a full folder.
model_->CreateAndPopulateFolderWithApps(kMaxItemsInFolder);
AppListFolderItem* folder_item = static_cast<AppListFolderItem*>(
model_->top_level_item_list()->item_at(0));
// Create and add another item.
model_->PopulateAppWithId(kMaxItemsInFolder);
// Drag the new item to the left so that the grid reorders.
AppListItemView* dragged_view = InitiateDragForItemAtCurrentPageAt(
AppsGridView::MOUSE, 0, 1, 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(kMaxItemsInFolder, 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(AppsGridViewDragTest, MouseDragItemReorderBeforeFolderDropPoint) {
model_->PopulateApps(2);
UpdateLayout();
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
apps_grid_view_);
gfx::Point to = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
int half_tile_width = std::abs(GetItemRectOnCurrentPageAt(0, 1).x() -
GetItemRectOnCurrentPageAt(0, 0).x()) /
2;
gfx::Vector2d drag_vector(-half_tile_width - 4, 0);
// Flip drag vector in rtl.
if (is_rtl_)
drag_vector.set_x(-drag_vector.x());
// Drag left but stop before the folder dropping circle.
UpdateDrag(AppsGridView::MOUSE, to + 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(AppsGridViewDragTest, MouseDragItemReorderAfterFolderDropPoint) {
model_->PopulateApps(2);
UpdateLayout();
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
apps_grid_view_);
gfx::Point to = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
int half_tile_width = std::abs(GetItemRectOnCurrentPageAt(0, 1).x() -
GetItemRectOnCurrentPageAt(0, 0).x()) /
2;
gfx::Vector2d drag_vector(
-2 * half_tile_width -
GetAppListConfig()->folder_dropping_circle_radius() - 4,
0);
// Flip drag vector in rtl.
if (is_rtl_)
drag_vector.set_x(-drag_vector.x());
// Drag left, past the folder dropping circle.
UpdateDrag(AppsGridView::MOUSE, to + 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(AppsGridViewDragTest, 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);
UpdateLayout();
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
apps_grid_view_);
gfx::Point to = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
int half_tile_width = std::abs(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);
// Flip drag vector in rtl.
if (is_rtl_)
drag_vector.set_x(-drag_vector.x());
// Drag down, between apps 5 and 6. The gap should open up, making space for
// app 1 in the bottom left.
UpdateDrag(AppsGridView::MOUSE, to + 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(AppsGridViewDragTest, 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);
UpdateLayout();
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 1, 0,
apps_grid_view_);
gfx::Point to = GetItemRectOnCurrentPageAt(1, 0).CenterPoint();
int half_tile_width = std::abs(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);
// Flip drag vector in rtl.
if (is_rtl_)
drag_vector.set_x(-drag_vector.x());
// Drag up, between apps 0 and 2. The gap should open up, making space for app
// 1 in the top right.
UpdateDrag(AppsGridView::MOUSE, to + 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(AppsGridViewDragTest, 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);
UpdateLayout();
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
apps_grid_view_);
gfx::Point to = GetItemRectOnCurrentPageAt(0, 1).CenterPoint();
int half_tile_width = std::abs(GetItemRectOnCurrentPageAt(0, 1).x() -
GetItemRectOnCurrentPageAt(0, 0).x()) /
2;
int tile_height = GetItemRectOnCurrentPageAt(1, 0).y() -
GetItemRectOnCurrentPageAt(0, 0).y();
// Drag over by half a tile and down by one tile. This ends the drag to the
// right of the last item.
gfx::Vector2d drag_vector(half_tile_width, tile_height);
// Flip drag vector in rtl.
if (is_rtl_)
drag_vector.set_x(-drag_vector.x());
UpdateDrag(AppsGridView::MOUSE, to + 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(AppsGridViewDragTest, MouseDragFolderOverItemReorder) {
size_t kTotalItems = 2;
AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(kTotalItems);
model_->PopulateAppWithId(kTotalItems);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
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(AppsGridViewDragTest, MouseDragWithCancelKeepsOrder) {
size_t kTotalItems = 2;
model_->PopulateApps(kTotalItems);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
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(AppsGridViewDragTest, MouseDragWithDeleteItemKeepsOrder) {
size_t kTotalItems = 3;
model_->PopulateApps(kTotalItems);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
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(AppsGridViewDragTest, MouseDragWithAddItemKeepsOrder) {
size_t kTotalItems = 2;
model_->PopulateApps(kTotalItems);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
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_P(AppsGridViewClamshellAndTabletTest, MoveItemInModelPastEndOfList) {
model_->PopulateApps(20);
apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
// 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_P(AppsGridViewClamshellAndTabletTest,
ControlArrowSwapsAppsWithinSamePage) {
model_->PopulateApps(20);
apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
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_P(AppsGridViewClamshellAndTabletTest, ControlArrowRecordsHistogramBasic) {
base::HistogramTester histogram_tester;
model_->PopulateApps(20);
apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
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(GetItemMoveTypeHistogramName(), 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(GetItemMoveTypeHistogramName(), 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(GetItemMoveTypeHistogramName(), 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(GetItemMoveTypeHistogramName(), 6, 4);
}
// Test that histograms do not record when the keyboard move is a no-op.
TEST_P(AppsGridViewClamshellAndTabletTest,
ControlArrowDoesNotRecordHistogramWithNoOpMove) {
base::HistogramTester histogram_tester;
model_->PopulateApps(20);
apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
AppListItemView* moving_item = GetItemViewInTopLevelGrid(0);
apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item);
// Make 2 no-op moves and one successful move from 0,0 and 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(GetItemMoveTypeHistogramName(), 6, 1);
}
// Tests that an item is scrolled to visible position if moved to initially
// invisible grid slot, and that histograms for a long keyboard sequence move
// gets recorded only once.
TEST_P(AppsGridViewClamshellAndTabletTest,
ControlArrowDownwardMoveSequenceToSlotNotInitiallyVisible) {
base::HistogramTester histogram_tester;
model_->PopulateApps(40);
apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
AppListItemView* moving_item = GetItemViewInTopLevelGrid(0);
apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item);
auto app_list_item_view_visible = [this](const views::View* view) -> bool {
return apps_grid_view_->GetWidget()->GetWindowBoundsInScreen().Contains(
view->GetBoundsInScreen());
};
// The test will repeatedly move an item down until the target slot is in a
// position that was initially not visible (either on different apps grid
// page, or clipped withing scrollable apps grid).
// Find the target row:
int first_hidden_row = 0;
while (true) {
const views::View* first_item_in_row =
GetItemViewInTopLevelGrid(first_hidden_row * apps_grid_view_->cols());
ASSERT_TRUE(first_item_in_row);
if (!app_list_item_view_visible(first_item_in_row))
break;
++first_hidden_row;
}
// Make that a series of moves when the control key is left pressed and expect
// one histogram is recorded.
for (int i = 0; i < first_hidden_row; ++i) {
SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN);
SimulateKeyReleased(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN);
}
SimulateKeyReleased(ui::VKEY_DOWN, ui::EF_NONE);
// Verify that the view is visible at the end of the move.
EXPECT_TRUE(app_list_item_view_visible(moving_item));
histogram_tester.ExpectBucketCount(GetItemMoveTypeHistogramName(), 6, 1);
}
// Tests that moving an app down when it is directly below a gap results in a
// swap with the closest item.
TEST_P(AppsGridViewClamshellAndTabletTest, 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_P(AppsGridViewTabletTest, ControlArrowSwapsBetweenFullPages) {
const int kPages = 3;
model_->PopulateApps(GetTilesPerPage(0) + (kPages - 1) * GetTilesPerPage(1));
apps_grid_view_->UpdatePagedViewStructure();
// 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, GetTilesPerPage(0) - apps_grid_view_->cols() + 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(0, false /*animate*/);
const GridIndex moved_view_index(
0, GetTilesPerPage(0) - apps_grid_view_->cols() + 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, GetTilesPerPage(0) - 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(is_rtl_ ? ui::VKEY_LEFT : 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(is_rtl_ ? ui::VKEY_RIGHT : 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_P(AppsGridViewClamshellAndTabletTest,
ControlArrowDownAndRightCreatesNewPage) {
base::HistogramTester histogram_tester;
const int kFirstPageSize = paged_apps_grid_view_ ? GetTilesPerPage(0) : 20;
model_->PopulateApps(kFirstPageSize);
// Focus the last item on the page.
AppListItemView* moving_item = GetItemViewInTopLevelGrid(kFirstPageSize - 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);
bool expect_page_creation = !is_productivity_launcher_enabled_;
histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7,
expect_page_creation ? 1 : 0);
if (expect_page_creation) {
EXPECT_EQ(moving_item, test_api_->GetViewAtIndex(GridIndex(1, 0)));
} else {
EXPECT_EQ(moving_item,
test_api_->GetViewAtIndex(GridIndex(0, kFirstPageSize - 1)));
}
EXPECT_EQ(kFirstPageSize - (expect_page_creation ? 1 : 0),
test_api_->AppsOnPage(0));
if (paged_apps_grid_view_) {
EXPECT_EQ(expect_page_creation ? 1 : 0,
GetPaginationModel()->selected_page());
EXPECT_EQ(expect_page_creation ? 2 : 1,
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,
expect_page_creation ? 2 : 0);
// 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(expect_page_creation ? nullptr : moving_item,
test_api_->GetViewAtIndex(GridIndex(0, kFirstPageSize - 1)));
if (expect_page_creation) {
EXPECT_EQ(moving_item, test_api_->GetViewAtIndex(GridIndex(1, 0)));
} else {
EXPECT_EQ(moving_item,
test_api_->GetViewAtIndex(GridIndex(0, kFirstPageSize - 1)));
}
EXPECT_EQ(kFirstPageSize - (expect_page_creation ? 1 : 0),
test_api_->AppsOnPage(0));
EXPECT_EQ(expect_page_creation ? 1 : 0, test_api_->AppsOnPage(1));
if (paged_apps_grid_view_) {
EXPECT_EQ(expect_page_creation ? 1 : 0,
GetPaginationModel()->selected_page());
EXPECT_EQ(expect_page_creation ? 2 : 1,
GetPaginationModel()->total_pages());
}
histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7,
expect_page_creation ? 3 : 0);
}
// Tests that a page can be deleted if a lonely app is moved down or right to
// another page.
TEST_F(AppsGridViewTest, ControlArrowUpOrLeftRemovesPage) {
base::HistogramTester histogram_tester;
// Move an app so it is by itself on page 1.
model_->PopulateApps(GetTilesPerPage(0));
AppListItemView* moving_item =
GetItemViewInTopLevelGrid(GetTilesPerPage(0) - 1);
apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item);
SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN);
histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7, 1);
EXPECT_EQ(1, GetPaginationModel()->selected_page());
EXPECT_EQ(2, GetPaginationModel()->total_pages());
// Move the app up, test that the page is deleted.
SimulateKeyPress(ui::VKEY_UP, ui::EF_CONTROL_DOWN);
histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7, 2);
EXPECT_EQ(0, GetPaginationModel()->selected_page());
EXPECT_EQ(1, GetPaginationModel()->total_pages());
// Move the app to be by itself again on page 1.
SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN);
histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7, 3);
EXPECT_EQ(1, GetPaginationModel()->selected_page());
EXPECT_EQ(2, GetPaginationModel()->total_pages());
// Move the app left, test that the page is deleted.
SimulateKeyPress(ui::VKEY_LEFT, ui::EF_CONTROL_DOWN);
histogram_tester.ExpectBucketCount("Apps.AppListPageSwitcherSource", 7, 4);
EXPECT_EQ(0, GetPaginationModel()->selected_page());
EXPECT_EQ(1, GetPaginationModel()->total_pages());
}
// Tests that moving a lonely app on the last page down is a no-op when there
// are no pages below.
TEST_P(AppsGridViewClamshellAndTabletTest,
ControlArrowDownOnLastAppOnLastPage) {
base::HistogramTester histogram_tester;
const int kItemCount = paged_apps_grid_view_ ? GetTilesPerPage(0) + 1 : 21;
model_->PopulateApps(kItemCount);
AppListItemView* moving_item = GetItemViewInTopLevelGrid(kItemCount - 1);
apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item);
if (paged_apps_grid_view_) {
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, 0);
histogram_tester.ExpectBucketCount("Apps.AppListAppMovingType", 6, 0);
histogram_tester.ExpectBucketCount("Apps.AppListBubbleAppMovingType", 6, 0);
if (paged_apps_grid_view_) {
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, 0);
histogram_tester.ExpectBucketCount("Apps.AppListAppMovingType", 6, 0);
histogram_tester.ExpectBucketCount("Apps.AppListBubbleAppMovingType", 6, 0);
if (paged_apps_grid_view_) {
EXPECT_EQ(1, GetPaginationModel()->selected_page());
EXPECT_EQ(2, GetPaginationModel()->total_pages());
}
}
// Test that moving an item down or right when it is by itself on a page with a
// page below results in the page deletion.
TEST_F(AppsGridViewTest, ControlArrowDownOrRightRemovesPage) {
// Move an app so it is by itself on page 1, with another app on page 2.
model_->PopulateApps(GetTilesPerPage(0));
AppListItemView* moving_item =
GetItemViewInTopLevelGrid(GetTilesPerPage(0) - 1);
apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item);
SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN);
SimulateKeyPress(ui::VKEY_UP);
SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN);
SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN);
SimulateKeyPress(ui::VKEY_UP);
// The lonely app is selected on page 1, with a page below it containing one
// app.
EXPECT_EQ(1, GetPaginationModel()->selected_page());
EXPECT_EQ(3, GetPaginationModel()->total_pages());
// Test that moving the app on page 1 down, deletes the second page and
// creates a final page with 2 apps.
SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN);
EXPECT_EQ(1, GetPaginationModel()->selected_page());
EXPECT_EQ(2, GetPaginationModel()->total_pages());
EXPECT_EQ(2, test_api_->AppsOnPage(1));
// Create a third page, with an app by itself.
SimulateKeyPress(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN);
SimulateKeyPress(ui::VKEY_UP);
EXPECT_EQ(1, GetPaginationModel()->selected_page());
EXPECT_EQ(3, GetPaginationModel()->total_pages());
// Test that moving the app right moves the selected app to the third page,
// and the second page is deleted.
SimulateKeyPress(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN);
EXPECT_EQ(1, GetPaginationModel()->selected_page());
EXPECT_EQ(2, GetPaginationModel()->total_pages());
EXPECT_EQ(2, test_api_->AppsOnPage(1));
}
// Tests that control + shift + arrow puts |selected_item_| into a folder or
// creates a folder if one does not exist.
TEST_P(AppsGridViewClamshellAndTabletTest, ControlShiftArrowFoldersItemBasic) {
base::HistogramTester histogram_tester;
model_->PopulateApps(3 * apps_grid_view_->cols());
UpdateLayout();
// 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();
ui::test::EventGenerator* const event_generator = GetEventGenerator();
// Press an arrow key to engage keyboard traversal in fullscreen launcher.
event_generator->PressAndReleaseKey(ui::VKEY_DOWN);
// Focus first item, and folder it.
apps_grid_view_->GetFocusManager()->SetFocusedView(first_item);
event_generator->PressAndReleaseKey(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.
AppListItemView* new_folder = GetItemViewInTopLevelGrid(0);
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(GetItemMoveTypeHistogramName(),
kMoveByKeyboardIntoFolder, 1);
// With productivity launcher enabled, the folder is expected to get opened
// after creation.
EXPECT_EQ(!is_productivity_launcher_enabled_,
apps_grid_view_->IsSelectedView(new_folder));
EXPECT_EQ(is_productivity_launcher_enabled_,
GetAppListTestHelper()->IsInFolderView());
if (is_productivity_launcher_enabled_) {
EXPECT_EQ(folder_item, app_list_folder_view_->folder_item());
EXPECT_TRUE(app_list_folder_view_->folder_header_view()
->GetFolderNameViewForTest()
->HasFocus());
// Close the folder.
event_generator->PressAndReleaseKey(ui::VKEY_UP);
event_generator->PressAndReleaseKey(ui::VKEY_ESCAPE);
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
}
ASSERT_TRUE(new_folder->HasFocus());
ASSERT_TRUE(apps_grid_view_->IsSelectedView(new_folder));
// Test that, when a folder is selected, control+shift+arrow does nothing.
event_generator->PressAndReleaseKey(ui::VKEY_RIGHT,
ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
EXPECT_TRUE(apps_grid_view_->IsSelectedView(new_folder));
EXPECT_TRUE(new_folder->HasFocus());
EXPECT_EQ(2u, folder_item->ChildItemCount());
histogram_tester.ExpectBucketCount(GetItemMoveTypeHistogramName(),
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));
event_generator->PressAndReleaseKey(ui::VKEY_LEFT,
ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
EXPECT_TRUE(apps_grid_view_->IsSelectedView(new_folder));
EXPECT_TRUE(new_folder->HasFocus());
EXPECT_EQ(3u, folder_item->ChildItemCount());
histogram_tester.ExpectBucketCount(GetItemMoveTypeHistogramName(),
kMoveByKeyboardIntoFolder, 2);
// Move selection to the item below the folder and put it in the folder.
event_generator->PressAndReleaseKey(ui::VKEY_DOWN);
event_generator->PressAndReleaseKey(ui::VKEY_UP,
ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
EXPECT_TRUE(apps_grid_view_->IsSelectedView(new_folder));
EXPECT_TRUE(new_folder->HasFocus());
EXPECT_EQ(4u, folder_item->ChildItemCount());
histogram_tester.ExpectBucketCount(GetItemMoveTypeHistogramName(),
kMoveByKeyboardIntoFolder, 3);
// Move the folder to the second row, then put the item above the folder in
// the folder.
event_generator->PressAndReleaseKey(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN);
event_generator->PressAndReleaseKey(ui::VKEY_UP);
event_generator->PressAndReleaseKey(ui::VKEY_DOWN,
ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
EXPECT_TRUE(apps_grid_view_->IsSelectedView(new_folder));
EXPECT_TRUE(new_folder->HasFocus());
EXPECT_EQ(5u, folder_item->ChildItemCount());
histogram_tester.ExpectBucketCount(GetItemMoveTypeHistogramName(),
kMoveByKeyboardIntoFolder, 4);
}
// Tests that foldering an item that is on a different page fails.
TEST_P(AppsGridViewTabletTest, ControlShiftArrowFailsToFolderAcrossPages) {
model_->PopulateApps(GetTilesPerPage(0) + GetTilesPerPage(1));
UpdateLayout();
// 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, GetTilesPerPage(0) - apps_grid_view_->cols() + 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, GetTilesPerPage(0) - 1);
AppListItemView* attempted_folder_view =
test_api_->GetViewAtIndex(moved_view_index);
SimulateKeyPress(is_rtl_ ? ui::VKEY_LEFT : 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(is_rtl_ ? ui::VKEY_RIGHT : 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());
}
}
TEST_P(AppsGridViewClamshellAndTabletTest,
KeyboardReparentFromFolderInLastVisibleSlot) {
// Create grid with a folder on the last slot in a page (or for scrollable
// grid, the last slot in the page with enough items that the slot is
// initially not in the visible part of the grid).
const int kTopLevelItemCount = paged_apps_grid_view_
? GetTilesPerPage(0) + GetTilesPerPage(1)
: apps_grid_view_->cols() * 8;
model_->PopulateApps(kTopLevelItemCount - 1);
const AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(3);
const std::string folder_id = folder_item->id();
apps_grid_view_->UpdatePagedViewStructure();
UpdateLayout();
AppListItemView* folder_view = apps_grid_view_->view_model()->view_at(
apps_grid_view_->view_model()->view_size() - 1);
ASSERT_TRUE(folder_view->is_folder());
EXPECT_FALSE(apps_grid_view_->GetWidget()->GetWindowBoundsInScreen().Contains(
folder_view->GetBoundsInScreen()));
folder_view->RequestFocus();
EXPECT_TRUE(apps_grid_view_->GetWidget()->GetWindowBoundsInScreen().Contains(
folder_view->GetBoundsInScreen()));
// Open the folder.
ui::test::EventGenerator* const event_generator = GetEventGenerator();
event_generator->PressAndReleaseKey(ui::VKEY_RETURN);
ASSERT_TRUE(GetAppListTestHelper()->IsInFolderView());
const AppListItemView* reparented_item_view =
folder_apps_grid_view()->view_model()->view_at(0);
ASSERT_TRUE(reparented_item_view);
ASSERT_TRUE(reparented_item_view->item());
std::string reparented_item_id = reparented_item_view->item()->id();
EXPECT_EQ(base::StringPrintf("Item %d", kTopLevelItemCount - 1),
reparented_item_id);
ASSERT_TRUE(reparented_item_view->HasFocus());
// Reparent the item to the slot after the folder view, which should be the
// last spot in the grid.
const ui::KeyboardCode forward_key = is_rtl_ ? ui::VKEY_LEFT : ui::VKEY_RIGHT;
event_generator->PressAndReleaseKey(forward_key,
ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
ASSERT_FALSE(GetAppListTestHelper()->IsInFolderView());
ASSERT_EQ(folder_item, model_->FindItem(folder_id));
EXPECT_EQ(2u, folder_item->ChildItemCount());
const AppListItemView* last_top_level_item_view =
apps_grid_view_->view_model()->view_at(
apps_grid_view_->view_model()->view_size() - 1);
// Verify the view is within visible grid bounds, and that it has focus.
EXPECT_EQ(reparented_item_id, last_top_level_item_view->item()->id());
EXPECT_TRUE(apps_grid_view_->GetWidget()->GetWindowBoundsInScreen().Contains(
last_top_level_item_view->GetBoundsInScreen()));
EXPECT_TRUE(last_top_level_item_view->HasFocus());
// In paged apps grid, the item should have been moved to a new page.
if (paged_apps_grid_view_) {
EXPECT_EQ(2, GetPaginationModel()->selected_page());
EXPECT_EQ(3, GetPaginationModel()->total_pages());
}
}
TEST_P(AppsGridViewClamshellAndTabletTest,
KeyboardReparentFromFolderInLastVisibleSlotUsingDownKey) {
// Create grid with a folder on the last slot in a page (or for scrollable
// grid, the last slot in the page with enough items that the slot is
// initially not in the visible part of the grid).
const int kTopLevelItemCount = paged_apps_grid_view_
? GetTilesPerPage(0) + GetTilesPerPage(1)
: apps_grid_view_->cols() * 8;
model_->PopulateApps(kTopLevelItemCount - 1);
const AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(3);
const std::string folder_id = folder_item->id();
apps_grid_view_->UpdatePagedViewStructure();
UpdateLayout();
AppListItemView* folder_view = apps_grid_view_->view_model()->view_at(
apps_grid_view_->view_model()->view_size() - 1);
ASSERT_TRUE(folder_view->is_folder());
EXPECT_FALSE(apps_grid_view_->GetWidget()->GetWindowBoundsInScreen().Contains(
folder_view->GetBoundsInScreen()));
folder_view->RequestFocus();
EXPECT_TRUE(apps_grid_view_->GetWidget()->GetWindowBoundsInScreen().Contains(
folder_view->GetBoundsInScreen()));
// Open the folder.
ui::test::EventGenerator* const event_generator = GetEventGenerator();
event_generator->PressAndReleaseKey(ui::VKEY_RETURN);
ASSERT_TRUE(GetAppListTestHelper()->IsInFolderView());
const AppListItemView* reparented_item_view =
folder_apps_grid_view()->view_model()->view_at(0);
ASSERT_TRUE(reparented_item_view);
ASSERT_TRUE(reparented_item_view->item());
std::string reparented_item_id = reparented_item_view->item()->id();
EXPECT_EQ(base::StringPrintf("Item %d", kTopLevelItemCount - 1),
reparented_item_id);
ASSERT_TRUE(reparented_item_view->HasFocus());
// Reparent the item to the slot after the folder view, which should be the
// last spot in the grid.
event_generator->PressAndReleaseKey(ui::VKEY_DOWN,
ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
ASSERT_FALSE(GetAppListTestHelper()->IsInFolderView());
ASSERT_EQ(folder_item, model_->FindItem(folder_id));
EXPECT_EQ(2u, folder_item->ChildItemCount());
const AppListItemView* last_top_level_item_view =
apps_grid_view_->view_model()->view_at(
apps_grid_view_->view_model()->view_size() - 1);
// Verify the view is within visible grid bounds, and that it has focus.
EXPECT_EQ(reparented_item_id, last_top_level_item_view->item()->id());
EXPECT_TRUE(apps_grid_view_->GetWidget()->GetWindowBoundsInScreen().Contains(
last_top_level_item_view->GetBoundsInScreen()));
EXPECT_TRUE(last_top_level_item_view->HasFocus());
// In paged apps grid, the item should have been moved to a new page.
if (paged_apps_grid_view_) {
EXPECT_EQ(2, GetPaginationModel()->selected_page());
EXPECT_EQ(3, GetPaginationModel()->total_pages());
}
}
TEST_P(AppsGridViewTabletTest,
KeyboardReparentFromFolderPrefersLeavingMovedItemOnCurrentPage) {
model_->PopulateApps(GetTilesPerPage(0) - 2);
const AppListFolderItem* folder_item =
model_->CreateAndPopulateFolderWithApps(3);
model_->PopulateApps(GetTilesPerPage(1));
const std::string folder_id = folder_item->id();
apps_grid_view_->UpdatePagedViewStructure();
UpdateLayout();
AppListItemView* folder_view =
test_api_->GetViewAtIndex(GridIndex(0, GetTilesPerPage(0) - 2));
ASSERT_TRUE(folder_view->is_folder());
folder_view->RequestFocus();
// Open the folder.
ui::test::EventGenerator* const event_generator = GetEventGenerator();
event_generator->PressAndReleaseKey(ui::VKEY_RETURN);
ASSERT_TRUE(GetAppListTestHelper()->IsInFolderView());
const AppListItemView* reparented_item_view =
folder_apps_grid_view()->view_model()->view_at(0);
ASSERT_TRUE(reparented_item_view);
ASSERT_TRUE(reparented_item_view->item());
std::string reparented_item_id = reparented_item_view->item()->id();
EXPECT_EQ(base::StringPrintf("Item %d", GetTilesPerPage(0) - 2),
reparented_item_id);
ASSERT_TRUE(reparented_item_view->HasFocus());
// Reparent the item to the slot after the folder view, which should be the
// last spot in the grid.
event_generator->PressAndReleaseKey(ui::VKEY_DOWN,
ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
ASSERT_FALSE(GetAppListTestHelper()->IsInFolderView());
ASSERT_EQ(folder_item, model_->FindItem(folder_id));
EXPECT_EQ(2u, folder_item->ChildItemCount());
const AppListItemView* last_item_on_first_page =
test_api_->GetViewAtIndex(GridIndex(0, GetTilesPerPage(0) - 1));
// Verify the view is within visible grid bounds, and that it has focus.
EXPECT_EQ(reparented_item_id, last_item_on_first_page->item()->id());
EXPECT_TRUE(apps_grid_view_->GetWidget()->GetWindowBoundsInScreen().Contains(
last_item_on_first_page->GetBoundsInScreen()));
EXPECT_TRUE(last_item_on_first_page->HasFocus());
EXPECT_EQ(0, GetPaginationModel()->selected_page());
EXPECT_EQ(2, GetPaginationModel()->total_pages());
}
// Tests that foldering the item on the last slot of a page doesn't crash.
TEST_P(AppsGridViewClamshellAndTabletTest,
ControlShiftArrowFolderLastItemOnPage) {
const int kNumberOfApps = 4;
model_->PopulateApps(kNumberOfApps);
UpdateLayout();
// 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();
ui::test::EventGenerator* const event_generator = GetEventGenerator();
// Press an arrow key to engage keyboard traversal in fullscreen launcher.
event_generator->PressAndReleaseKey(ui::VKEY_DOWN);
apps_grid_view_->GetFocusManager()->SetFocusedView(moving_item);
event_generator->PressAndReleaseKey(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);
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));
// With productivity launcher enabled, the folder is expected to get opened
// after creation.
EXPECT_EQ(!is_productivity_launcher_enabled_,
apps_grid_view_->IsSelectedView(new_folder));
EXPECT_EQ(is_productivity_launcher_enabled_,
GetAppListTestHelper()->IsInFolderView());
if (is_productivity_launcher_enabled_) {
EXPECT_EQ(folder_item, app_list_folder_view_->folder_item());
EXPECT_TRUE(app_list_folder_view_->folder_header_view()
->GetFolderNameViewForTest()
->HasFocus());
// Close the folder.
event_generator->PressAndReleaseKey(ui::VKEY_UP);
event_generator->PressAndReleaseKey(ui::VKEY_ESCAPE);
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
}
EXPECT_TRUE(new_folder->HasFocus());
EXPECT_TRUE(apps_grid_view_->IsSelectedView(new_folder));
}
TEST_P(AppsGridViewTabletTest, TouchDragFlipToNextPage) {
ASSERT_TRUE(paged_apps_grid_view_);
// Create 3 full pages of apps.
model_->PopulateApps(GetTilesPerPage(0) + GetTilesPerPage(1) +
GetTilesPerPage(2));
UpdateLayout();
const gfx::Rect apps_grid_bounds = paged_apps_grid_view_->GetLocalBounds();
// Drag an item to the bottom to start flipping pages.
page_flip_waiter_->Reset();
InitiateDragForItemAtCurrentPageAt(AppsGridView::TOUCH, 0, 0,
paged_apps_grid_view_);
gfx::Point apps_grid_bottom_center =
gfx::Point(apps_grid_bounds.width() / 2, apps_grid_bounds.bottom() - 1);
UpdateDrag(AppsGridView::TOUCH, apps_grid_bottom_center,
paged_apps_grid_view_, 5 /*steps*/);
while (HasPendingPageFlip(paged_apps_grid_view_)) {
page_flip_waiter_->Wait();
}
if (features::IsProductivityLauncherEnabled()) {
// A new page cannot be created or flipped to with the ProductivityLauncher
// flag enabled.
EXPECT_EQ("1,2", page_flip_waiter_->selected_pages());
EXPECT_EQ(2, GetPaginationModel()->selected_page());
} else {
// 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());
}
// The drag is centered relative to the app item icon bounds, not the whole
// app item view.
gfx::Vector2d icon_offset(0,
GetAppListConfig()->grid_icon_bottom_padding() / 2);
EXPECT_EQ(apps_grid_bottom_center - icon_offset, GetDragIconCenter());
// End the drag to satisfy checks in AppsGridView destructor.
EndDrag(apps_grid_view_, /*cancel=*/true);
}
TEST_P(AppsGridViewTabletTest, DragAcrossPagesToTheLastSlot) {
ASSERT_TRUE(paged_apps_grid_view_);
// Create a full page and a partially full second page.
model_->PopulateApps(GetTilesPerPage(0) + 3);
apps_grid_view_->UpdatePagedViewStructure();
UpdateLayout();
// Drag an item from the first page to the last existing slot on the next
// page.
const views::ViewModelT<AppListItemView>* view_model =
apps_grid_view_->view_model();
AppListItemView* dragged_view = view_model->view_at(0);
AppListItemView* original_first_item_on_second_page =
view_model->view_at(GetTilesPerPage(0));
auto* generator = GetEventGenerator();
// Initiate drag.
generator->MoveMouseTo(dragged_view->GetBoundsInScreen().CenterPoint());
generator->PressLeftButton();
dragged_view->FireMouseDragTimerForTest();
generator->MoveMouseBy(10, 10);
// Drag the item to launcher page flip zone, and flip the launcher to the
// second page.
generator->MoveMouseTo(
paged_apps_grid_view_->GetBoundsInScreen().bottom_center() +
gfx::Vector2d(0, -1));
ASSERT_TRUE(HasPendingPageFlip(paged_apps_grid_view_));
// Task to move mouse from the page flip area after the page gets flipped, to
// prevent subseuquent page flips.
PostPageFlipTask task(GetPaginationModel(), base::BindLambdaForTesting([&]() {
generator->MoveMouseBy(0, -50);
generator->MoveMouseBy(0, -50);
}));
page_flip_waiter_->Wait();
// Ensure that the reoreder timer ran, and that any views on the second page
// that should have been moved to the first page have done so.
ASSERT_TRUE(paged_apps_grid_view_->reorder_timer_for_test()->IsRunning());
paged_apps_grid_view_->reorder_timer_for_test()->FireNow();
test_api_->WaitForItemMoveAnimationDone();
// Move the item to the first empty slot on the second page.
gfx::Point empty_slot =
test_api_->GetItemTileRectAtVisualIndex(1, 3).CenterPoint();
views::View::ConvertPointToScreen(paged_apps_grid_view_, &empty_slot);
generator->MoveMouseTo(empty_slot);
test_api_->WaitForItemMoveAnimationDone();
if (paged_apps_grid_view_->reorder_timer_for_test()->IsRunning())
paged_apps_grid_view_->reorder_timer_for_test()->FireNow();
test_api_->WaitForItemMoveAnimationDone();
const int expected_final_slot = is_productivity_launcher_enabled_ ? 2 : 3;
EXPECT_EQ(GridIndex(1, expected_final_slot),
paged_apps_grid_view_->reorder_placeholder());
// Verify that the last item in the grid is left of the expected placeholder
// location.
const gfx::Rect last_slot_rect =
GetItemRectOnCurrentPageAt(1, expected_final_slot);
const views::View* last_view =
view_model->view_at(view_model->view_size() - 1);
if (is_rtl_) {
gfx::Point last_view_left_center_in_grid =
last_view->GetLocalBounds().left_center();
views::View::ConvertPointToTarget(last_view, paged_apps_grid_view_,
&last_view_left_center_in_grid);
EXPECT_GE(last_view_left_center_in_grid.x(), last_slot_rect.right());
} else {
gfx::Point last_view_right_center_in_grid =
last_view->GetLocalBounds().right_center();
views::View::ConvertPointToTarget(last_view, paged_apps_grid_view_,
&last_view_right_center_in_grid);
EXPECT_LE(last_view_right_center_in_grid.x(), last_slot_rect.x());
}
EndDrag(paged_apps_grid_view_, false);
EXPECT_EQ(1, GetPaginationModel()->selected_page());
EXPECT_EQ(2, GetPaginationModel()->total_pages());
TestAppListItemViewIndice();
// Verify that the dragged item was moved to the last slot.
AppListItemView* last_item_view =
test_api_->GetViewAtVisualIndex(1, expected_final_slot);
ASSERT_TRUE(last_item_view);
EXPECT_EQ(dragged_view->item()->id(), last_item_view->item()->id());
// For productivity launcher, the first item on second page should have been
// moved to the first page (to fill up the empty slot left by moving the
// draggged item away). For non-productivity launcher, the last slot should
// remain empty.
AppListItemView* last_item_on_first_page =
test_api_->GetViewAtVisualIndex(0, GetTilesPerPage(0) - 1);
ASSERT_EQ(is_productivity_launcher_enabled_, !!last_item_on_first_page);
if (is_productivity_launcher_enabled_) {
EXPECT_EQ(original_first_item_on_second_page->item()->id(),
last_item_on_first_page->item()->id());
}
}
TEST_P(AppsGridViewTabletTest, DragAcrossPagesToSecondToLastSlot) {
ASSERT_TRUE(paged_apps_grid_view_);
// Create a full page and a partially full second page.
model_->PopulateApps(GetTilesPerPage(0) + 3);
apps_grid_view_->UpdatePagedViewStructure();
UpdateLayout();
const views::ViewModelT<AppListItemView>* view_model =
apps_grid_view_->view_model();
AppListItemView* dragged_view = view_model->view_at(0);
AppListItemView* original_first_item_on_second_page =
view_model->view_at(GetTilesPerPage(0));
auto* generator = GetEventGenerator();
// Initiate drag.
generator->MoveMouseTo(dragged_view->GetBoundsInScreen().CenterPoint());
generator->PressLeftButton();
dragged_view->FireMouseDragTimerForTest();
generator->MoveMouseBy(10, 10);
// Drag the item to launcher page flip zone, and flip the launcher to the
// second page.
generator->MoveMouseTo(
paged_apps_grid_view_->GetBoundsInScreen().bottom_center() +
gfx::Vector2d(0, -1));
ASSERT_TRUE(HasPendingPageFlip(paged_apps_grid_view_));
// Task to move mouse from the page flip area after the page gets flipped, to
// prevent subseuquent page flips.
PostPageFlipTask task(GetPaginationModel(), base::BindLambdaForTesting([&]() {
generator->MoveMouseBy(0, -50);
generator->MoveMouseBy(0, -50);
}));
page_flip_waiter_->Wait();
// Ensure that the reoreder timer ran, and that any views on the second page
// that should have been moved to the first page have done so.
ASSERT_TRUE(paged_apps_grid_view_->reorder_timer_for_test()->IsRunning());
paged_apps_grid_view_->reorder_timer_for_test()->FireNow();
test_api_->WaitForItemMoveAnimationDone();
// Move the item between two last slots on the page.
views::View* last_view = view_model->view_at(view_model->view_size() - 1);
const gfx::Point last_slot = last_view->GetBoundsInScreen().CenterPoint();
views::View* second_to_last_view =
view_model->view_at(view_model->view_size() - 2);
const gfx::Point second_to_last_slot =
second_to_last_view->GetBoundsInScreen().CenterPoint();
const gfx::Point drop_point((last_slot.x() + second_to_last_slot.x()) / 2,
last_slot.y());
generator->MoveMouseTo(drop_point);
if (paged_apps_grid_view_->reorder_timer_for_test()->IsRunning())
paged_apps_grid_view_->reorder_timer_for_test()->FireNow();
test_api_->WaitForItemMoveAnimationDone();
const int expected_final_slot = is_productivity_launcher_enabled_ ? 1 : 2;
EXPECT_EQ(GridIndex(1, expected_final_slot),
paged_apps_grid_view_->reorder_placeholder());
// Verify that the last item in the grid is right of the expected placeholder
// location.
const gfx::Rect target_slot_rect =
GetItemRectOnCurrentPageAt(1, expected_final_slot);
if (is_rtl_) {
gfx::Point last_view_right_center_in_grid =
last_view->GetLocalBounds().right_center();
views::View::ConvertPointToTarget(last_view, paged_apps_grid_view_,
&last_view_right_center_in_grid);
EXPECT_LE(last_view_right_center_in_grid.x(), target_slot_rect.x());
} else {
gfx::Point last_view_left_center_in_grid =
last_view->GetLocalBounds().left_center();
views::View::ConvertPointToTarget(last_view, paged_apps_grid_view_,
&last_view_left_center_in_grid);
EXPECT_GE(last_view_left_center_in_grid.x(), target_slot_rect.right());
}
// Verify that second to last item in the grid is left of the expected
// placeholder location.
if (is_rtl_) {
gfx::Point second_to_last_view_left_center_in_grid =
second_to_last_view->GetLocalBounds().left_center();
views::View::ConvertPointToTarget(second_to_last_view,
paged_apps_grid_view_,
&second_to_last_view_left_center_in_grid);
EXPECT_GE(second_to_last_view_left_center_in_grid.x(),
target_slot_rect.right());
} else {
gfx::Point second_to_last_view_right_center_in_grid =
second_to_last_view->GetLocalBounds().right_center();
views::View::ConvertPointToTarget(
second_to_last_view, paged_apps_grid_view_,
&second_to_last_view_right_center_in_grid);
EXPECT_LE(second_to_last_view_right_center_in_grid.x(),
target_slot_rect.x());
}
generator->ReleaseLeftButton();
EXPECT_EQ(1, GetPaginationModel()->selected_page());
EXPECT_EQ(2, GetPaginationModel()->total_pages());
TestAppListItemViewIndice();
// Verify that the dragged item was moved to the target slot.
AppListItemView* last_item_view =
test_api_->GetViewAtVisualIndex(1, expected_final_slot);
ASSERT_TRUE(last_item_view);
EXPECT_EQ(dragged_view->item()->id(), last_item_view->item()->id());
// For productivity launcher, the first item on second page should have been
// moved to the first page (to fill up the empty slot left by moving the
// draggged item away). For non-productivity launcher, the last slot should
// remain empty.
AppListItemView* last_item_on_first_page =
test_api_->GetViewAtVisualIndex(0, GetTilesPerPage(0) - 1);
ASSERT_EQ(is_productivity_launcher_enabled_, !!last_item_on_first_page);
if (is_productivity_launcher_enabled_) {
EXPECT_EQ(original_first_item_on_second_page->item()->id(),
last_item_on_first_page->item()->id());
}
}
TEST_P(AppsGridViewTabletTest,
UpdatePagingIfPageSizesChangeOverflownLandspaceToPortait) {
ASSERT_TRUE(paged_apps_grid_view_);
// Create 2 full pages of apps, and add another app to overflow to third page.
const int kTotalApps = GetTilesPerPage(0) + GetTilesPerPage(1) + 1;
model_->PopulateApps(kTotalApps);
EXPECT_EQ(3, GetPaginationModel()->total_pages());
// Rotate the screen, and verify that the number of pages decreased if new
// page structure fit all apps into 2 pages (number of items per page may
// change between landscape and protrait mode for productivity launcher).
UpdateDisplay("1024x768/r");
EXPECT_EQ(kTotalApps <= GetTilesPerPage(0) + GetTilesPerPage(1) ? 2 : 3,
GetPaginationModel()->total_pages());
}
TEST_P(AppsGridViewTabletTest,
UpdatePagingIfPageSizesChangeUnderflowLandspaceToPortait) {
ASSERT_TRUE(paged_apps_grid_view_);
// Create 2 full pages of apps, and add another app to overflow to third page.
const int kTotalApps = GetTilesPerPage(0) + GetTilesPerPage(1) - 1;
model_->PopulateApps(kTotalApps);
EXPECT_EQ(2, GetPaginationModel()->total_pages());
// Rotate the screen, and verify that the number of pages increased if new
// page structure does not fit all apps into 2 pages (number of items per page
// may change between landscape and protrait mode for productivity launcher).
UpdateDisplay("1024x768/r");
EXPECT_EQ(kTotalApps <= GetTilesPerPage(0) + GetTilesPerPage(1) ? 2 : 3,
GetPaginationModel()->total_pages());
}
TEST_P(AppsGridViewTabletTest,
UpdatePagingIfPageSizesChangeOverflownPortraitToLandcape) {
ASSERT_TRUE(paged_apps_grid_view_);
UpdateDisplay("1024x768/r");
// Create 2 full pages of apps, and add another app to overflow to third page.
const int kTotalApps = GetTilesPerPage(0) + GetTilesPerPage(1) + 1;
model_->PopulateApps(kTotalApps);
EXPECT_EQ(3, GetPaginationModel()->total_pages());
// Rotate the screen, and verify that the number of pages decreased if new
// page structure fit all apps into 2 pages (number of items per page may
// change between landscape and protrait mode for productivity launcher).
UpdateDisplay("1024x768");
EXPECT_EQ(kTotalApps <= GetTilesPerPage(0) + GetTilesPerPage(1) ? 2 : 3,
GetPaginationModel()->total_pages());
}
TEST_P(AppsGridViewTabletTest,
UpdatePagingIfPageSizesChangeUnderflowPortraitToLandscape) {
ASSERT_TRUE(paged_apps_grid_view_);
UpdateDisplay("1024x768/r");
// Create 2 full pages of apps, and add another app to overflow to third page.
const int kTotalApps = GetTilesPerPage(0) + GetTilesPerPage(1) - 1;
model_->PopulateApps(kTotalApps);
EXPECT_EQ(2, GetPaginationModel()->total_pages());
// Rotate the screen, and verify that the number of pages increaesed if new
// page structure fits all apps into 2 pages (which may be the case if
// productivity launcher is enabled, in which case protrait mode grid has more
// items per page than landscape UI).
UpdateDisplay("1024x768");
EXPECT_EQ(kTotalApps <= GetTilesPerPage(0) + GetTilesPerPage(1) ? 2 : 3,
GetPaginationModel()->total_pages());
}
TEST_P(AppsGridViewTabletTest, TouchDragFlipToPreviousPage) {
ASSERT_TRUE(paged_apps_grid_view_);
// Create 3 full pages of apps.
model_->PopulateApps(GetTilesPerPage(0) + GetTilesPerPage(1) +
GetTilesPerPage(2));
// Select the last page.
GetPaginationModel()->SelectPage(2, /*animate=*/false);
// Drag an item to the top to start flipping pages.
page_flip_waiter_->Reset();
InitiateDragForItemAtCurrentPageAt(AppsGridView::TOUCH, 0, 0,
paged_apps_grid_view_);
gfx::Point apps_grid_top_center(
paged_apps_grid_view_->GetLocalBounds().width() / 2, 0);
UpdateDrag(AppsGridView::TOUCH, apps_grid_top_center, paged_apps_grid_view_,
5 /*steps*/);
while (HasPendingPageFlip(paged_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());
// The drag is centered relative to the app item icon bounds, not the whole
// app item view.
gfx::Vector2d icon_offset(0,
GetAppListConfig()->grid_icon_bottom_padding() / 2);
EXPECT_EQ(apps_grid_top_center - icon_offset, GetDragIconCenter());
// End the drag to satisfy checks in AppsGridView destructor.
EndDrag(paged_apps_grid_view_, /*cancel=*/true);
}
TEST_P(AppsGridViewDragTest, CancelDragDoesNotReorderItems) {
const int kTotalItems = 4;
model_->PopulateApps(kTotalItems);
ASSERT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"),
model_->GetModelContent());
// Starts a mouse drag and then cancels it.
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
apps_grid_view_);
const gfx::Point to = GetItemRectOnCurrentPageAt(0, 2).CenterPoint();
UpdateDrag(AppsGridView::MOUSE, to, apps_grid_view_);
EndDrag(apps_grid_view_, /*cancel=*/true);
// Model is not changed.
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);
UpdateLayout();
EXPECT_TRUE(search_box_view_->search_box()->HasFocus());
EXPECT_FALSE(apps_grid_view_->view_model()->view_at(0)->HasFocus());
}
// Test focus change during dragging an item. (See https://crbug.com/834682)
TEST_P(AppsGridViewDragTest, FocusOfDraggedViewDuringDrag) {
model_->PopulateApps(1);
UpdateLayout();
AppListItemView* item_view = InitiateDragForItemAtCurrentPageAt(
AppsGridView::MOUSE, 0, 0, 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_view_->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(AppsGridViewDragTest, FocusOfDraggedViewAfterDrag) {
model_->PopulateApps(1);
UpdateLayout();
auto* item_view = apps_grid_view_->view_model()->view_at(0);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
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*/);
if (features::IsProductivityLauncherEnabled()) {
// ProductivityLauncher keeps focus on the search box after drags.
EXPECT_TRUE(search_box_view_->search_box()->HasFocus());
EXPECT_FALSE(item_view->HasFocus());
} else {
EXPECT_FALSE(search_box_view_->search_box()->HasFocus());
EXPECT_TRUE(item_view->HasFocus());
}
}
// Verify the dragged item's focus after the item is dragged from a folder with
// a single items.
TEST_P(AppsGridViewDragTest, FocusOfReparentedDragViewWithFolderDeleted) {
// Creates a folder item with two items.
model_->CreateAndPopulateFolderWithApps(2);
model_->PopulateApps(1);
test_api_->Update();
// Leave the dragged item as a single folder child.
model_->DeleteItem("Item 1");
// One folder and one app. Therefore the top level view count is 2.
EXPECT_EQ(2, apps_grid_view_->view_model()->view_size());
// Open the folder.
test_api_->PressItemAt(0);
// Drag the first folder child out of the folder.
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
folder_apps_grid_view());
gfx::Point point_outside_folder =
app_list_folder_view()->GetLocalBounds().bottom_center() +
gfx::Vector2d(10, 10);
UpdateDrag(AppsGridView::MOUSE, point_outside_folder, folder_apps_grid_view(),
/*steps=*/10);
// 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 the item in (0,2) spot is the root apps grid. The spot is expected to
// be empty.
gfx::Point drop_point = GetItemRectOnCurrentPageAt(0, 2).CenterPoint();
views::View::ConvertPointToTarget(apps_grid_view_, folder_apps_grid_view(),
&drop_point);
UpdateDrag(AppsGridView::MOUSE, drop_point, folder_apps_grid_view(),
/*steps=*/5);
BoundsChangeCounter counter(GetItemViewInTopLevelGrid(1));
EndDrag(folder_apps_grid_view(), /*cancel=*/false);
// The folder should be deleted. The first item should be Item 2, the second
// item should be Item 0.
EXPECT_EQ(2, apps_grid_view_->view_model()->view_size());
EXPECT_EQ("Item 2", GetItemViewInTopLevelGrid(0)->item()->id());
EXPECT_EQ("Item 0", GetItemViewInTopLevelGrid(1)->item()->id());
AppListItemView* const dragged_view = GetItemViewInTopLevelGrid(1);
if (features::IsProductivityLauncherEnabled()) {
// Verify that Item 2's bounds do not change after calling `EndDrag()`.
EXPECT_EQ(0, counter.bounds_change_count());
// ProductivityLauncher keeps focus on the search box after drags.
EXPECT_TRUE(search_box_view_->search_box()->HasFocus());
EXPECT_FALSE(dragged_view->HasFocus());
} else {
// Verify that Item 2's bounds change once after calling `EndDrag()` due to
// ending the cardified state.
EXPECT_EQ(1, counter.bounds_change_count());
// The dragged item is focused but is not selected.
EXPECT_TRUE(dragged_view->HasFocus());
EXPECT_FALSE(apps_grid_view_->has_selected_view());
// Press the arrow key. The dragged item is selected now.
PressAndReleaseKey(ui::VKEY_RIGHT);
EXPECT_TRUE(dragged_view->HasFocus());
EXPECT_TRUE(apps_grid_view_->has_selected_view());
EXPECT_EQ(dragged_view, apps_grid_view_->selected_view());
}
}
TEST_P(AppsGridViewDragTest, FocusOfReparentedDragViewAfterDrag) {
// Creates a folder item - the folder size was chosen arbitrarily.
model_->CreateAndPopulateFolderWithApps(5);
// Add more apps to the root apps grid.
model_->PopulateApps(2);
test_api_->Update();
// Open the folder.
test_api_->PressItemAt(0);
// Drag the first folder child out of the folder.
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
folder_apps_grid_view());
gfx::Point point_outside_folder =
app_list_folder_view()->GetLocalBounds().bottom_center() +
gfx::Vector2d(10, 10);
UpdateDrag(AppsGridView::MOUSE, point_outside_folder, folder_apps_grid_view(),
/*steps=*/10);
// 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 the item in (0,3) spot is the root apps grid. The spot is expected to
// be empty.
gfx::Point drop_point = GetItemRectOnCurrentPageAt(0, 3).CenterPoint();
views::View::ConvertPointToTarget(apps_grid_view_, folder_apps_grid_view(),
&drop_point);
UpdateDrag(AppsGridView::MOUSE, drop_point, folder_apps_grid_view(),
/*steps=*/5);
EndDrag(folder_apps_grid_view(), /*cancel=*/false);
AppListItemView* const item_view = GetItemViewInTopLevelGrid(3);
EXPECT_EQ("Item 0", item_view->item()->id());
if (features::IsProductivityLauncherEnabled()) {
// ProductivityLauncher keeps focus on the search box after drags.
EXPECT_TRUE(search_box_view_->search_box()->HasFocus());
EXPECT_FALSE(item_view->HasFocus());
} else {
EXPECT_TRUE(item_view->HasFocus());
}
}
TEST_P(AppsGridViewDragTest, DragAndPinItemToShelf) {
model_->PopulateApps(2);
UpdateLayout();
AppListItemView* const item_view = GetItemViewInTopLevelGrid(1);
auto* generator = GetEventGenerator();
generator->MoveMouseTo(item_view->GetBoundsInScreen().CenterPoint());
generator->PressLeftButton();
item_view->FireMouseDragTimerForTest();
generator->MoveMouseBy(10, 10);
// Verify that item drag has started.
ASSERT_TRUE(apps_grid_view_->drag_item());
ASSERT_TRUE(apps_grid_view_->IsDragging());
ASSERT_EQ(item_view->item(), apps_grid_view_->drag_item());
// Shelf should start handling the drag if it moves within its bounds.
auto* shelf_view = GetPrimaryShelf()->GetShelfViewForTesting();
generator->MoveMouseTo(shelf_view->GetBoundsInScreen().left_center());
ASSERT_TRUE(apps_grid_view_->FireDragToShelfTimerForTest());
EXPECT_EQ("Item 1", shelf_view->drag_and_drop_shelf_id().app_id);
// Releasing drag over shelf should pin the dragged app.
generator->ReleaseLeftButton();
EXPECT_TRUE(ShelfModel::Get()->IsAppPinned("Item 1"));
EXPECT_EQ("Item 1", ShelfModel::Get()->items()[0].id.app_id);
}
TEST_P(AppsGridViewDragTest, DragAndPinNotInitiallyVisibleItemToShelf) {
// Add more apps to the root apps grid.
model_->PopulateApps(50);
UpdateLayout();
// Select item that is not withing the default apps grid view bounds.
AppListItemView* const item_view = GetItemViewInTopLevelGrid(40);
ASSERT_FALSE(apps_grid_view_->GetWidget()->GetWindowBoundsInScreen().Contains(
item_view->GetBoundsInScreen()));
// Focusing and scrolling view to visible should ensure it becomes visible
// both in scrollable and paged apps grid.
item_view->RequestFocus();
item_view->ScrollViewToVisible();
ASSERT_TRUE(apps_grid_view_->GetWidget()->GetWindowBoundsInScreen().Contains(
item_view->GetBoundsInScreen()));
auto* generator = GetEventGenerator();
generator->MoveMouseTo(item_view->GetBoundsInScreen().CenterPoint());
generator->PressLeftButton();
item_view->FireMouseDragTimerForTest();
generator->MoveMouseBy(10, 10);
// Verify app list item drag has started.
ASSERT_TRUE(apps_grid_view_->drag_item());
ASSERT_TRUE(apps_grid_view_->IsDragging());
ASSERT_EQ(item_view->item(), apps_grid_view_->drag_item());
// Shelf should start handling the drag if it moves within its bounds.
auto* shelf_view = GetPrimaryShelf()->GetShelfViewForTesting();
generator->MoveMouseTo(shelf_view->GetBoundsInScreen().left_center());
ASSERT_TRUE(apps_grid_view_->FireDragToShelfTimerForTest());
EXPECT_EQ("Item 40", shelf_view->drag_and_drop_shelf_id().app_id);
// Releasing drag over shelf should pin the dragged app.
generator->ReleaseLeftButton();
EXPECT_TRUE(ShelfModel::Get()->IsAppPinned("Item 40"));
EXPECT_EQ("Item 40", ShelfModel::Get()->items()[0].id.app_id);
}
TEST_P(AppsGridViewDragTest, DragItemToAndFromShelf) {
model_->PopulateApps(2);
UpdateLayout();
AppListItemView* const item_view = GetItemViewInTopLevelGrid(1);
auto* generator = GetEventGenerator();
generator->MoveMouseTo(item_view->GetBoundsInScreen().CenterPoint());
generator->PressLeftButton();
item_view->FireMouseDragTimerForTest();
generator->MoveMouseBy(10, 10);
// Verify app list item drag has started.
ASSERT_TRUE(apps_grid_view_->drag_item());
ASSERT_TRUE(apps_grid_view_->IsDragging());
ASSERT_EQ(item_view->item(), apps_grid_view_->drag_item());
// Shelf should start handling the drag if it moves within its bounds.
auto* shelf_view = GetPrimaryShelf()->GetShelfViewForTesting();
generator->MoveMouseTo(shelf_view->GetBoundsInScreen().left_center());
ASSERT_TRUE(apps_grid_view_->FireDragToShelfTimerForTest());
EXPECT_EQ("Item 1", shelf_view->drag_and_drop_shelf_id().app_id);
// Move the app away from shelf, and verify the app doesn't get pinned when
// the drag ends.
generator->MoveMouseTo(apps_grid_view_->GetBoundsInScreen().origin());
generator->ReleaseLeftButton();
EXPECT_FALSE(ShelfModel::Get()->IsAppPinned("Item 1"));
EXPECT_TRUE(ShelfModel::Get()->items().empty());
}
TEST_P(AppsGridViewDragTest, DragAndPinItemFromFolderToShelf) {
// Creates a folder item - the folder size was chosen arbitrarily.
model_->CreateAndPopulateFolderWithApps(5);
// Add more apps to the root apps grid.
model_->PopulateApps(2);
test_api_->Update();
// Open the folder.
test_api_->PressItemAt(0);
AppListItemView* const item_view =
GetItemViewInAppsGridAt(1, folder_apps_grid_view());
auto* generator = GetEventGenerator();
generator->MoveMouseTo(item_view->GetBoundsInScreen().CenterPoint());
generator->PressLeftButton();
item_view->FireMouseDragTimerForTest();
generator->MoveMouseBy(10, 10);
// Verify app list item drag has started.
ASSERT_TRUE(folder_apps_grid_view()->drag_item());
ASSERT_TRUE(folder_apps_grid_view()->IsDragging());
ASSERT_EQ(item_view->item(), folder_apps_grid_view()->drag_item());
generator->MoveMouseTo(
app_list_folder_view()->GetBoundsInScreen().right_center() +
gfx::Vector2d(20, 0));
// Fire the reparent timer that should be started when an item is dragged out
// of folder bounds.
ASSERT_TRUE(folder_apps_grid_view()->FireFolderItemReparentTimerForTest());
// Shelf should start handling the drag if it moves within its bounds.
auto* shelf_view = GetPrimaryShelf()->GetShelfViewForTesting();
generator->MoveMouseTo(shelf_view->GetBoundsInScreen().left_center());
ASSERT_TRUE(folder_apps_grid_view()->FireDragToShelfTimerForTest());
EXPECT_EQ("Item 1", shelf_view->drag_and_drop_shelf_id().app_id);
// Releasing drag over shelf should pin the dragged app.
generator->ReleaseLeftButton();
EXPECT_TRUE(ShelfModel::Get()->IsAppPinned("Item 1"));
EXPECT_EQ("Item 1", ShelfModel::Get()->items()[0].id.app_id);
}
TEST_P(AppsGridViewDragTest, DragAndPinNotInitiallyVisibleFolderItemToShelf) {
model_->CreateAndPopulateFolderWithApps(2 * kMaxItemsPerFolderPage);
UpdateLayout();
// Open the folder.
test_api_->PressItemAt(0);
// Select item that is not within the initial folder view bounds.
AppListItemView* const item_view =
GetItemViewInAppsGridAt(30, folder_apps_grid_view());
ASSERT_FALSE(app_list_folder_view()->GetBoundsInScreen().Contains(
item_view->GetBoundsInScreen()));
// Focusing and scrolling view to visible should ensure it becomes visible
// both in scrollable and paged apps grid.
item_view->RequestFocus();
item_view->ScrollViewToVisible();
ASSERT_TRUE(app_list_folder_view()->GetBoundsInScreen().Contains(
item_view->GetBoundsInScreen()));
auto* generator = GetEventGenerator();
generator->MoveMouseTo(item_view->GetBoundsInScreen().CenterPoint());
generator->PressLeftButton();
item_view->FireMouseDragTimerForTest();
generator->MoveMouseBy(10, 10);
// Verify app list item drag has started.
ASSERT_TRUE(folder_apps_grid_view()->drag_item());
ASSERT_TRUE(folder_apps_grid_view()->IsDragging());
ASSERT_EQ(item_view->item(), folder_apps_grid_view()->drag_item());
generator->MoveMouseTo(
app_list_folder_view()->GetBoundsInScreen().right_center() +
gfx::Vector2d(20, 0));
// Fire the reparent timer that should be started when an item is dragged out
// of folder bounds.
ASSERT_TRUE(folder_apps_grid_view()->FireFolderItemReparentTimerForTest());
// Shelf should start handling the drag if it moves within its bounds.
auto* shelf_view = GetPrimaryShelf()->GetShelfViewForTesting();
generator->MoveMouseTo(shelf_view->GetBoundsInScreen().left_center());
ASSERT_TRUE(folder_apps_grid_view()->FireDragToShelfTimerForTest());
EXPECT_EQ("Item 30", shelf_view->drag_and_drop_shelf_id().app_id);
// Releasing drag over shelf should pin the dragged app.
generator->ReleaseLeftButton();
EXPECT_TRUE(ShelfModel::Get()->IsAppPinned("Item 30"));
EXPECT_EQ("Item 30", ShelfModel::Get()->items()[0].id.app_id);
}
TEST_P(AppsGridViewDragTest, DragAnItemFromFolderToAndFromShelf) {
// Creates a folder item - the folder size was chosen arbitrarily.
model_->CreateAndPopulateFolderWithApps(5);
// Add more apps to the root apps grid.
model_->PopulateApps(2);
UpdateLayout();
// Open the folder.
test_api_->PressItemAt(0);
AppListItemView* const item_view =
GetItemViewInAppsGridAt(1, folder_apps_grid_view());
auto* generator = GetEventGenerator();
generator->MoveMouseTo(item_view->GetBoundsInScreen().CenterPoint());
generator->PressLeftButton();
item_view->FireMouseDragTimerForTest();
generator->MoveMouseBy(10, 10);
// Verify app list item drag has started.
ASSERT_TRUE(folder_apps_grid_view()->drag_item());
ASSERT_TRUE(folder_apps_grid_view()->IsDragging());
ASSERT_EQ(item_view->item(), folder_apps_grid_view()->drag_item());
generator->MoveMouseTo(
app_list_folder_view()->GetBoundsInScreen().right_center() +
gfx::Vector2d(20, 0));
// Fire the reparent timer that should be started when an item is dragged out
// of folder bounds.
ASSERT_TRUE(folder_apps_grid_view()->FireFolderItemReparentTimerForTest());
// Shelf should start handling the drag if it moves within its bounds.
auto* shelf_view = GetPrimaryShelf()->GetShelfViewForTesting();
generator->MoveMouseTo(shelf_view->GetBoundsInScreen().left_center());
ASSERT_TRUE(folder_apps_grid_view()->FireDragToShelfTimerForTest());
EXPECT_EQ("Item 1", shelf_view->drag_and_drop_shelf_id().app_id);
// Move the app away from shelf, and verify the app doesn't get pinned when
// the drag ends.
generator->MoveMouseTo(apps_grid_view_->GetBoundsInScreen().origin());
generator->ReleaseLeftButton();
EXPECT_FALSE(ShelfModel::Get()->IsAppPinned("Item 1"));
EXPECT_TRUE(ShelfModel::Get()->items().empty());
}
TEST_P(AppsGridViewTabletTest, Basic) {
base::HistogramTester histogram_tester;
model_->PopulateApps(GetTilesPerPage(0) + 1);
EXPECT_EQ(2, GetPaginationModel()->total_pages());
gfx::Point apps_grid_view_origin =
apps_grid_view_->GetBoundsInScreen().origin();
ui::GestureEvent scroll_begin(
apps_grid_view_origin.x(), apps_grid_view_origin.y(), 0,
base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, 0, -1));
ui::GestureEvent scroll_update(
apps_grid_view_origin.x(), apps_grid_view_origin.y(), 0,
base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, 0, -10));
ui::GestureEvent scroll_end(
apps_grid_view_origin.x(), apps_grid_view_origin.y(), 0,
base::TimeTicks(), ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END));
// Drag up on the app grid when on page 1, this should move the AppsGridView
// but not the AppListView.
apps_grid_view_->OnGestureEvent(&scroll_begin);
EXPECT_TRUE(scroll_begin.handled());
histogram_tester.ExpectTotalCount(
"Apps.PaginationTransition.DragScroll.PresentationTime.TabletMode", 0);
apps_grid_view_->OnGestureEvent(&scroll_update);
EXPECT_TRUE(scroll_update.handled());
ASSERT_FALSE(app_list_view_->is_in_drag());
ASSERT_NE(0, GetPaginationModel()->transition().progress);
histogram_tester.ExpectTotalCount(
"Apps.PaginationTransition.DragScroll.PresentationTime.TabletMode", 1);
histogram_tester.ExpectTotalCount(
"Apps.PaginationTransition.DragScroll.PresentationTime.MaxLatency."
"TabletMode",
0);
apps_grid_view_->OnGestureEvent(&scroll_end);
histogram_tester.ExpectTotalCount(
"Apps.PaginationTransition.DragScroll.PresentationTime.TabletMode", 1);
histogram_tester.ExpectTotalCount(
"Apps.PaginationTransition.DragScroll.PresentationTime.MaxLatency."
"TabletMode",
1);
}
// 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(0));
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());
views::View* scrollable_container = app_list_view_->app_list_main_view()
->contents_view()
->apps_container_view()
->scrollable_container_for_test();
ASSERT_FALSE(scrollable_container->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(scrollable_container->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(scrollable_container->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 = kMaxItemsPerFolderPage;
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(16, AppsGridViewTestApi(folder_apps_grid_view()).TilesPerPage(0));
EXPECT_EQ(1, GetTotalPages(folder_apps_grid_view()));
EXPECT_EQ(0, GetSelectedPage(folder_apps_grid_view()));
EXPECT_TRUE(folder_apps_grid_view()->IsInFolder());
}
// This is a NonBubble test because new empty pages cannot be created with the
// ProductivityLauncher feature.
TEST_P(AppsGridViewDragNonBubbleTest, MoveAnItemToNewEmptyPage) {
const int kApps = 2;
model_->PopulateApps(kApps);
const views::ViewModelT<AppListItemView>* view_model =
apps_grid_view_->view_model();
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
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());
}
// This is a NonBubble test because new empty pages cannot be created with the
// ProductivityLauncher feature.
TEST_P(AppsGridViewDragNonBubbleTest, MoveLastItemToCreateFolderInNextPage) {
const int kApps = 2;
model_->PopulateApps(kApps);
const views::ViewModelT<AppListItemView>* view_model =
apps_grid_view_->view_model();
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
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);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
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());
}
// This is a NonBubble test because new empty pages cannot be created with the
// ProductivityLauncher feature.
TEST_P(AppsGridViewDragNonBubbleTest, MoveLastItemForReorderInNextPage) {
const int kApps = 2;
model_->PopulateApps(kApps);
const views::ViewModelT<AppListItemView>* view_model =
apps_grid_view_->view_model();
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
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);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
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());
}
// This is a NonBubble test because new empty pages cannot be created with the
// ProductivityLauncher feature.
TEST_P(AppsGridViewDragNonBubbleTest, MoveLastItemToNewEmptyPage) {
const int kApps = 1;
model_->PopulateApps(kApps);
const views::ViewModelT<AppListItemView>* view_model =
apps_grid_view_->view_model();
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
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);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
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(0);
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(AppsGridViewClamshellAndTabletTest, RootGridUpdatesOnModelChange) {
model_->PopulateApps(2);
UpdateLayout();
const views::ViewModelT<AppListItemView>* view_model =
apps_grid_view_->view_model();
EXPECT_EQ(2, view_model->view_size());
TestAppListItemViewIndice();
// Update the model, and verify the apps grid gets updated.
auto model_override = std::make_unique<test::AppListTestModel>();
model_override->PopulateApps(3);
model_override->CreateAndPopulateFolderWithApps(5);
model_override->PopulateApps(3);
auto search_model_override = std::make_unique<SearchModel>();
Shell::Get()->app_list_controller()->SetActiveModel(
/*profile_id=*/1, model_override.get(), search_model_override.get());
UpdateLayout();
// Verify that the view model size matches the new model.
EXPECT_EQ(7, view_model->view_size());
TestAppListItemViewIndice();
// Verify that clicking an item activates it.
LeftClickOn(view_model->view_at(0));
EXPECT_EQ("Item 0", GetTestAppListClient()->activate_item_last_id());
// Clicking on the folder item transitions to folder view.
LeftClickOn(view_model->view_at(3));
EXPECT_TRUE(GetAppListTestHelper()->IsInFolderView());
ASSERT_EQ(5, folder_apps_grid_view()->view_model()->view_size());
// Click on an item within the folder.
LeftClickOn(folder_apps_grid_view()->view_model()->view_at(1));
EXPECT_EQ("Item 4", GetTestAppListClient()->activate_item_last_id());
// Switch model to original one, and verify the folder view gets closed.
Shell::Get()->app_list_controller()->SetActiveModel(
/*profile_id=*/1, model_.get(), search_model_.get());
UpdateLayout();
EXPECT_FALSE(GetAppListTestHelper()->IsInFolderView());
EXPECT_EQ(2, view_model->view_size());
TestAppListItemViewIndice();
LeftClickOn(view_model->view_at(1));
EXPECT_EQ("Item 1", GetTestAppListClient()->activate_item_last_id());
Shell::Get()->app_list_controller()->ClearActiveModel();
EXPECT_EQ(0, view_model->view_size());
}
TEST_P(AppsGridViewClamshellAndTabletTest,
TouchScrollFromFolderNameDoesNotAffectRootGrid) {
// Add enough items to the root grid so the launcher becomes paged.
model_->PopulateApps(1);
model_->CreateAndPopulateFolderWithApps(5);
// `GetTilesPerPage()` may return a large number for bubble launcher - ensure
// the number of test apps is not excessive.
model_->PopulateApps(std::min(30, GetTilesPerPage(0)));
UpdateLayout();
// Open the folder view.
LeftClickOn(apps_grid_view_->view_model()->view_at(1));
ASSERT_TRUE(GetAppListTestHelper()->IsInFolderView());
AppsGridView* const root_grid_view = apps_grid_view_;
const gfx::Point original_root_grid_origin =
apps_grid_view_->GetBoundsInScreen().origin();
const gfx::Point original_first_item_origin =
apps_grid_view_->view_model()->view_at(0)->GetBoundsInScreen().origin();
ui::test::ScrollStepCallback verify_grid_bounds = base::BindLambdaForTesting(
[&](ui::EventType event_type, const gfx::Vector2dF& offset) {
EXPECT_EQ(original_root_grid_origin,
root_grid_view->GetBoundsInScreen().origin());
EXPECT_EQ(original_first_item_origin, root_grid_view->view_model()
->view_at(0)
->GetBoundsInScreen()
.origin());
});
// Simulate upward gesture scroll from folder header view, and verify it
// doesn't affect the root apps grid view location.
gfx::Point scroll_start = app_list_folder_view_->folder_header_view()
->GetBoundsInScreen()
.CenterPoint();
GetEventGenerator()->GestureScrollSequenceWithCallback(
scroll_start, scroll_start - gfx::Vector2d(0, 100),
/*duration=*/base::Milliseconds(50),
/*steps=*/5, verify_grid_bounds);
ASSERT_EQ(original_root_grid_origin,
apps_grid_view_->GetBoundsInScreen().origin());
ASSERT_EQ(
original_first_item_origin,
apps_grid_view_->view_model()->view_at(0)->GetBoundsInScreen().origin());
ASSERT_TRUE(GetAppListTestHelper()->IsInFolderView());
// Simulate downward gesture scroll from folder header view, and verify it
// doesn't affect the root apps grid view location.
scroll_start = app_list_folder_view_->folder_header_view()
->GetBoundsInScreen()
.CenterPoint();
GetEventGenerator()->GestureScrollSequenceWithCallback(
scroll_start, scroll_start + gfx::Vector2d(0, 100),
/*duration=*/base::Milliseconds(50),
/*steps=*/5, verify_grid_bounds);
EXPECT_EQ(original_root_grid_origin,
apps_grid_view_->GetBoundsInScreen().origin());
EXPECT_EQ(
original_first_item_origin,
apps_grid_view_->view_model()->view_at(0)->GetBoundsInScreen().origin());
}
TEST_P(AppsGridViewClamshellAndTabletTest,
TouchScrollFromFolderGridDoesNotAffectRootGrid) {
// Add enough items to the root grid so the launcher becomes paged.
model_->PopulateApps(1);
model_->CreateAndPopulateFolderWithApps(5);
// `GetTilesPerPage()` may return a large number for bubble launcher - ensure
// the number of test apps is not excessive.
model_->PopulateApps(std::min(30, GetTilesPerPage(0)));
UpdateLayout();
// Open the folder view.
LeftClickOn(apps_grid_view_->view_model()->view_at(1));
ASSERT_TRUE(GetAppListTestHelper()->IsInFolderView());
AppsGridView* const root_grid_view = apps_grid_view_;
const gfx::Point original_root_grid_origin =
apps_grid_view_->GetBoundsInScreen().origin();
const gfx::Point original_first_item_origin =
apps_grid_view_->view_model()->view_at(0)->GetBoundsInScreen().origin();
ui::test::ScrollStepCallback verify_grid_bounds = base::BindLambdaForTesting(
[&](ui::EventType event, const gfx::Vector2dF& offset) {
EXPECT_EQ(original_root_grid_origin,
root_grid_view->GetBoundsInScreen().origin());
EXPECT_EQ(original_first_item_origin, root_grid_view->view_model()
->view_at(0)
->GetBoundsInScreen()
.origin());
});
// Simulate downward gesture scroll from folder grid (outside any folder app
// list item view), and verify it doesn't affect the root apps grid view
// location.
gfx::Point scroll_start = GetItemViewInAppsGridAt(0, folder_apps_grid_view())
->GetBoundsInScreen()
.right_center() +
gfx::Vector2d(1, 0);
GetEventGenerator()->GestureScrollSequenceWithCallback(
scroll_start, scroll_start - gfx::Vector2d(0, 100),
/*duration=*/base::Milliseconds(50),
/*steps=*/5, verify_grid_bounds);
ASSERT_EQ(original_root_grid_origin,
apps_grid_view_->GetBoundsInScreen().origin());
ASSERT_EQ(
original_first_item_origin,
apps_grid_view_->view_model()->view_at(0)->GetBoundsInScreen().origin());
ASSERT_TRUE(GetAppListTestHelper()->IsInFolderView());
// Simulate downward gesture scroll from folder header view, and verify it
// doesn't affect the root apps grid view location.
scroll_start = GetItemViewInAppsGridAt(0, folder_apps_grid_view())
->GetBoundsInScreen()
.right_center() +
gfx::Vector2d(1, 0);
GetEventGenerator()->GestureScrollSequenceWithCallback(
scroll_start, scroll_start + gfx::Vector2d(0, 100),
/*duration=*/base::Milliseconds(50),
/*steps=*/5, verify_grid_bounds);
EXPECT_EQ(original_root_grid_origin,
apps_grid_view_->GetBoundsInScreen().origin());
EXPECT_EQ(
original_first_item_origin,
apps_grid_view_->view_model()->view_at(0)->GetBoundsInScreen().origin());
}
// This is a NonBubble test because page breaks are ignored with the
// ProductivityLauncher feature.
TEST_P(AppsGridViewDragNonBubbleTest, PageBreakItemAddedAfterDrag) {
// There are two pages and last item is on second page.
const int kApps = 2 + GetTilesPerPage(0);
model_->PopulateApps(kApps);
GetPaginationModel()->SelectPage(1, false);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
apps_grid_view_);
gfx::Rect tile_rect = test_api_->GetItemTileRectAtVisualIndex(0, 0);
gfx::Point to_in_previous_page =
is_rtl_ ? tile_rect.right_center() : tile_rect.left_center();
// 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(0) - 1)
model_content.append(",PageBreakItem");
}
EXPECT_EQ(model_content, model_->GetModelContent());
}
TEST_P(AppsGridViewTabletTest, MoveItemToPreviousFullPage) {
// There are two pages and last item is on second page.
const int kApps = 2 + GetTilesPerPage(0);
model_->PopulateApps(kApps);
const views::ViewModelT<AppListItemView>* view_model =
apps_grid_view_->view_model();
GetPaginationModel()->SelectPage(1, false);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
apps_grid_view_);
gfx::Rect tile_rect = test_api_->GetItemTileRectAtVisualIndex(0, 0);
gfx::Point to_in_previous_page =
is_rtl_ ? tile_rect.right_center() : tile_rect.left_center();
// 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(0);
int slot = i % GetTilesPerPage(0);
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());
}
}
// This is a NonBubble test because page breaks are ignored with the
// ProductivityLauncher feature.
TEST_P(AppsGridViewDragNonBubbleTest, MoveItemSubsequentDragKeepPageBreak) {
// There are two pages and last item is on second page.
const int kApps = 2 + GetTilesPerPage(0);
model_->PopulateApps(kApps);
const views::ViewModelT<AppListItemView>* view_model =
apps_grid_view_->view_model();
GetPaginationModel()->SelectPage(1, false);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
apps_grid_view_);
gfx::Rect tile_rect = test_api_->GetItemTileRectAtVisualIndex(0, 0);
gfx::Point to_in_previous_page =
is_rtl_ ? tile_rect.right_center() : tile_rect.left_center();
// 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);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 1,
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(0);
int slot = i % GetTilesPerPage(0);
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(0) - 1)
model_content.append(",PageBreakItem");
}
EXPECT_EQ(model_content, model_->GetModelContent());
}
TEST_F(AppsGridViewTest, CreateANewPageWithKeyboardLogsMetrics) {
base::HistogramTester histogram_tester;
model_->PopulateApps(2);
// Select first app and move it with the keyboard down to create a new page.
AppListItemView* moving_item = 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);
}
// This is a NonBubble test because new empty pages cannot be created with the
// ProductivityLauncher feature.
TEST_P(AppsGridViewDragNonBubbleTest, CreateANewPageByDraggingLogsMetrics) {
ASSERT_TRUE(paged_apps_grid_view_) << "Only available in tablet mode or when "
"ProductivityLauncher is disabled.";
base::HistogramTester histogram_tester;
model_->PopulateApps(2);
InitiateDragForItemAtCurrentPageAt(AppsGridView::MOUSE, 0, 0,
paged_apps_grid_view_);
const gfx::Rect apps_grid_bounds = paged_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, paged_apps_grid_view_);
while (HasPendingPageFlip(paged_apps_grid_view_))
page_flip_waiter_->Wait();
EndDrag(paged_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(0));
// 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) {
ASSERT_TRUE(paged_apps_grid_view_);
// Create only one page with two apps.
model_->PopulateApps(2);
// Start cardified apps grid.
InitiateDragForItemAtCurrentPageAt(AppsGridView::TOUCH, 0, 0,
paged_apps_grid_view_);
EXPECT_TRUE(paged_apps_grid_view_->cardified_state_for_testing());
const int kExpectedBackgroundCardCount =
features::IsProductivityLauncherEnabled() ? 1 : 2;
EXPECT_EQ(kExpectedBackgroundCardCount,
paged_apps_grid_view_->BackgroundCardCountForTesting());
EndDrag(paged_apps_grid_view_, false /*cancel*/);
}
TEST_P(AppsGridViewCardifiedStateTest, BackgroundCardBounds) {
ASSERT_TRUE(paged_apps_grid_view_);
model_->PopulateApps(30);
// Enter cardified state.
InitiateDragForItemAtCurrentPageAt(AppsGridView::TOUCH, 0, 0,
paged_apps_grid_view_);
ASSERT_TRUE(paged_apps_grid_view_->cardified_state_for_testing());
const int kExpectedBackgroundCardCount =
features::IsProductivityLauncherEnabled() ? 2 : 3;
ASSERT_EQ(kExpectedBackgroundCardCount,
paged_apps_grid_view_->BackgroundCardCountForTesting());
// Verify that all items in the current page fit within the background card.
gfx::Rect background_card_bounds =
paged_apps_grid_view_->GetBackgroundCardBoundsForTesting(0);
gfx::Rect clip_rect = paged_apps_grid_view_->GetMirroredRect(
paged_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(
GetTilesPerPage(0) / apps_grid_view_->cols() - 1,
apps_grid_view_->cols() - 1);
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 screen rotation (r = 90 degrees clockwise).
UpdateDisplay("1024x768/r");
app_list_view_->OnParentWindowBoundsChanged();
ASSERT_TRUE(paged_apps_grid_view_->cardified_state_for_testing());
ASSERT_EQ(kExpectedBackgroundCardCount,
paged_apps_grid_view_->BackgroundCardCountForTesting());
// Verify that all items in the current page fit within the background card.
background_card_bounds =
paged_apps_grid_view_->GetBackgroundCardBoundsForTesting(0);
clip_rect = paged_apps_grid_view_->GetMirroredRect(
paged_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(
GetTilesPerPage(0) / apps_grid_view_->cols() - 1,
apps_grid_view_->cols() - 1);
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(paged_apps_grid_view_, false /*cancel*/);
EXPECT_EQ(gfx::Rect(), paged_apps_grid_view_->layer()->clip_rect());
EXPECT_FALSE(paged_apps_grid_view_->cardified_state_for_testing());
EXPECT_EQ(0, paged_apps_grid_view_->BackgroundCardCountForTesting());
}
TEST_P(AppsGridViewCardifiedStateTest, BackgroundCardBoundsOnSecondPage) {
ASSERT_TRUE(paged_apps_grid_view_);
model_->PopulateApps(30);
// Enter cardified state, and drag the item to the second apps grid page.
InitiateDragForItemAtCurrentPageAt(AppsGridView::TOUCH, 0, 0,
paged_apps_grid_view_);
const gfx::Point to_in_next_page =
test_api_->GetItemTileRectAtVisualIndex(1, 0).left_center();
// Drag the first item to the next page to create another page.
UpdateDragToNeighborPage(true /* next_page */, to_in_next_page);
// Trigger cardified state again.
InitiateDragForItemAtCurrentPageAt(AppsGridView::TOUCH, 0, 0,
paged_apps_grid_view_);
ASSERT_TRUE(paged_apps_grid_view_->cardified_state_for_testing());
const int kExpectedBackgroundCardCount =
features::IsProductivityLauncherEnabled() ? 2 : 3;
ASSERT_EQ(kExpectedBackgroundCardCount,
paged_apps_grid_view_->BackgroundCardCountForTesting());
// Verify that all items in the current page fit within the background card.
gfx::Rect background_card_bounds =
paged_apps_grid_view_->GetBackgroundCardBoundsForTesting(1);
gfx::Rect clip_rect = paged_apps_grid_view_->GetMirroredRect(
paged_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(
GetTilesPerPage(1) / apps_grid_view_->cols() - 1,
apps_grid_view_->cols() - 1);
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 screen rotation (r = 90 degrees clockwise).
UpdateDisplay("1024x768/r");
ASSERT_TRUE(paged_apps_grid_view_->cardified_state_for_testing());
ASSERT_EQ(kExpectedBackgroundCardCount,
paged_apps_grid_view_->BackgroundCardCountForTesting());
// Verify that all items in the current page fit within the background card.
background_card_bounds =
paged_apps_grid_view_->GetBackgroundCardBoundsForTesting(1);
clip_rect = paged_apps_grid_view_->GetMirroredRect(
paged_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(
GetTilesPerPage(1) / apps_grid_view_->cols() - 1,
apps_grid_view_->cols() - 1);
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(paged_apps_grid_view_, false /*cancel*/);
EXPECT_EQ(gfx::Rect(), paged_apps_grid_view_->layer()->clip_rect());
EXPECT_FALSE(paged_apps_grid_view_->cardified_state_for_testing());
EXPECT_EQ(0, paged_apps_grid_view_->BackgroundCardCountForTesting());
}
// This is a NonBubble test because new empty pages cannot be created with the
// ProductivityLauncher feature.
TEST_P(AppsGridViewDragNonBubbleTest,
PeekingCardOnLastPageAfterCreatingNewPage) {
ASSERT_TRUE(paged_apps_grid_view_);
// Create only one page with two apps.
model_->PopulateApps(2);
InitiateDragForItemAtCurrentPageAt(AppsGridView::TOUCH, 0, 0,
paged_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.
InitiateDragForItemAtCurrentPageAt(AppsGridView::TOUCH, 0, 0,
paged_apps_grid_view_);
EXPECT_TRUE(paged_apps_grid_view_->cardified_state_for_testing());
EXPECT_EQ(3, paged_apps_grid_view_->BackgroundCardCountForTesting());
EndDrag(paged_apps_grid_view_, false /*cancel*/);
}
TEST_P(AppsGridViewCardifiedStateTest, AppsGridIsCardifiedDuringDrag) {
ASSERT_TRUE(paged_apps_grid_view_);
// Create only one page with two apps.
model_->PopulateApps(2);
InitiateDragForItemAtCurrentPageAt(AppsGridView::TOUCH, 0, 0,
paged_apps_grid_view_);
EXPECT_TRUE(paged_apps_grid_view_->cardified_state_for_testing());
EndDrag(paged_apps_grid_view_, false /*cancel*/);
EXPECT_FALSE(paged_apps_grid_view_->cardified_state_for_testing());
}
TEST_P(AppsGridViewCardifiedStateTest,
DragWithinFolderDoesNotEnterCardifiedState) {
ASSERT_TRUE(paged_apps_grid_view_);
// Creates a folder item and open it.
const size_t kTotalItems = kMaxItemsPerFolderPage;
model_->CreateAndPopulateFolderWithApps(kTotalItems);
test_api_->Update();
test_api_->PressItemAt(0);
AppsGridViewTestApi folder_grid_test_api(folder_apps_grid_view());
// Drag the first folder child within the folder.
InitiateDragForItemAtCurrentPageAt(AppsGridView::TOUCH, 0, 0,
folder_apps_grid_view());
const gfx::Point to =
folder_grid_test_api.GetItemTileRectOnCurrentPageAt(0, 1).CenterPoint();
UpdateDrag(AppsGridView::TOUCH, 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(paged_apps_grid_view_->cardified_state_for_testing());
EndDrag(folder_apps_grid_view(), false /*cancel*/);
}
TEST_P(AppsGridViewCardifiedStateTest, DragOutsideFolderEntersCardifiedState) {
ASSERT_TRUE(paged_apps_grid_view_);
// 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());
// Drag the first folder child out of the folder.
AppListItemView* drag_view = InitiateDragForItemAtCurrentPageAt(
AppsGridView::TOUCH, 0, 0, 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::TOUCH, 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(paged_apps_grid_view_->cardified_state_for_testing());
EndDrag(folder_apps_grid_view(), false /*cancel*/);
EXPECT_FALSE(paged_apps_grid_view_->cardified_state_for_testing());
}
TEST_P(AppsGridViewCardifiedStateTest,
DragItemIntoFolderStaysInCardifiedState) {
ASSERT_TRUE(paged_apps_grid_view_);
// Create a folder item with some apps. Add another app to the main grid.
model_->CreateAndPopulateFolderWithApps(2);
model_->PopulateApps(1);
InitiateDragForItemAtCurrentPageAt(AppsGridView::TOUCH, 0, 1,
paged_apps_grid_view_);
// Dragging item_1 over folder to expand it.
const gfx::Point to = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
UpdateDrag(AppsGridView::TOUCH, to, paged_apps_grid_view_, 10 /*steps*/);
EXPECT_TRUE(paged_apps_grid_view_->cardified_state_for_testing());
EndDrag(paged_apps_grid_view_, false /*cancel*/);
EXPECT_FALSE(paged_apps_grid_view_->cardified_state_for_testing());
test_api_->WaitForItemMoveAnimationDone();
test_api_->LayoutToIdealBounds();
}
TEST_P(AppsGridViewAppSortTest, ContextMenuInTopLevelAppListSortAllApps) {
// In this test, the sort algorithm is not tested. Instead, the context menu
// that contains the options to sort is verified to be shown in apps grid
// view. The menu option selecting is also simulated to ensure the sorting is
// called. The actual sort algorithm is tested in
// chrome/browser/ui/app_list/app_list_sort_browsertest.cc.
model_->PopulateApps(1);
AppsGridContextMenu* context_menu = apps_grid_view_->context_menu_for_test();
EXPECT_FALSE(context_menu->IsMenuShowing());
EXPECT_EQ(AppListSortOrder::kCustom, model_->requested_sort_order());
// Get a point in `apps_grid_view_` that doesn't have an item on it.
const gfx::Point empty_space =
apps_grid_view_->GetBoundsInScreen().CenterPoint();
// Open the menu to test the alphabetical sort option.
SimulateRightClickOrLongPressAt(empty_space);
EXPECT_TRUE(context_menu->IsMenuShowing());
// Cache the current context menu view.
views::MenuItemView* reorder_option =
context_menu->root_menu_item_view()->GetSubmenu()->GetMenuItemAt(1);
ASSERT_TRUE(reorder_option->title() == u"Name");
// Open the Reorder by Name submenu.
const gfx::Point reorder_option_point =
reorder_option->GetBoundsInScreen().CenterPoint();
SimulateLeftClickOrTapAt(reorder_option_point);
EXPECT_EQ(AppListSortOrder::kNameAlphabetical,
model_->requested_sort_order());
EXPECT_FALSE(context_menu->IsMenuShowing());
// Open the menu again to test the color sort option.
SimulateRightClickOrLongPressAt(empty_space);
EXPECT_TRUE(context_menu->IsMenuShowing());
reorder_option =
context_menu->root_menu_item_view()->GetSubmenu()->GetMenuItemAt(2);
ASSERT_TRUE(reorder_option->title() == u"Color");
const gfx::Point color_option =
reorder_option->GetBoundsInScreen().CenterPoint();
SimulateLeftClickOrTapAt(color_option);
EXPECT_EQ(AppListSortOrder::kColor, model_->requested_sort_order());
EXPECT_FALSE(context_menu->IsMenuShowing());
}
TEST_P(AppsGridViewAppSortTest, ContextMenuOnFolderItemSortAllApps) {
// In this test, the sort algorithm is not tested. Instead, the context menu
// that contains the options to sort is verified to be shown on folder app
// list item view. The menu option selecting is also simulated to ensure the
// sorting is called. The actual sort algorithm is tested in
// chrome/browser/ui/app_list/app_list_sort_browsertest.cc.
// Create a folder item and update the layout.
model_->CreateAndPopulateFolderWithApps(2);
UpdateLayout();
EXPECT_EQ(AppListSortOrder::kCustom, model_->requested_sort_order());
// Get a point on the folder item.
AppListItemView* folder_item = apps_grid_view_->view_model()->view_at(0);
ASSERT_TRUE(folder_item->is_folder());
gfx::Point folder_item_point = folder_item->GetBoundsInScreen().CenterPoint();
AppsGridContextMenu* context_menu = folder_item->context_menu_for_folder();
ASSERT_TRUE(context_menu);
EXPECT_FALSE(context_menu->IsMenuShowing());
// Open the menu to test the alphabetical sort option.
SimulateRightClickOrLongPressAt(folder_item_point);
EXPECT_TRUE(context_menu->IsMenuShowing());
// Cache the current context menu view.
views::MenuItemView* reorder_option =
context_menu->root_menu_item_view()->GetSubmenu()->GetMenuItemAt(1);
ASSERT_TRUE(reorder_option->title() == u"Name");
// Open the Reorder by Name submenu.
gfx::Point reorder_option_point =
reorder_option->GetBoundsInScreen().CenterPoint();
SimulateLeftClickOrTapAt(reorder_option_point);
EXPECT_EQ(AppListSortOrder::kNameAlphabetical,
model_->requested_sort_order());
EXPECT_FALSE(context_menu->IsMenuShowing());
// Open the menu again to test the color sort option.
SimulateRightClickOrLongPressAt(folder_item_point);
EXPECT_TRUE(context_menu->IsMenuShowing());
reorder_option =
context_menu->root_menu_item_view()->GetSubmenu()->GetMenuItemAt(2);
ASSERT_TRUE(reorder_option->title() == u"Color");
const gfx::Point color_option =
reorder_option->GetBoundsInScreen().CenterPoint();
SimulateLeftClickOrTapAt(color_option);
EXPECT_EQ(AppListSortOrder::kColor, model_->requested_sort_order());
EXPECT_FALSE(context_menu->IsMenuShowing());
}
} // namespace test
} // namespace ash