// 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