// Copyright 2013 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/shelf/shelf_model.h" #include <set> #include <string> #include "ash/ash_switches.h" #include "ash/shelf/shelf_model_observer.h" #include "base/command_line.h" #include "base/strings/stringprintf.h" #include "testing/gtest/include/gtest/gtest.h" namespace ash { namespace { // ShelfModelObserver implementation that tracks what message are invoked. class TestShelfModelObserver : public ShelfModelObserver { public: TestShelfModelObserver() : added_count_(0), removed_count_(0), changed_count_(0), moved_count_(0) { } // Returns a string description of the changes that have occurred since this // was last invoked. Resets state to initial state. std::string StateStringAndClear() { std::string result; AddToResult("added=%d", added_count_, &result); AddToResult("removed=%d", removed_count_, &result); AddToResult("changed=%d", changed_count_, &result); AddToResult("moved=%d", moved_count_, &result); added_count_ = removed_count_ = changed_count_ = moved_count_ = 0; return result; } // ShelfModelObserver overrides: virtual void ShelfItemAdded(int index) OVERRIDE { added_count_++; } virtual void ShelfItemRemoved(int index, LauncherID id) OVERRIDE { removed_count_++; } virtual void ShelfItemChanged(int index, const LauncherItem& old_item) OVERRIDE { changed_count_++; } virtual void ShelfItemMoved(int start_index, int target_index) OVERRIDE { moved_count_++; } virtual void ShelfStatusChanged() OVERRIDE { } private: void AddToResult(const std::string& format, int count, std::string* result) { if (!count) return; if (!result->empty()) *result += " "; *result += base::StringPrintf(format.c_str(), count); } int added_count_; int removed_count_; int changed_count_; int moved_count_; DISALLOW_COPY_AND_ASSIGN(TestShelfModelObserver); }; } // namespace class ShelfModelTest : public testing::Test { public: ShelfModelTest() {} virtual ~ShelfModelTest() {} virtual void SetUp() { model_.reset(new ShelfModel); observer_.reset(new TestShelfModelObserver); EXPECT_EQ(0, model_->item_count()); LauncherItem item; item.type = TYPE_APP_LIST; model_->Add(item); EXPECT_EQ(1, model_->item_count()); model_->AddObserver(observer_.get()); } virtual void TearDown() { observer_.reset(); model_.reset(); } scoped_ptr<ShelfModel> model_; scoped_ptr<TestShelfModelObserver> observer_; private: DISALLOW_COPY_AND_ASSIGN(ShelfModelTest); }; TEST_F(ShelfModelTest, BasicAssertions) { // Add an item. LauncherItem item; item.type = TYPE_APP_SHORTCUT; int index = model_->Add(item); EXPECT_EQ(2, model_->item_count()); EXPECT_EQ("added=1", observer_->StateStringAndClear()); // Change to a platform app item. LauncherID original_id = model_->items()[index].id; item.type = TYPE_PLATFORM_APP; model_->Set(index, item); EXPECT_EQ(original_id, model_->items()[index].id); EXPECT_EQ("changed=1", observer_->StateStringAndClear()); EXPECT_EQ(TYPE_PLATFORM_APP, model_->items()[index].type); // Remove the item. model_->RemoveItemAt(index); EXPECT_EQ(1, model_->item_count()); EXPECT_EQ("removed=1", observer_->StateStringAndClear()); // Add an app item. item.type = TYPE_APP_SHORTCUT; index = model_->Add(item); observer_->StateStringAndClear(); // Change everything. model_->Set(index, item); EXPECT_EQ("changed=1", observer_->StateStringAndClear()); EXPECT_EQ(TYPE_APP_SHORTCUT, model_->items()[index].type); // Add another item. item.type = TYPE_APP_SHORTCUT; model_->Add(item); observer_->StateStringAndClear(); // Move the second to the first. model_->Move(1, 0); EXPECT_EQ("moved=1", observer_->StateStringAndClear()); // And back. model_->Move(0, 1); EXPECT_EQ("moved=1", observer_->StateStringAndClear()); // Verifies all the items get unique ids. std::set<LauncherID> ids; for (int i = 0; i < model_->item_count(); ++i) ids.insert(model_->items()[i].id); EXPECT_EQ(model_->item_count(), static_cast<int>(ids.size())); } // Assertions around where items are added. TEST_F(ShelfModelTest, AddIndices) { // Insert browser short cut at index 1. LauncherItem browser_shortcut; browser_shortcut.type = TYPE_BROWSER_SHORTCUT; int browser_shortcut_index = model_->Add(browser_shortcut); EXPECT_EQ(1, browser_shortcut_index); // platform app items should be after browser shortcut. LauncherItem item; item.type = TYPE_PLATFORM_APP; int platform_app_index1 = model_->Add(item); EXPECT_EQ(2, platform_app_index1); // Add another platform app item, it should follow first. int platform_app_index2 = model_->Add(item); EXPECT_EQ(3, platform_app_index2); // APP_SHORTCUT priority is higher than PLATFORM_APP but same as // BROWSER_SHORTCUT. So APP_SHORTCUT is located after BROWSER_SHORCUT. item.type = TYPE_APP_SHORTCUT; int app_shortcut_index1 = model_->Add(item); EXPECT_EQ(2, app_shortcut_index1); item.type = TYPE_APP_SHORTCUT; int app_shortcut_index2 = model_->Add(item); EXPECT_EQ(3, app_shortcut_index2); // Check that AddAt() figures out the correct indexes for app shortcuts. // APP_SHORTCUT and BROWSER_SHORTCUT has the same weight. // So APP_SHORTCUT is located at index 0. And, BROWSER_SHORTCUT is located at // index 1. item.type = TYPE_APP_SHORTCUT; int app_shortcut_index3 = model_->AddAt(1, item); EXPECT_EQ(1, app_shortcut_index3); item.type = TYPE_APP_SHORTCUT; int app_shortcut_index4 = model_->AddAt(6, item); EXPECT_EQ(5, app_shortcut_index4); item.type = TYPE_APP_SHORTCUT; int app_shortcut_index5 = model_->AddAt(3, item); EXPECT_EQ(3, app_shortcut_index5); // Before there are any panels, no icons should be right aligned. EXPECT_EQ(model_->item_count(), model_->FirstPanelIndex()); // Check that AddAt() figures out the correct indexes for platform apps and // panels. item.type = TYPE_PLATFORM_APP; int platform_app_index3 = model_->AddAt(3, item); EXPECT_EQ(7, platform_app_index3); item.type = TYPE_APP_PANEL; int app_panel_index1 = model_->AddAt(2, item); EXPECT_EQ(10, app_panel_index1); item.type = TYPE_PLATFORM_APP; int platform_app_index4 = model_->AddAt(11, item); EXPECT_EQ(10, platform_app_index4); item.type = TYPE_APP_PANEL; int app_panel_index2 = model_->AddAt(12, item); EXPECT_EQ(12, app_panel_index2); item.type = TYPE_PLATFORM_APP; int platform_app_index5 = model_->AddAt(7, item); EXPECT_EQ(7, platform_app_index5); item.type = TYPE_APP_PANEL; int app_panel_index3 = model_->AddAt(13, item); EXPECT_EQ(13, app_panel_index3); // Right aligned index should be the first app panel index. EXPECT_EQ(12, model_->FirstPanelIndex()); EXPECT_EQ(TYPE_BROWSER_SHORTCUT, model_->items()[2].type); EXPECT_EQ(TYPE_APP_LIST, model_->items()[0].type); } // Assertions around where items are added. TEST_F(ShelfModelTest, AddIndicesForLegacyShelfLayout) { CommandLine::ForCurrentProcess()->AppendSwitch( ash::switches::kAshDisableAlternateShelfLayout); // Insert browser short cut at index 0. LauncherItem browser_shortcut; browser_shortcut.type = TYPE_BROWSER_SHORTCUT; int browser_shortcut_index = model_->Add(browser_shortcut); EXPECT_EQ(0, browser_shortcut_index); // platform app items should be after browser shortcut. LauncherItem item; item.type = TYPE_PLATFORM_APP; int platform_app_index1 = model_->Add(item); EXPECT_EQ(1, platform_app_index1); // Add another platform app item, it should follow first. int platform_app_index2 = model_->Add(item); EXPECT_EQ(2, platform_app_index2); // APP_SHORTCUT priority is higher than PLATFORM_APP but same as // BROWSER_SHORTCUT. So APP_SHORTCUT is located after BROWSER_SHORCUT. item.type = TYPE_APP_SHORTCUT; int app_shortcut_index1 = model_->Add(item); EXPECT_EQ(1, app_shortcut_index1); item.type = TYPE_APP_SHORTCUT; int app_shortcut_index2 = model_->Add(item); EXPECT_EQ(2, app_shortcut_index2); // Check that AddAt() figures out the correct indexes for app shortcuts. // APP_SHORTCUT and BROWSER_SHORTCUT has the same weight. // So APP_SHORTCUT is located at index 0. And, BROWSER_SHORTCUT is located at // index 1. item.type = TYPE_APP_SHORTCUT; int app_shortcut_index3 = model_->AddAt(0, item); EXPECT_EQ(0, app_shortcut_index3); item.type = TYPE_APP_SHORTCUT; int app_shortcut_index4 = model_->AddAt(5, item); EXPECT_EQ(4, app_shortcut_index4); item.type = TYPE_APP_SHORTCUT; int app_shortcut_index5 = model_->AddAt(2, item); EXPECT_EQ(2, app_shortcut_index5); // Before there are any panels, no icons should be right aligned. EXPECT_EQ(model_->item_count(), model_->FirstPanelIndex()); // Check that AddAt() figures out the correct indexes for platform apps and // panels. item.type = TYPE_PLATFORM_APP; int platform_app_index3 = model_->AddAt(2, item); EXPECT_EQ(6, platform_app_index3); item.type = TYPE_APP_PANEL; int app_panel_index1 = model_->AddAt(2, item); EXPECT_EQ(10, app_panel_index1); item.type = TYPE_PLATFORM_APP; int platform_app_index4 = model_->AddAt(11, item); EXPECT_EQ(9, platform_app_index4); item.type = TYPE_APP_PANEL; int app_panel_index2 = model_->AddAt(12, item); EXPECT_EQ(12, app_panel_index2); item.type = TYPE_PLATFORM_APP; int platform_app_index5 = model_->AddAt(7, item); EXPECT_EQ(7, platform_app_index5); item.type = TYPE_APP_PANEL; int app_panel_index3 = model_->AddAt(13, item); EXPECT_EQ(13, app_panel_index3); // Right aligned index should be the first app panel index. EXPECT_EQ(12, model_->FirstPanelIndex()); EXPECT_EQ(TYPE_BROWSER_SHORTCUT, model_->items()[1].type); EXPECT_EQ(TYPE_APP_LIST, model_->items()[model_->FirstPanelIndex() - 1].type); } // Assertions around id generation and usage. TEST_F(ShelfModelTest, LauncherIDTests) { // Get the next to use ID counter. LauncherID id = model_->next_id(); // Calling this function multiple times does not change the returned ID. EXPECT_EQ(model_->next_id(), id); // Check that when we reserve a value it will be the previously retrieved ID, // but it will not change the item count and retrieving the next ID should // produce something new. EXPECT_EQ(model_->reserve_external_id(), id); EXPECT_EQ(1, model_->item_count()); LauncherID id2 = model_->next_id(); EXPECT_NE(id2, id); // Adding another item to the list should also produce a new ID. LauncherItem item; item.type = TYPE_PLATFORM_APP; model_->Add(item); EXPECT_NE(model_->next_id(), id2); } // This verifies that converting an existing item into a lower weight category // (e.g. shortcut to running but not pinned app) will move it to the proper // location. See crbug.com/248769. TEST_F(ShelfModelTest, CorrectMoveItemsWhenStateChange) { // The first item is the app list and last item is the browser. LauncherItem browser_shortcut; browser_shortcut.type = TYPE_BROWSER_SHORTCUT; int browser_shortcut_index = model_->Add(browser_shortcut); EXPECT_EQ(TYPE_APP_LIST, model_->items()[0].type); EXPECT_EQ(1, browser_shortcut_index); // Add three shortcuts. They should all be moved between the two. LauncherItem item; item.type = TYPE_APP_SHORTCUT; int app1_index = model_->Add(item); EXPECT_EQ(2, app1_index); int app2_index = model_->Add(item); EXPECT_EQ(3, app2_index); int app3_index = model_->Add(item); EXPECT_EQ(4, app3_index); // Now change the type of the second item and make sure that it is moving // behind the shortcuts. item.type = TYPE_PLATFORM_APP; model_->Set(app2_index, item); // The item should have moved in front of the app launcher. EXPECT_EQ(TYPE_PLATFORM_APP, model_->items()[4].type); } TEST_F(ShelfModelTest, CorrectMoveItemsWhenStateChangeForLegacyShelfLayout) { CommandLine::ForCurrentProcess()->AppendSwitch( ash::switches::kAshDisableAlternateShelfLayout); // The first item is the browser and the second item is app list. LauncherItem browser_shortcut; browser_shortcut.type = TYPE_BROWSER_SHORTCUT; int browser_shortcut_index = model_->Add(browser_shortcut); EXPECT_EQ(0, browser_shortcut_index); EXPECT_EQ(TYPE_APP_LIST, model_->items()[1].type); // Add three shortcuts. They should all be moved between the two. LauncherItem item; item.type = TYPE_APP_SHORTCUT; int app1_index = model_->Add(item); EXPECT_EQ(1, app1_index); int app2_index = model_->Add(item); EXPECT_EQ(2, app2_index); int app3_index = model_->Add(item); EXPECT_EQ(3, app3_index); // Now change the type of the second item and make sure that it is moving // behind the shortcuts. item.type = TYPE_PLATFORM_APP; model_->Set(app2_index, item); // The item should have moved in front of the app launcher. EXPECT_EQ(TYPE_PLATFORM_APP, model_->items()[3].type); } } // namespace ash