chromium/ash/tooltips/tooltip_controller_unittest.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/corewm/tooltip_controller.h"

#include <memory>

#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/desks/desks_util.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/font.h"
#include "ui/gfx/geometry/point.h"
#include "ui/views/corewm/tooltip_controller_test_helper.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/public/tooltip_client.h"

using views::corewm::TooltipController;
using views::corewm::TooltipTrigger;
using views::corewm::test::TooltipControllerTestHelper;
using views::corewm::test::TooltipTestView;

// The tests in this file exercise bits of TooltipController that are hard to
// test outside of ash. Meaning these tests require the shell and related things
// to be installed.

namespace ash {

namespace {

views::Widget* CreateNewWidgetWithBoundsOn(int display,
                                           const gfx::Rect& bounds) {
  views::Widget* widget = new views::Widget;
  views::Widget::InitParams params(
      views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  params.accept_events = true;
  params.parent =
      Shell::Get()->GetContainer(Shell::GetAllRootWindows().at(display),
                                 desks_util::GetActiveDeskContainerId());
  params.bounds = bounds;
  widget->Init(std::move(params));
  widget->Show();
  return widget;
}

views::Widget* CreateNewWidgetOn(int display) {
  return CreateNewWidgetWithBoundsOn(display, gfx::Rect());
}

void AddViewToWidgetAndResize(views::Widget* widget, views::View* view) {
  if (!widget->GetContentsView())
    widget->SetContentsView(std::make_unique<views::View>());

  views::View* contents_view = widget->GetContentsView();
  contents_view->AddChildView(view);
  view->SetBounds(contents_view->width(), 0, 100, 100);
  gfx::Rect contents_view_bounds = contents_view->bounds();
  contents_view_bounds.Union(view->bounds());
  contents_view->SetBoundsRect(contents_view_bounds);
  widget->SetBounds(gfx::Rect(widget->GetWindowBoundsInScreen().origin(),
                              contents_view_bounds.size()));
}

}  // namespace

class TooltipControllerTest : public AshTestBase {
 public:
  TooltipControllerTest() = default;

  TooltipControllerTest(const TooltipControllerTest&) = delete;
  TooltipControllerTest& operator=(const TooltipControllerTest&) = delete;

  ~TooltipControllerTest() override = default;

  void SetUp() override {
    AshTestBase::SetUp();
    helper_ = std::make_unique<TooltipControllerTestHelper>(
        Shell::GetPrimaryRootWindow());
  }

 protected:
  std::unique_ptr<TooltipControllerTestHelper> helper_;
};

TEST_F(TooltipControllerTest, NonNullTooltipClient) {
  EXPECT_TRUE(::wm::GetTooltipClient(Shell::GetPrimaryRootWindow()) != NULL);
  EXPECT_EQ(std::u16string(), helper_->GetTooltipText());
  EXPECT_EQ(NULL, helper_->GetTooltipParentWindow());
  EXPECT_FALSE(helper_->IsTooltipVisible());
}

TEST_F(TooltipControllerTest, HideTooltipWhenCursorHidden) {
  std::unique_ptr<views::Widget> widget(CreateNewWidgetOn(0));
  TooltipTestView* view = new TooltipTestView;
  AddViewToWidgetAndResize(widget.get(), view);
  view->set_tooltip_text(u"Tooltip Text");
  EXPECT_EQ(std::u16string(), helper_->GetTooltipText());
  EXPECT_EQ(NULL, helper_->GetTooltipParentWindow());

  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
  generator.MoveMouseRelativeTo(widget->GetNativeView(),
                                view->bounds().CenterPoint());
  std::u16string expected_tooltip = u"Tooltip Text";

  // Mouse event triggers tooltip update so it becomes visible.
  EXPECT_TRUE(helper_->IsTooltipVisible());

  // Disable mouse event which hides the cursor and check again.
  Shell::Get()->cursor_manager()->DisableMouseEvents();
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(Shell::Get()->cursor_manager()->IsCursorVisible());
  helper_->UpdateIfRequired(TooltipTrigger::kCursor);
  EXPECT_FALSE(helper_->IsTooltipVisible());

  // Enable mouse event which shows the cursor and re-check.
  Shell::Get()->cursor_manager()->EnableMouseEvents();
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(Shell::Get()->cursor_manager()->IsCursorVisible());
  generator.MoveMouseBy(0, 1);
  helper_->UpdateIfRequired(TooltipTrigger::kCursor);
  EXPECT_TRUE(helper_->IsTooltipVisible());
}

TEST_F(TooltipControllerTest, TooltipsOnMultiDisplayShouldNotCrash) {
  UpdateDisplay("1000x600,600x400");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  std::unique_ptr<views::Widget> widget1(
      CreateNewWidgetWithBoundsOn(0, gfx::Rect(10, 10, 100, 100)));
  TooltipTestView* view1 = new TooltipTestView;
  AddViewToWidgetAndResize(widget1.get(), view1);
  view1->set_tooltip_text(u"Tooltip Text for view 1");
  EXPECT_EQ(widget1->GetNativeView()->GetRootWindow(), root_windows[0]);

  std::unique_ptr<views::Widget> widget2(
      CreateNewWidgetWithBoundsOn(1, gfx::Rect(1200, 10, 100, 100)));
  TooltipTestView* view2 = new TooltipTestView;
  AddViewToWidgetAndResize(widget2.get(), view2);
  view2->set_tooltip_text(u"Tooltip Text for view 2");
  EXPECT_EQ(widget2->GetNativeView()->GetRootWindow(), root_windows[1]);

  // Show tooltip on second display.
  ui::test::EventGenerator generator(root_windows[1]);
  generator.MoveMouseRelativeTo(widget2->GetNativeView(),
                                view2->bounds().CenterPoint());
  EXPECT_TRUE(helper_->IsTooltipVisible());

  // Get rid of secondary display. This destroy's the tooltip's aura window. If
  // we have handled this case, we will not crash in the following statement.
  UpdateDisplay("1000x600");
  EXPECT_FALSE(helper_->IsTooltipVisible());
  EXPECT_EQ(widget2->GetNativeView()->GetRootWindow(), root_windows[0]);

  // The tooltip should create a new aura window for itself, so we should still
  // be able to show tooltips on the primary display.
  ui::test::EventGenerator generator1(root_windows[0]);
  generator1.MoveMouseRelativeTo(widget1->GetNativeView(),
                                 view1->bounds().CenterPoint());
  EXPECT_TRUE(helper_->IsTooltipVisible());
}

TEST_F(TooltipControllerTest, HideTooltipWhenViewHidden) {
  std::unique_ptr<views::Widget> widget(CreateNewWidgetOn(0));
  TooltipTestView* view = new TooltipTestView;
  AddViewToWidgetAndResize(widget.get(), view);
  view->set_tooltip_text(u"Tooltip Text");
  EXPECT_EQ(std::u16string(), helper_->GetTooltipText());
  EXPECT_EQ(nullptr, helper_->GetTooltipParentWindow());

  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
  generator.MoveMouseRelativeTo(widget->GetNativeView(),
                                view->bounds().CenterPoint());

  // Mouse event triggers tooltip update so it becomes visible.
  EXPECT_TRUE(helper_->IsTooltipVisible());

  view->SetVisible(false);
  EXPECT_FALSE(helper_->IsTooltipVisible());
}

}  // namespace ash