1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "ash/launcher/launcher_model.h" 6 7 #include <set> 8 #include <string> 9 10 #include "ash/launcher/launcher_model_observer.h" 11 #include "base/strings/stringprintf.h" 12 #include "testing/gtest/include/gtest/gtest.h" 13 14 namespace ash { 15 16 namespace { 17 18 // LauncherModelObserver implementation that tracks what message are invoked. 19 class TestLauncherModelObserver : public LauncherModelObserver { 20 public: 21 TestLauncherModelObserver() 22 : added_count_(0), 23 removed_count_(0), 24 changed_count_(0), 25 moved_count_(0) { 26 } 27 28 // Returns a string description of the changes that have occurred since this 29 // was last invoked. Resets state to initial state. 30 std::string StateStringAndClear() { 31 std::string result; 32 AddToResult("added=%d", added_count_, &result); 33 AddToResult("removed=%d", removed_count_, &result); 34 AddToResult("changed=%d", changed_count_, &result); 35 AddToResult("moved=%d", moved_count_, &result); 36 added_count_ = removed_count_ = changed_count_ = moved_count_ = 0; 37 return result; 38 } 39 40 // LauncherModelObserver overrides: 41 virtual void LauncherItemAdded(int index) OVERRIDE { 42 added_count_++; 43 } 44 virtual void LauncherItemRemoved(int index, LauncherID id) OVERRIDE { 45 removed_count_++; 46 } 47 virtual void LauncherItemChanged(int index, 48 const LauncherItem& old_item) OVERRIDE { 49 changed_count_++; 50 } 51 virtual void LauncherItemMoved(int start_index, int target_index) OVERRIDE { 52 moved_count_++; 53 } 54 virtual void LauncherStatusChanged() OVERRIDE { 55 } 56 57 private: 58 void AddToResult(const std::string& format, int count, std::string* result) { 59 if (!count) 60 return; 61 if (!result->empty()) 62 *result += " "; 63 *result += base::StringPrintf(format.c_str(), count); 64 } 65 66 int added_count_; 67 int removed_count_; 68 int changed_count_; 69 int moved_count_; 70 71 DISALLOW_COPY_AND_ASSIGN(TestLauncherModelObserver); 72 }; 73 74 } // namespace 75 76 TEST(LauncherModel, BasicAssertions) { 77 TestLauncherModelObserver observer; 78 LauncherModel model; 79 80 // Model is initially populated with item. 81 EXPECT_EQ(1, model.item_count()); 82 83 // Add an item. 84 model.AddObserver(&observer); 85 LauncherItem item; 86 int index = model.Add(item); 87 EXPECT_EQ(2, model.item_count()); 88 EXPECT_EQ("added=1", observer.StateStringAndClear()); 89 90 // Verifies all the items get unique ids. 91 std::set<LauncherID> ids; 92 for (int i = 0; i < model.item_count(); ++i) 93 ids.insert(model.items()[i].id); 94 EXPECT_EQ(model.item_count(), static_cast<int>(ids.size())); 95 96 // Change a tabbed image. 97 LauncherID original_id = model.items()[index].id; 98 model.Set(index, LauncherItem()); 99 EXPECT_EQ(original_id, model.items()[index].id); 100 EXPECT_EQ("changed=1", observer.StateStringAndClear()); 101 EXPECT_EQ(TYPE_TABBED, model.items()[index].type); 102 103 // Remove the item. 104 model.RemoveItemAt(index); 105 EXPECT_EQ(1, model.item_count()); 106 EXPECT_EQ("removed=1", observer.StateStringAndClear()); 107 108 // Add an app item. 109 item.type = TYPE_APP_SHORTCUT; 110 index = model.Add(item); 111 observer.StateStringAndClear(); 112 113 // Change everything. 114 model.Set(index, item); 115 EXPECT_EQ("changed=1", observer.StateStringAndClear()); 116 EXPECT_EQ(TYPE_APP_SHORTCUT, model.items()[index].type); 117 118 // Add another item. 119 item.type = TYPE_APP_SHORTCUT; 120 model.Add(item); 121 observer.StateStringAndClear(); 122 123 // Move the third to the second. 124 model.Move(2, 1); 125 EXPECT_EQ("moved=1", observer.StateStringAndClear()); 126 127 // And back. 128 model.Move(1, 2); 129 EXPECT_EQ("moved=1", observer.StateStringAndClear()); 130 } 131 132 // Assertions around where items are added. 133 TEST(LauncherModel, AddIndices) { 134 TestLauncherModelObserver observer; 135 LauncherModel model; 136 137 // Model is initially populated with one item. 138 EXPECT_EQ(1, model.item_count()); 139 140 // Insert browser short cut at index 0. 141 LauncherItem browser_shortcut; 142 browser_shortcut.type = TYPE_BROWSER_SHORTCUT; 143 int browser_shortcut_index = model.Add(browser_shortcut); 144 EXPECT_EQ(0, browser_shortcut_index); 145 146 // Tabbed items should be after browser shortcut. 147 LauncherItem item; 148 int tabbed_index1 = model.Add(item); 149 EXPECT_EQ(1, tabbed_index1); 150 151 // Add another tabbed item, it should follow first. 152 int tabbed_index2 = model.Add(item); 153 EXPECT_EQ(2, tabbed_index2); 154 155 // APP_SHORTCUT's priority is higher than TABBED but same as 156 // BROWSER_SHORTCUT. So APP_SHORTCUT is located after BROWSER_SHORCUT. 157 item.type = TYPE_APP_SHORTCUT; 158 int app_shortcut_index1 = model.Add(item); 159 EXPECT_EQ(1, app_shortcut_index1); 160 161 item.type = TYPE_APP_SHORTCUT; 162 int app_shortcut_index2 = model.Add(item); 163 EXPECT_EQ(2, app_shortcut_index2); 164 165 // Check that AddAt() figures out the correct indexes for app shortcuts. 166 // APP_SHORTCUT and BROWSER_SHORTCUT has the same weight. 167 // So APP_SHORTCUT is located at index 0. And, BROWSER_SHORTCUT is located at 168 // index 1. 169 item.type = TYPE_APP_SHORTCUT; 170 int app_shortcut_index3 = model.AddAt(0, item); 171 EXPECT_EQ(0, app_shortcut_index3); 172 173 item.type = TYPE_APP_SHORTCUT; 174 int app_shortcut_index4 = model.AddAt(5, item); 175 EXPECT_EQ(4, app_shortcut_index4); 176 177 item.type = TYPE_APP_SHORTCUT; 178 int app_shortcut_index5 = model.AddAt(2, item); 179 EXPECT_EQ(2, app_shortcut_index5); 180 181 // Before there are any panels, no icons should be right aligned. 182 EXPECT_EQ(model.item_count(), model.FirstPanelIndex()); 183 184 // Check that AddAt() figures out the correct indexes for tabs and panels. 185 item.type = TYPE_TABBED; 186 int tabbed_index3 = model.AddAt(2, item); 187 EXPECT_EQ(6, tabbed_index3); 188 189 item.type = TYPE_APP_PANEL; 190 int app_panel_index1 = model.AddAt(2, item); 191 EXPECT_EQ(10, app_panel_index1); 192 193 item.type = TYPE_TABBED; 194 int tabbed_index4 = model.AddAt(11, item); 195 EXPECT_EQ(9, tabbed_index4); 196 197 item.type = TYPE_APP_PANEL; 198 int app_panel_index2 = model.AddAt(12, item); 199 EXPECT_EQ(12, app_panel_index2); 200 201 item.type = TYPE_TABBED; 202 int tabbed_index5 = model.AddAt(7, item); 203 EXPECT_EQ(7, tabbed_index5); 204 205 item.type = TYPE_APP_PANEL; 206 int app_panel_index3 = model.AddAt(13, item); 207 EXPECT_EQ(13, app_panel_index3); 208 209 // Right aligned index should be the first app panel index. 210 EXPECT_EQ(12, model.FirstPanelIndex()); 211 212 EXPECT_EQ(TYPE_BROWSER_SHORTCUT, model.items()[1].type); 213 EXPECT_EQ(TYPE_APP_LIST, model.items()[model.FirstPanelIndex() - 1].type); 214 } 215 216 // Assertions around id generation and usage. 217 TEST(LauncherModel, LauncherIDTests) { 218 TestLauncherModelObserver observer; 219 LauncherModel model; 220 221 EXPECT_EQ(1, model.item_count()); 222 223 // Get the next to use ID counter. 224 LauncherID id = model.next_id(); 225 226 // Calling this function multiple times does not change the returned ID. 227 EXPECT_EQ(model.next_id(), id); 228 229 // Check that when we reserve a value it will be the previously retrieved ID, 230 // but it will not change the item count and retrieving the next ID should 231 // produce something new. 232 EXPECT_EQ(model.reserve_external_id(), id); 233 EXPECT_EQ(1, model.item_count()); 234 LauncherID id2 = model.next_id(); 235 EXPECT_NE(id2, id); 236 237 // Adding another item to the list should also produce a new ID. 238 LauncherItem item; 239 item.type = TYPE_TABBED; 240 model.Add(item); 241 EXPECT_NE(model.next_id(), id2); 242 } 243 244 // This verifies that converting an existing item into a lower weight category 245 // (e.g. shortcut to running but not pinned app) will move it to the proper 246 // location. See crbug.com/248769. 247 TEST(LauncherModel, CorrectMoveItemsWhenStateChange) { 248 LauncherModel model; 249 250 // The app list should be the last item in the list. 251 EXPECT_EQ(1, model.item_count()); 252 253 // The first item is the browser. 254 LauncherItem browser_shortcut; 255 browser_shortcut.type = TYPE_BROWSER_SHORTCUT; 256 int browser_shortcut_index = model.Add(browser_shortcut); 257 EXPECT_EQ(0, browser_shortcut_index); 258 259 // Add three shortcuts. They should all be moved between the two. 260 LauncherItem item; 261 item.type = TYPE_APP_SHORTCUT; 262 int app1_index = model.Add(item); 263 EXPECT_EQ(1, app1_index); 264 int app2_index = model.Add(item); 265 EXPECT_EQ(2, app2_index); 266 int app3_index = model.Add(item); 267 EXPECT_EQ(3, app3_index); 268 269 // Now change the type of the second item and make sure that it is moving 270 // behind the shortcuts. 271 item.type = TYPE_PLATFORM_APP; 272 model.Set(app2_index, item); 273 274 // The item should have moved in front of the app launcher. 275 EXPECT_EQ(TYPE_PLATFORM_APP, model.items()[3].type); 276 } 277 278 } // namespace ash 279