1 // Copyright (c) 2020 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 "chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.h"
6
7 #include <memory>
8
9 #include "base/macros.h"
10 #include "base/optional.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/values.h"
13 #include "chrome/browser/extensions/extension_tab_util.h"
14 #include "chrome/browser/ui/tabs/tab_group_model.h"
15 #include "chrome/browser/ui/webui/tab_strip/tab_strip_ui_embedder.h"
16 #include "chrome/browser/ui/webui/tab_strip/tab_strip_ui_layout.h"
17 #include "chrome/test/base/browser_with_test_window_test.h"
18 #include "chrome/test/base/testing_profile_manager.h"
19 #include "components/tab_groups/tab_group_color.h"
20 #include "components/tab_groups/tab_group_id.h"
21 #include "components/tab_groups/tab_group_visual_data.h"
22 #include "content/public/browser/web_ui.h"
23 #include "content/public/test/test_web_ui.h"
24 #include "content/public/test/web_contents_tester.h"
25 #include "ui/base/accelerators/accelerator.h"
26 #include "ui/base/theme_provider.h"
27 #include "ui/gfx/color_utils.h"
28 #include "ui/gfx/geometry/point.h"
29
30 namespace {
31
32 class TestTabStripUIHandler : public TabStripUIHandler {
33 public:
TestTabStripUIHandler(content::WebUI * web_ui,Browser * browser,TabStripUIEmbedder * embedder)34 explicit TestTabStripUIHandler(content::WebUI* web_ui,
35 Browser* browser,
36 TabStripUIEmbedder* embedder)
37 : TabStripUIHandler(browser, embedder) {
38 set_web_ui(web_ui);
39 }
40 };
41
42 class StubTabStripUIEmbedder : public TabStripUIEmbedder {
43 public:
GetAcceleratorProvider() const44 const ui::AcceleratorProvider* GetAcceleratorProvider() const override {
45 return nullptr;
46 }
CloseContainer()47 void CloseContainer() override {}
ShowContextMenuAtPoint(gfx::Point point,std::unique_ptr<ui::MenuModel> menu_model)48 void ShowContextMenuAtPoint(
49 gfx::Point point,
50 std::unique_ptr<ui::MenuModel> menu_model) override {}
ShowEditDialogForGroupAtPoint(gfx::Point point,gfx::Rect rect,tab_groups::TabGroupId group_id)51 void ShowEditDialogForGroupAtPoint(gfx::Point point,
52 gfx::Rect rect,
53 tab_groups::TabGroupId group_id) override {
54 }
GetLayout()55 TabStripUILayout GetLayout() override { return TabStripUILayout(); }
GetColor(int id) const56 SkColor GetColor(int id) const override { return SK_ColorWHITE; }
57 };
58
59 } // namespace
60
61 class TabStripUIHandlerTest : public BrowserWithTestWindowTest {
62 public:
63 TabStripUIHandlerTest() = default;
64
SetUp()65 void SetUp() override {
66 BrowserWithTestWindowTest::SetUp();
67 handler_ = std::make_unique<TestTabStripUIHandler>(web_ui(), browser(),
68 &stub_embedder_);
69 handler()->AllowJavascriptForTesting();
70 web_ui()->ClearTrackedCalls();
71 }
72
handler()73 TabStripUIHandler* handler() { return handler_.get(); }
web_ui()74 content::TestWebUI* web_ui() { return &web_ui_; }
75
ExpectVisualDataDictionary(const tab_groups::TabGroupVisualData visual_data,const base::DictionaryValue * visual_data_dict)76 void ExpectVisualDataDictionary(
77 const tab_groups::TabGroupVisualData visual_data,
78 const base::DictionaryValue* visual_data_dict) {
79 std::string group_title;
80 ASSERT_TRUE(visual_data_dict->GetString("title", &group_title));
81 EXPECT_EQ(base::UTF16ToASCII(visual_data.title()), group_title);
82
83 std::string group_color;
84 ASSERT_TRUE(visual_data_dict->GetString("color", &group_color));
85 EXPECT_EQ(color_utils::SkColorToRgbString(SK_ColorWHITE), group_color);
86 }
87
88 private:
89 StubTabStripUIEmbedder stub_embedder_;
90 content::TestWebUI web_ui_;
91 std::unique_ptr<TestTabStripUIHandler> handler_;
92 };
93
TEST_F(TabStripUIHandlerTest,GroupClosedEvent)94 TEST_F(TabStripUIHandlerTest, GroupClosedEvent) {
95 AddTab(browser(), GURL("http://foo"));
96 tab_groups::TabGroupId expected_group_id =
97 browser()->tab_strip_model()->AddToNewGroup({0});
98 browser()->tab_strip_model()->RemoveFromGroup({0});
99
100 const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
101 EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
102 EXPECT_EQ("tab-group-closed", call_data.arg1()->GetString());
103 EXPECT_EQ(expected_group_id.ToString(), call_data.arg2()->GetString());
104 }
105
TEST_F(TabStripUIHandlerTest,GroupStateChangedEvents)106 TEST_F(TabStripUIHandlerTest, GroupStateChangedEvents) {
107 AddTab(browser(), GURL("http://foo/1"));
108 AddTab(browser(), GURL("http://foo/2"));
109
110 // Add one of the tabs to a group to test for a tab-group-state-changed event.
111 tab_groups::TabGroupId expected_group_id =
112 browser()->tab_strip_model()->AddToNewGroup({0, 1});
113 int expected_tab_id = extensions::ExtensionTabUtil::GetTabId(
114 browser()->tab_strip_model()->GetWebContentsAt(1));
115
116 const content::TestWebUI::CallData& grouped_call_data =
117 *web_ui()->call_data().back();
118 EXPECT_EQ("cr.webUIListenerCallback", grouped_call_data.function_name());
119 EXPECT_EQ("tab-group-state-changed", grouped_call_data.arg1()->GetString());
120 EXPECT_EQ(expected_tab_id, grouped_call_data.arg2()->GetInt());
121 EXPECT_EQ(1, grouped_call_data.arg3()->GetInt());
122 EXPECT_EQ(expected_group_id.ToString(),
123 grouped_call_data.arg4()->GetString());
124
125 // Remove the tab from the group to test for a tab-group-state-changed event.
126 browser()->tab_strip_model()->RemoveFromGroup({1});
127 const content::TestWebUI::CallData& ungrouped_call_data =
128 *web_ui()->call_data().back();
129 EXPECT_EQ("cr.webUIListenerCallback", ungrouped_call_data.function_name());
130 EXPECT_EQ("tab-group-state-changed", ungrouped_call_data.arg1()->GetString());
131 EXPECT_EQ(expected_tab_id, ungrouped_call_data.arg2()->GetInt());
132 EXPECT_EQ(1, ungrouped_call_data.arg3()->GetInt());
133 EXPECT_EQ(nullptr, ungrouped_call_data.arg4());
134 }
135
TEST_F(TabStripUIHandlerTest,GroupMovedEvents)136 TEST_F(TabStripUIHandlerTest, GroupMovedEvents) {
137 // Create a tab group and a few other tabs to allow the group to move.
138 AddTab(browser(), GURL("http://foo/1"));
139 AddTab(browser(), GURL("http://foo/2"));
140 AddTab(browser(), GURL("http://foo/3"));
141 AddTab(browser(), GURL("http://foo/4"));
142 tab_groups::TabGroupId expected_group_id =
143 browser()->tab_strip_model()->AddToNewGroup({0, 1});
144
145 // Select all the tabs in the group.
146 ui::ListSelectionModel selection;
147 selection.AddIndexToSelection(0);
148 selection.AddIndexToSelection(1);
149 selection.set_active(0);
150 browser()->tab_strip_model()->SetSelectionFromModel(selection);
151
152 web_ui()->ClearTrackedCalls();
153
154 // Move the selected tabs to later in the tab strip. This should result in
155 // a single event that is fired to indicate the entire group has moved.
156 int expected_index = 2;
157 browser()->tab_strip_model()->MoveSelectedTabsTo(expected_index);
158
159 EXPECT_EQ(1U, web_ui()->call_data().size());
160
161 const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
162 EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
163 EXPECT_EQ("tab-group-moved", call_data.arg1()->GetString());
164 EXPECT_EQ(expected_group_id.ToString(), call_data.arg2()->GetString());
165 EXPECT_EQ(expected_index, call_data.arg3()->GetInt());
166
167 web_ui()->ClearTrackedCalls();
168
169 // Move the selected tabs to earlier in the tab strip. This should also
170 // result in a single event that is fired to indicate the entire group has
171 // moved.
172 expected_index = 1;
173 browser()->tab_strip_model()->MoveSelectedTabsTo(expected_index);
174
175 EXPECT_EQ(1U, web_ui()->call_data().size());
176
177 const content::TestWebUI::CallData& call_data2 =
178 *web_ui()->call_data().back();
179 EXPECT_EQ("cr.webUIListenerCallback", call_data2.function_name());
180 EXPECT_EQ("tab-group-moved", call_data2.arg1()->GetString());
181 EXPECT_EQ(expected_group_id.ToString(), call_data2.arg2()->GetString());
182 EXPECT_EQ(expected_index, call_data2.arg3()->GetInt());
183 }
184
TEST_F(TabStripUIHandlerTest,GetGroupVisualData)185 TEST_F(TabStripUIHandlerTest, GetGroupVisualData) {
186 AddTab(browser(), GURL("http://foo/1"));
187 AddTab(browser(), GURL("http://foo/2"));
188 tab_groups::TabGroupId group1 =
189 browser()->tab_strip_model()->AddToNewGroup({0});
190 const tab_groups::TabGroupVisualData group1_visuals(
191 base::ASCIIToUTF16("Group 1"), tab_groups::TabGroupColorId::kGreen);
192 browser()
193 ->tab_strip_model()
194 ->group_model()
195 ->GetTabGroup(group1)
196 ->SetVisualData(group1_visuals);
197 tab_groups::TabGroupId group2 =
198 browser()->tab_strip_model()->AddToNewGroup({1});
199 const tab_groups::TabGroupVisualData group2_visuals(
200 base::ASCIIToUTF16("Group 2"), tab_groups::TabGroupColorId::kCyan);
201 browser()
202 ->tab_strip_model()
203 ->group_model()
204 ->GetTabGroup(group2)
205 ->SetVisualData(group2_visuals);
206
207 base::ListValue args;
208 args.AppendString("callback-id");
209 handler()->HandleGetGroupVisualData(&args);
210
211 const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
212 EXPECT_EQ("cr.webUIResponse", call_data.function_name());
213 EXPECT_EQ("callback-id", call_data.arg1()->GetString());
214 EXPECT_TRUE(call_data.arg2()->GetBool());
215
216 const base::DictionaryValue* returned_data;
217 ASSERT_TRUE(call_data.arg3()->GetAsDictionary(&returned_data));
218
219 const base::DictionaryValue* group1_dict;
220 ASSERT_TRUE(returned_data->GetDictionary(group1.ToString(), &group1_dict));
221 ExpectVisualDataDictionary(group1_visuals, group1_dict);
222
223 const base::DictionaryValue* group2_dict;
224 ASSERT_TRUE(returned_data->GetDictionary(group2.ToString(), &group2_dict));
225 ExpectVisualDataDictionary(group2_visuals, group2_dict);
226 }
227
TEST_F(TabStripUIHandlerTest,GroupVisualDataChangedEvent)228 TEST_F(TabStripUIHandlerTest, GroupVisualDataChangedEvent) {
229 AddTab(browser(), GURL("http://foo"));
230 tab_groups::TabGroupId expected_group_id =
231 browser()->tab_strip_model()->AddToNewGroup({0});
232 const tab_groups::TabGroupVisualData new_visual_data(
233 base::ASCIIToUTF16("My new title"), tab_groups::TabGroupColorId::kGreen);
234 browser()
235 ->tab_strip_model()
236 ->group_model()
237 ->GetTabGroup(expected_group_id)
238 ->SetVisualData(new_visual_data);
239
240 const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
241 EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
242 EXPECT_EQ("tab-group-visuals-changed", call_data.arg1()->GetString());
243 EXPECT_EQ(expected_group_id.ToString(), call_data.arg2()->GetString());
244
245 const base::DictionaryValue* visual_data;
246 ASSERT_TRUE(call_data.arg3()->GetAsDictionary(&visual_data));
247 ExpectVisualDataDictionary(new_visual_data, visual_data);
248 }
249
TEST_F(TabStripUIHandlerTest,GroupTab)250 TEST_F(TabStripUIHandlerTest, GroupTab) {
251 // Add a tab inside of a group.
252 AddTab(browser(), GURL("http://foo"));
253 tab_groups::TabGroupId group_id =
254 browser()->tab_strip_model()->AddToNewGroup({0});
255
256 // Add another tab, and try to group it.
257 AddTab(browser(), GURL("http://foo"));
258 base::ListValue args;
259 args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
260 browser()->tab_strip_model()->GetWebContentsAt(0)));
261 args.AppendString(group_id.ToString());
262 handler()->HandleGroupTab(&args);
263
264 ASSERT_EQ(group_id, browser()->tab_strip_model()->GetTabGroupForTab(0));
265 }
266
TEST_F(TabStripUIHandlerTest,MoveGroup)267 TEST_F(TabStripUIHandlerTest, MoveGroup) {
268 AddTab(browser(), GURL("http://foo/1"));
269 AddTab(browser(), GURL("http://foo/2"));
270 tab_groups::TabGroupId group_id =
271 browser()->tab_strip_model()->AddToNewGroup({0});
272
273 // Move the group to index 1.
274 int new_index = 1;
275 base::ListValue args;
276 args.AppendString(group_id.ToString());
277 args.AppendInteger(new_index);
278 handler()->HandleMoveGroup(&args);
279
280 std::vector<int> tabs_in_group = browser()
281 ->tab_strip_model()
282 ->group_model()
283 ->GetTabGroup(group_id)
284 ->ListTabs();
285 ASSERT_EQ(new_index, tabs_in_group.front());
286 ASSERT_EQ(new_index, tabs_in_group.back());
287 }
288
TEST_F(TabStripUIHandlerTest,MoveGroupAcrossWindows)289 TEST_F(TabStripUIHandlerTest, MoveGroupAcrossWindows) {
290 AddTab(browser(), GURL("http://foo"));
291
292 // Create a new window with the same profile, and add a group to it.
293 std::unique_ptr<BrowserWindow> new_window(CreateBrowserWindow());
294 std::unique_ptr<Browser> new_browser =
295 CreateBrowser(profile(), browser()->type(), false, new_window.get());
296 AddTab(new_browser.get(), GURL("http://foo"));
297 AddTab(new_browser.get(), GURL("http://foo"));
298 tab_groups::TabGroupId group_id =
299 new_browser.get()->tab_strip_model()->AddToNewGroup({0, 1});
300
301 // Create some visual data to make sure it gets transferred.
302 const tab_groups::TabGroupVisualData visual_data(
303 base::ASCIIToUTF16("My group"), tab_groups::TabGroupColorId::kGreen);
304 new_browser.get()
305 ->tab_strip_model()
306 ->group_model()
307 ->GetTabGroup(group_id)
308 ->SetVisualData(visual_data);
309
310 content::WebContents* moved_contents1 =
311 new_browser.get()->tab_strip_model()->GetWebContentsAt(0);
312 content::WebContents* moved_contents2 =
313 new_browser.get()->tab_strip_model()->GetWebContentsAt(1);
314 web_ui()->ClearTrackedCalls();
315
316 int new_index = -1;
317 base::ListValue args;
318 args.AppendString(group_id.ToString());
319 args.AppendInteger(new_index);
320 handler()->HandleMoveGroup(&args);
321
322 ASSERT_EQ(0U, new_browser.get()
323 ->tab_strip_model()
324 ->group_model()
325 ->ListTabGroups()
326 .size());
327 ASSERT_EQ(moved_contents1, browser()->tab_strip_model()->GetWebContentsAt(1));
328 ASSERT_EQ(moved_contents2, browser()->tab_strip_model()->GetWebContentsAt(2));
329
330 base::Optional<tab_groups::TabGroupId> new_group_id =
331 browser()->tab_strip_model()->GetTabGroupForTab(1);
332 ASSERT_TRUE(new_group_id.has_value());
333 ASSERT_EQ(browser()->tab_strip_model()->GetTabGroupForTab(1),
334 browser()->tab_strip_model()->GetTabGroupForTab(2));
335
336 const tab_groups::TabGroupVisualData* new_visual_data =
337 browser()
338 ->tab_strip_model()
339 ->group_model()
340 ->GetTabGroup(new_group_id.value())
341 ->visual_data();
342 ASSERT_EQ(visual_data.title(), new_visual_data->title());
343 ASSERT_EQ(visual_data.color(), new_visual_data->color());
344
345 // Test that a WebUI event for the ID change was sent first.
346 const content::TestWebUI::CallData& call_data =
347 *web_ui()->call_data().front();
348 EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
349 EXPECT_EQ("tab-group-id-replaced", call_data.arg1()->GetString());
350 EXPECT_EQ(group_id.ToString(), call_data.arg2()->GetString());
351 EXPECT_EQ(new_group_id.value().ToString(), call_data.arg3()->GetString());
352 }
353
TEST_F(TabStripUIHandlerTest,MoveGroupAcrossProfiles)354 TEST_F(TabStripUIHandlerTest, MoveGroupAcrossProfiles) {
355 AddTab(browser(), GURL("http://foo"));
356
357 TestingProfile* different_profile =
358 profile_manager()->CreateTestingProfile("different_profile");
359 std::unique_ptr<BrowserWindow> new_window(CreateBrowserWindow());
360 std::unique_ptr<Browser> new_browser = CreateBrowser(
361 different_profile, browser()->type(), false, new_window.get());
362 AddTab(new_browser.get(), GURL("http://foo"));
363 tab_groups::TabGroupId group_id =
364 new_browser.get()->tab_strip_model()->AddToNewGroup({0});
365
366 int new_index = -1;
367 base::ListValue args;
368 args.AppendString(group_id.ToString());
369 args.AppendInteger(new_index);
370 handler()->HandleMoveGroup(&args);
371
372 ASSERT_TRUE(
373 new_browser.get()->tab_strip_model()->group_model()->ContainsTabGroup(
374 group_id));
375
376 // Close all tabs before destructing.
377 new_browser.get()->tab_strip_model()->CloseAllTabs();
378 }
379
TEST_F(TabStripUIHandlerTest,MoveTab)380 TEST_F(TabStripUIHandlerTest, MoveTab) {
381 AddTab(browser(), GURL("http://foo"));
382 AddTab(browser(), GURL("http://foo"));
383
384 content::WebContents* contents_prev_at_0 =
385 browser()->tab_strip_model()->GetWebContentsAt(0);
386 content::WebContents* contents_prev_at_1 =
387 browser()->tab_strip_model()->GetWebContentsAt(1);
388
389 // Move tab at index 0 to index 1.
390 base::ListValue args;
391 args.AppendInteger(
392 extensions::ExtensionTabUtil::GetTabId(contents_prev_at_0));
393 args.AppendInteger(1);
394 handler()->HandleMoveTab(&args);
395
396 ASSERT_EQ(1, browser()->tab_strip_model()->GetIndexOfWebContents(
397 contents_prev_at_0));
398 ASSERT_EQ(0, browser()->tab_strip_model()->GetIndexOfWebContents(
399 contents_prev_at_1));
400 }
401
TEST_F(TabStripUIHandlerTest,MoveTabAcrossProfiles)402 TEST_F(TabStripUIHandlerTest, MoveTabAcrossProfiles) {
403 AddTab(browser(), GURL("http://foo"));
404
405 TestingProfile* different_profile =
406 profile_manager()->CreateTestingProfile("different_profile");
407 std::unique_ptr<BrowserWindow> new_window(CreateBrowserWindow());
408 std::unique_ptr<Browser> new_browser = CreateBrowser(
409 different_profile, browser()->type(), false, new_window.get());
410 AddTab(new_browser.get(), GURL("http://foo"));
411
412 base::ListValue args;
413 args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
414 new_browser->tab_strip_model()->GetWebContentsAt(0)));
415 args.AppendInteger(1);
416 handler()->HandleMoveTab(&args);
417
418 ASSERT_FALSE(browser()->tab_strip_model()->ContainsIndex(1));
419
420 // Close all tabs before destructing.
421 new_browser.get()->tab_strip_model()->CloseAllTabs();
422 }
423
TEST_F(TabStripUIHandlerTest,MoveTabAcrossWindows)424 TEST_F(TabStripUIHandlerTest, MoveTabAcrossWindows) {
425 AddTab(browser(), GURL("http://foo"));
426
427 std::unique_ptr<BrowserWindow> new_window(CreateBrowserWindow());
428 std::unique_ptr<Browser> new_browser =
429 CreateBrowser(profile(), browser()->type(), false, new_window.get());
430 AddTab(new_browser.get(), GURL("http://foo"));
431 content::WebContents* moved_contents =
432 new_browser.get()->tab_strip_model()->GetWebContentsAt(0);
433
434 base::ListValue args;
435 args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
436 new_browser->tab_strip_model()->GetWebContentsAt(0)));
437 args.AppendInteger(1);
438 handler()->HandleMoveTab(&args);
439
440 ASSERT_EQ(moved_contents, browser()->tab_strip_model()->GetWebContentsAt(1));
441
442 // Close all tabs before destructing.
443 new_browser.get()->tab_strip_model()->CloseAllTabs();
444 }
445
TEST_F(TabStripUIHandlerTest,TabCreated)446 TEST_F(TabStripUIHandlerTest, TabCreated) {
447 AddTab(browser(), GURL("http://foo"));
448
449 const content::TestWebUI::CallData& call_data =
450 *web_ui()->call_data().front();
451 EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
452 EXPECT_EQ("tab-created", call_data.arg1()->GetString());
453
454 const base::DictionaryValue* tab_data;
455 ASSERT_TRUE(call_data.arg2()->GetAsDictionary(&tab_data));
456
457 int tab_id;
458 ASSERT_TRUE(tab_data->GetInteger("id", &tab_id));
459 ASSERT_EQ(extensions::ExtensionTabUtil::GetTabId(
460 browser()->tab_strip_model()->GetWebContentsAt(0)),
461 tab_id);
462
463 bool is_active;
464 ASSERT_TRUE(tab_data->GetBoolean("active", &is_active));
465 ASSERT_TRUE(is_active);
466
467 int tab_index;
468 ASSERT_TRUE(tab_data->GetInteger("index", &tab_index));
469 ASSERT_EQ(0, tab_index);
470 }
471
TEST_F(TabStripUIHandlerTest,TabRemoved)472 TEST_F(TabStripUIHandlerTest, TabRemoved) {
473 // Two tabs so the browser does not close when a tab is closed.
474 AddTab(browser(), GURL("http://foo"));
475 AddTab(browser(), GURL("http://foo"));
476 int expected_tab_id = extensions::ExtensionTabUtil::GetTabId(
477 browser()->tab_strip_model()->GetWebContentsAt(0));
478
479 web_ui()->ClearTrackedCalls();
480 browser()->tab_strip_model()->GetWebContentsAt(0)->Close();
481
482 const content::TestWebUI::CallData& call_data =
483 *web_ui()->call_data().front();
484 EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
485 EXPECT_EQ("tab-removed", call_data.arg1()->GetString());
486 EXPECT_EQ(expected_tab_id, call_data.arg2()->GetInt());
487 }
488
TEST_F(TabStripUIHandlerTest,TabMoved)489 TEST_F(TabStripUIHandlerTest, TabMoved) {
490 AddTab(browser(), GURL("http://foo"));
491 AddTab(browser(), GURL("http://foo"));
492
493 int from_index = 0;
494 int expected_to_index = 1;
495 int expected_tab_id = extensions::ExtensionTabUtil::GetTabId(
496 browser()->tab_strip_model()->GetWebContentsAt(from_index));
497
498 browser()->tab_strip_model()->MoveWebContentsAt(from_index, expected_to_index,
499 false);
500
501 const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
502 EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
503 EXPECT_EQ("tab-moved", call_data.arg1()->GetString());
504 EXPECT_EQ(expected_tab_id, call_data.arg2()->GetInt());
505 EXPECT_EQ(expected_to_index, call_data.arg3()->GetInt());
506 }
507
TEST_F(TabStripUIHandlerTest,TabReplaced)508 TEST_F(TabStripUIHandlerTest, TabReplaced) {
509 AddTab(browser(), GURL("http://foo"));
510 int expected_previous_id = extensions::ExtensionTabUtil::GetTabId(
511 browser()->tab_strip_model()->GetWebContentsAt(0));
512
513 web_ui()->ClearTrackedCalls();
514 browser()->tab_strip_model()->ReplaceWebContentsAt(
515 0, content::WebContentsTester::CreateTestWebContents(profile(), nullptr));
516 int expected_new_id = extensions::ExtensionTabUtil::GetTabId(
517 browser()->tab_strip_model()->GetWebContentsAt(0));
518
519 const content::TestWebUI::CallData& call_data =
520 *web_ui()->call_data().front();
521 EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
522 EXPECT_EQ("tab-replaced", call_data.arg1()->GetString());
523 ASSERT_EQ(expected_previous_id, call_data.arg2()->GetInt());
524 ASSERT_EQ(expected_new_id, call_data.arg3()->GetInt());
525 }
526
TEST_F(TabStripUIHandlerTest,TabActivated)527 TEST_F(TabStripUIHandlerTest, TabActivated) {
528 AddTab(browser(), GURL("http://foo"));
529 AddTab(browser(), GURL("http://foo"));
530 AddTab(browser(), GURL("http://foo"));
531
532 web_ui()->ClearTrackedCalls();
533 browser()->tab_strip_model()->ActivateTabAt(1);
534
535 const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
536 EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
537 EXPECT_EQ("tab-active-changed", call_data.arg1()->GetString());
538 EXPECT_EQ(extensions::ExtensionTabUtil::GetTabId(
539 browser()->tab_strip_model()->GetWebContentsAt(1)),
540 call_data.arg2()->GetInt());
541 }
542
TEST_F(TabStripUIHandlerTest,UngroupTab)543 TEST_F(TabStripUIHandlerTest, UngroupTab) {
544 // Add a tab inside of a group.
545 AddTab(browser(), GURL("http://foo"));
546 browser()->tab_strip_model()->AddToNewGroup({0});
547
548 // Add another tab at index 1, and try to group it.
549 base::ListValue args;
550 args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
551 browser()->tab_strip_model()->GetWebContentsAt(0)));
552 handler()->HandleUngroupTab(&args);
553
554 ASSERT_FALSE(browser()->tab_strip_model()->GetTabGroupForTab(0).has_value());
555 }
556
TEST_F(TabStripUIHandlerTest,CloseTab)557 TEST_F(TabStripUIHandlerTest, CloseTab) {
558 AddTab(browser(), GURL("http://foo"));
559 AddTab(browser(), GURL("http://bar"));
560
561 base::ListValue args;
562 args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
563 browser()->tab_strip_model()->GetWebContentsAt(0)));
564 args.AppendBoolean(false); // If the tab is closed by swipe.
565 handler()->HandleCloseTab(&args);
566
567 ASSERT_EQ(1, browser()->tab_strip_model()->GetTabCount());
568 }
569