// Copyright 2015 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 "base/strings/string_number_conversions.h" #include "components/mus/ws/server_window.h" #include "components/mus/ws/server_window_observer.h" #include "components/mus/ws/test_server_window_delegate.h" #include "testing/gtest/include/gtest/gtest.h" namespace mus { namespace ws { namespace { class TestTransientWindowObserver : public ServerWindowObserver { public: TestTransientWindowObserver() : add_count_(0), remove_count_(0) {} ~TestTransientWindowObserver() override {} int add_count() const { return add_count_; } int remove_count() const { return remove_count_; } // TransientWindowObserver overrides: void OnTransientWindowAdded(ServerWindow* window, ServerWindow* transient) override { add_count_++; } void OnTransientWindowRemoved(ServerWindow* window, ServerWindow* transient) override { remove_count_++; } private: int add_count_; int remove_count_; DISALLOW_COPY_AND_ASSIGN(TestTransientWindowObserver); }; ServerWindow* CreateTestWindow(TestServerWindowDelegate* delegate, const WindowId& window_id, ServerWindow* parent) { ServerWindow* window = new ServerWindow(delegate, window_id); window->SetVisible(true); if (parent) parent->Add(window); else delegate->set_root_window(window); return window; } std::string ChildWindowIDsAsString(ServerWindow* parent) { std::string result; for (auto i = parent->children().begin(); i != parent->children().end(); ++i) { if (!result.empty()) result += " "; result += base::IntToString(WindowIdToTransportId((*i)->id())); } return result; } } // namespace class TransientWindowsTest : public testing::Test { public: TransientWindowsTest() {} ~TransientWindowsTest() override {} private: DISALLOW_COPY_AND_ASSIGN(TransientWindowsTest); }; TEST_F(TransientWindowsTest, TransientChildren) { TestServerWindowDelegate server_window_delegate; scoped_ptr parent( CreateTestWindow(&server_window_delegate, WindowId(), nullptr)); scoped_ptr w1( CreateTestWindow(&server_window_delegate, WindowId(1, 1), parent.get())); scoped_ptr w3( CreateTestWindow(&server_window_delegate, WindowId(1, 2), parent.get())); ServerWindow* w2 = CreateTestWindow(&server_window_delegate, WindowId(1, 3), parent.get()); // w2 is now owned by w1. w1->AddTransientWindow(w2); // Stack w1 at the top (end), this should force w2 to be last (on top of w1). parent->StackChildAtTop(w1.get()); ASSERT_EQ(3u, parent->children().size()); EXPECT_EQ(w2, parent->children().back()); // Destroy w1, which should also destroy w3 (since it's a transient child). w1.reset(); w2 = nullptr; ASSERT_EQ(1u, parent->children().size()); EXPECT_EQ(w3.get(), parent->children()[0]); } // Tests that transient children are stacked as a unit when using stack above. TEST_F(TransientWindowsTest, TransientChildrenGroupAbove) { TestServerWindowDelegate server_window_delegate; scoped_ptr parent( CreateTestWindow(&server_window_delegate, WindowId(), nullptr)); scoped_ptr w1( CreateTestWindow(&server_window_delegate, WindowId(0, 1), parent.get())); ServerWindow* w11 = CreateTestWindow(&server_window_delegate, WindowId(0, 11), parent.get()); scoped_ptr w2( CreateTestWindow(&server_window_delegate, WindowId(0, 2), parent.get())); ServerWindow* w21 = CreateTestWindow(&server_window_delegate, WindowId(0, 21), parent.get()); ServerWindow* w211 = CreateTestWindow(&server_window_delegate, WindowId(0, 211), parent.get()); ServerWindow* w212 = CreateTestWindow(&server_window_delegate, WindowId(0, 212), parent.get()); ServerWindow* w213 = CreateTestWindow(&server_window_delegate, WindowId(0, 213), parent.get()); ServerWindow* w22 = CreateTestWindow(&server_window_delegate, WindowId(0, 22), parent.get()); ASSERT_EQ(8u, parent->children().size()); // w11 is now owned by w1. w1->AddTransientWindow(w11); // w21 is now owned by w2. w2->AddTransientWindow(w21); // w22 is now owned by w2. w2->AddTransientWindow(w22); // w211 is now owned by w21. w21->AddTransientWindow(w211); // w212 is now owned by w21. w21->AddTransientWindow(w212); // w213 is now owned by w21. w21->AddTransientWindow(w213); EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); // Stack w1 at the top (end), this should force w11 to be last (on top of w1). parent->StackChildAtTop(w1.get()); EXPECT_EQ(w11, parent->children().back()); EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); // This tests that the order in children_ array rather than in // transient_children_ array is used when reinserting transient children. // If transient_children_ array was used '22' would be following '21'. parent->StackChildAtTop(w2.get()); EXPECT_EQ(w22, parent->children().back()); EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); w11->Reorder(w2.get(), mojom::ORDER_DIRECTION_ABOVE); EXPECT_EQ(w11, parent->children().back()); EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); w21->Reorder(w1.get(), mojom::ORDER_DIRECTION_ABOVE); EXPECT_EQ(w22, parent->children().back()); EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); w21->Reorder(w22, mojom::ORDER_DIRECTION_ABOVE); EXPECT_EQ(w213, parent->children().back()); EXPECT_EQ("1 11 2 22 21 211 212 213", ChildWindowIDsAsString(parent.get())); w11->Reorder(w21, mojom::ORDER_DIRECTION_ABOVE); EXPECT_EQ(w11, parent->children().back()); EXPECT_EQ("2 22 21 211 212 213 1 11", ChildWindowIDsAsString(parent.get())); w213->Reorder(w21, mojom::ORDER_DIRECTION_ABOVE); EXPECT_EQ(w11, parent->children().back()); EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); // No change when stacking a transient parent above its transient child. w21->Reorder(w211, mojom::ORDER_DIRECTION_ABOVE); EXPECT_EQ(w11, parent->children().back()); EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); // This tests that the order in children_ array rather than in // transient_children_ array is used when reinserting transient children. // If transient_children_ array was used '22' would be following '21'. w2->Reorder(w1.get(), mojom::ORDER_DIRECTION_ABOVE); EXPECT_EQ(w212, parent->children().back()); EXPECT_EQ("1 11 2 22 21 213 211 212", ChildWindowIDsAsString(parent.get())); w11->Reorder(w213, mojom::ORDER_DIRECTION_ABOVE); EXPECT_EQ(w11, parent->children().back()); EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); } TEST_F(TransientWindowsTest, TransienChildGroupBelow) { TestServerWindowDelegate server_window_delegate; scoped_ptr parent( CreateTestWindow(&server_window_delegate, WindowId(), nullptr)); scoped_ptr w1( CreateTestWindow(&server_window_delegate, WindowId(0, 1), parent.get())); ServerWindow* w11 = CreateTestWindow(&server_window_delegate, WindowId(0, 11), parent.get()); scoped_ptr w2( CreateTestWindow(&server_window_delegate, WindowId(0, 2), parent.get())); ServerWindow* w21 = CreateTestWindow(&server_window_delegate, WindowId(0, 21), parent.get()); ServerWindow* w211 = CreateTestWindow(&server_window_delegate, WindowId(0, 211), parent.get()); ServerWindow* w212 = CreateTestWindow(&server_window_delegate, WindowId(0, 212), parent.get()); ServerWindow* w213 = CreateTestWindow(&server_window_delegate, WindowId(0, 213), parent.get()); ServerWindow* w22 = CreateTestWindow(&server_window_delegate, WindowId(0, 22), parent.get()); ASSERT_EQ(8u, parent->children().size()); // w11 is now owned by w1. w1->AddTransientWindow(w11); // w21 is now owned by w2. w2->AddTransientWindow(w21); // w22 is now owned by w2. w2->AddTransientWindow(w22); // w211 is now owned by w21. w21->AddTransientWindow(w211); // w212 is now owned by w21. w21->AddTransientWindow(w212); // w213 is now owned by w21. w21->AddTransientWindow(w213); EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); // Stack w2 at the bottom, this should force w11 to be last (on top of w1). // This also tests that the order in children_ array rather than in // transient_children_ array is used when reinserting transient children. // If transient_children_ array was used '22' would be following '21'. parent->StackChildAtBottom(w2.get()); EXPECT_EQ(w11, parent->children().back()); EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); parent->StackChildAtBottom(w1.get()); EXPECT_EQ(w22, parent->children().back()); EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); w21->Reorder(w1.get(), mojom::ORDER_DIRECTION_BELOW); EXPECT_EQ(w11, parent->children().back()); EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); w11->Reorder(w2.get(), mojom::ORDER_DIRECTION_BELOW); EXPECT_EQ(w22, parent->children().back()); EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); w22->Reorder(w21, mojom::ORDER_DIRECTION_BELOW); EXPECT_EQ(w213, parent->children().back()); EXPECT_EQ("1 11 2 22 21 211 212 213", ChildWindowIDsAsString(parent.get())); w21->Reorder(w11, mojom::ORDER_DIRECTION_BELOW); EXPECT_EQ(w11, parent->children().back()); EXPECT_EQ("2 22 21 211 212 213 1 11", ChildWindowIDsAsString(parent.get())); w213->Reorder(w211, mojom::ORDER_DIRECTION_BELOW); EXPECT_EQ(w11, parent->children().back()); EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); // No change when stacking a transient parent below its transient child. w21->Reorder(w211, mojom::ORDER_DIRECTION_BELOW); EXPECT_EQ(w11, parent->children().back()); EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); w1->Reorder(w2.get(), mojom::ORDER_DIRECTION_BELOW); EXPECT_EQ(w212, parent->children().back()); EXPECT_EQ("1 11 2 22 21 213 211 212", ChildWindowIDsAsString(parent.get())); w213->Reorder(w11, mojom::ORDER_DIRECTION_BELOW); EXPECT_EQ(w11, parent->children().back()); EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); } // Tests that transient windows are stacked properly when created. TEST_F(TransientWindowsTest, StackUponCreation) { TestServerWindowDelegate delegate; scoped_ptr parent( CreateTestWindow(&delegate, WindowId(), nullptr)); scoped_ptr window0( CreateTestWindow(&delegate, WindowId(0, 1), parent.get())); scoped_ptr window1( CreateTestWindow(&delegate, WindowId(0, 2), parent.get())); ServerWindow* window2 = CreateTestWindow(&delegate, WindowId(0, 3), parent.get()); window0->AddTransientWindow(window2); EXPECT_EQ("1 3 2", ChildWindowIDsAsString(parent.get())); } // Tests that windows are restacked properly after a call to // AddTransientWindow() or RemoveTransientWindow(). TEST_F(TransientWindowsTest, RestackUponAddOrRemoveTransientWindow) { TestServerWindowDelegate delegate; scoped_ptr parent( CreateTestWindow(&delegate, WindowId(), nullptr)); scoped_ptr windows[4]; for (int i = 0; i < 4; i++) windows[i].reset(CreateTestWindow(&delegate, WindowId(0, i), parent.get())); EXPECT_EQ("0 1 2 3", ChildWindowIDsAsString(parent.get())); windows[0]->AddTransientWindow(windows[2].get()); EXPECT_EQ("0 2 1 3", ChildWindowIDsAsString(parent.get())); windows[0]->AddTransientWindow(windows[3].get()); EXPECT_EQ("0 2 3 1", ChildWindowIDsAsString(parent.get())); windows[0]->RemoveTransientWindow(windows[2].get()); EXPECT_EQ("0 3 2 1", ChildWindowIDsAsString(parent.get())); windows[0]->RemoveTransientWindow(windows[3].get()); EXPECT_EQ("0 3 2 1", ChildWindowIDsAsString(parent.get())); } // Verifies TransientWindowObserver is notified appropriately. TEST_F(TransientWindowsTest, TransientWindowObserverNotified) { TestServerWindowDelegate delegate; scoped_ptr parent( CreateTestWindow(&delegate, WindowId(), nullptr)); scoped_ptr w1( CreateTestWindow(&delegate, WindowId(0, 1), parent.get())); TestTransientWindowObserver test_observer; parent->AddObserver(&test_observer); parent->AddTransientWindow(w1.get()); EXPECT_EQ(1, test_observer.add_count()); EXPECT_EQ(0, test_observer.remove_count()); parent->RemoveTransientWindow(w1.get()); EXPECT_EQ(1, test_observer.add_count()); EXPECT_EQ(1, test_observer.remove_count()); parent->RemoveObserver(&test_observer); } } // namespace ws } // namespace mus