chromium/ui/views/corewm/tooltip_controller_unittest.cc

// Copyright 2013 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 <utility>

#include "base/at_exit.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/client/window_types.h"
#include "ui/aura/test/aura_test_base.h"
#include "ui/aura/test/test_window_delegate.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/font.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/render_text.h"
#include "ui/gfx/text_elider.h"
#include "ui/views/buildflags.h"
#include "ui/views/corewm/test/tooltip_aura_test_api.h"
#include "ui/views/corewm/tooltip_aura.h"
#include "ui/views/corewm/tooltip_controller_test_helper.h"
#include "ui/views/corewm/tooltip_state_manager.h"
#include "ui/views/test/desktop_test_views_delegate.h"
#include "ui/views/test/native_widget_factory.h"
#include "ui/views/test/test_views_delegate.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/view.h"
#include "ui/views/widget/tooltip_manager.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/public/activation_client.h"
#include "ui/wm/public/tooltip_observer.h"

#if BUILDFLAG(IS_WIN)
#include "ui/base/win/scoped_ole_initializer.h"
#endif

#if BUILDFLAG(ENABLE_DESKTOP_AURA)
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#endif

namespace views::corewm::test {
namespace {

#if BUILDFLAG(IS_CHROMEOS_LACROS)
class TestTooltipLacros : public Tooltip {
 public:
  TestTooltipLacros() = default;

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

  ~TestTooltipLacros() override {
    state_manager_ = nullptr;
  }

  void AddObserver(wm::TooltipObserver* observer) override {}
  void RemoveObserver(wm::TooltipObserver* observer) override {}

  const std::u16string& tooltip_text() const { return tooltip_text_; }

  // Tooltip:
  int GetMaxWidth(const gfx::Point& location) const override { return 100; }
  void Update(aura::Window* window,
              const std::u16string& tooltip_text,
              const gfx::Point& position,
              const TooltipTrigger trigger) override {
    tooltip_parent_ = window;
    tooltip_text_ = tooltip_text;
    anchor_point_ = position + window->GetBoundsInScreen().OffsetFromOrigin();
    trigger_ = trigger;
  }
  void Show() override {
    is_visible_ = true;
    DCHECK(state_manager_);
    state_manager_->OnTooltipShownOnServer(tooltip_parent_, tooltip_text_,
                                           gfx::Rect());
  }
  void Hide() override {
    is_visible_ = false;
    tooltip_parent_ = nullptr;
    DCHECK(state_manager_);
    state_manager_->OnTooltipHiddenOnServer();
  }
  bool IsVisible() override { return is_visible_; }

  void SetStateManager(TooltipStateManager* state_manager) {
    state_manager_ = state_manager;
  }

  const gfx::Point& anchor_point() { return anchor_point_; }
  TooltipTrigger trigger() { return trigger_; }

 private:
  bool is_visible_ = false;
  raw_ptr<aura::Window> tooltip_parent_ = nullptr;
  raw_ptr<TooltipStateManager> state_manager_ = nullptr;  // not owned.
  std::u16string tooltip_text_;
  gfx::Point anchor_point_;
  TooltipTrigger trigger_;
};
#endif

std::unique_ptr<views::Widget> CreateWidget(aura::Window* root) {}

}  // namespace

class TooltipControllerTest : public ViewsTestBase {};

TEST_F(TooltipControllerTest, ViewTooltip) {}

TEST_F(TooltipControllerTest, HideEmptyTooltip) {}

TEST_F(TooltipControllerTest, DontShowTooltipOnTouch) {}

#if !BUILDFLAG(ENABLE_DESKTOP_AURA) || BUILDFLAG(IS_WIN)
// crbug.com/664370.
TEST_F(TooltipControllerTest, MaxWidth) {
  std::u16string text =
      u"Really, really, really, really, really, really long tooltip that "
      u"exceeds max width";
  view_->set_tooltip_text(text);
  gfx::Point center = GetWindow()->bounds().CenterPoint();

  generator_->MoveMouseTo(center);

  EXPECT_TRUE(helper_->IsTooltipVisible());
  EXPECT_EQ(helper_->state_manager()->tooltip_trigger(),
            TooltipTrigger::kCursor);
  const gfx::RenderText* render_text =
      test::TooltipAuraTestApi(tooltip_).GetRenderText();

  int max = helper_->controller()->GetMaxWidth(center);
  EXPECT_EQ(max, render_text->display_rect().width());
}

TEST_F(TooltipControllerTest, AccessibleNodeData) {
  std::u16string text = u"Tooltip Text";
  view_->set_tooltip_text(text);
  gfx::Point center = GetWindow()->bounds().CenterPoint();

  generator_->MoveMouseTo(center);

  EXPECT_TRUE(helper_->IsTooltipVisible());
  EXPECT_EQ(helper_->state_manager()->tooltip_trigger(),
            TooltipTrigger::kCursor);
  ui::AXNodeData node_data;
  test::TooltipAuraTestApi(tooltip_).GetAccessibleNodeData(&node_data);
  EXPECT_EQ(ax::mojom::Role::kTooltip, node_data.role);
  EXPECT_EQ(text, base::ASCIIToUTF16(node_data.GetStringAttribute(
                      ax::mojom::StringAttribute::kName)));
}

TEST_F(TooltipControllerTest, TooltipBounds) {
  // We don't need a real tootip. Let's just use a custom size and custom point
  // to test this function.
  gfx::Size tooltip_size(100, 40);
  gfx::Rect display_bounds(display::Screen::GetScreen()
                               ->GetDisplayNearestPoint(gfx::Point(0, 0))
                               .bounds());
  gfx::Point anchor_point = display_bounds.CenterPoint();

  // All tests here share the same expected y value.
  int a_expected_y(anchor_point.y() + TooltipAura::kCursorOffsetY);
  int b_expected_y(anchor_point.y());

  // 1. The tooltip fits entirely in the window.
  {
    // A. When attached to the cursor, the tooltip should be positioned at the
    // bottom-right corner of the cursor.
    gfx::Rect bounds = test::TooltipAuraTestApi(tooltip_).GetTooltipBounds(
        tooltip_size, anchor_point, TooltipTrigger::kCursor);
    gfx::Point expected_position(anchor_point.x() + TooltipAura::kCursorOffsetX,
                                 a_expected_y);
    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));

    // B. When not attached to the cursor, the tooltip should be horizontally
    // centered with the anchor point.
    bounds = test::TooltipAuraTestApi(tooltip_).GetTooltipBounds(
        tooltip_size, anchor_point, TooltipTrigger::kKeyboard);
    expected_position =
        gfx::Point(anchor_point.x() - tooltip_size.width() / 2, b_expected_y);
    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));
  }
  // 2. The tooltip overflows on the left side of the window.
  {
    anchor_point = display_bounds.left_center();
    anchor_point.Offset(-TooltipAura::kCursorOffsetX - 10, 0);

    // A. When attached to the cursor, the tooltip should be positioned at the
    // bottom-right corner of the cursor.
    gfx::Rect bounds = test::TooltipAuraTestApi(tooltip_).GetTooltipBounds(
        tooltip_size, anchor_point, TooltipTrigger::kCursor);
    gfx::Point expected_position(0, a_expected_y);
    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));

    // B. When not attached to the cursor, the tooltip should be horizontally
    // centered with the anchor point.
    bounds = test::TooltipAuraTestApi(tooltip_).GetTooltipBounds(
        tooltip_size, anchor_point, TooltipTrigger::kKeyboard);
    expected_position = gfx::Point(0, b_expected_y);
    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));
  }
  // 3. The tooltip overflows on the right side of the window.
  {
    anchor_point = display_bounds.right_center();
    anchor_point.Offset(10, 0);

    // A. When attached to the cursor, the tooltip should be positioned at the
    // bottom-right corner of the cursor.
    gfx::Rect bounds = test::TooltipAuraTestApi(tooltip_).GetTooltipBounds(
        tooltip_size, anchor_point, TooltipTrigger::kCursor);
    gfx::Point expected_position(display_bounds.right() - tooltip_size.width(),
                                 a_expected_y);
    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));

    // B. When not attached to the cursor, the tooltip should be horizontally
    // centered with the anchor point.
    bounds = test::TooltipAuraTestApi(tooltip_).GetTooltipBounds(
        tooltip_size, anchor_point, TooltipTrigger::kKeyboard);
    expected_position =
        gfx::Point(display_bounds.right() - tooltip_size.width(), b_expected_y);
    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));
  }
  // 4. The tooltip overflows on the bottom.
  {
    anchor_point = display_bounds.bottom_center();

    // A. When attached to the cursor, the tooltip should be positioned at the
    // bottom-right corner of the cursor.
    gfx::Rect bounds = test::TooltipAuraTestApi(tooltip_).GetTooltipBounds(
        tooltip_size, anchor_point, TooltipTrigger::kCursor);
    gfx::Point expected_position(anchor_point.x() + TooltipAura::kCursorOffsetX,
                                 anchor_point.y() - tooltip_size.height());
    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));

    // B. When not attached to the cursor, the tooltip should be horizontally
    // centered with the anchor point.
    bounds = test::TooltipAuraTestApi(tooltip_).GetTooltipBounds(
        tooltip_size, anchor_point, TooltipTrigger::kKeyboard);
    expected_position = gfx::Point(anchor_point.x() - tooltip_size.width() / 2,
                                   anchor_point.y() - tooltip_size.height());
    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));
  }
}
#endif

TEST_F(TooltipControllerTest, TooltipsInMultipleViews) {}

TEST_F(TooltipControllerTest, EnableOrDisableTooltips) {}

// Verifies tooltip isn't shown if tooltip text consists entirely of whitespace.
TEST_F(TooltipControllerTest, DontShowEmptyTooltips) {}

// Disabled on Lacros since TooltipLacros does not have tooltip timer on client
// side so cannot be tested on unittest.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#define MAYBE_TooltipUpdateWhenTooltipDeferTimerIsRunning
#else
#define MAYBE_TooltipUpdateWhenTooltipDeferTimerIsRunning
#endif
TEST_F(TooltipControllerTest,
       MAYBE_TooltipUpdateWhenTooltipDeferTimerIsRunning) {}

TEST_F(TooltipControllerTest, TooltipHidesOnKeyPressAndStaysHiddenUntilChange) {}

TEST_F(TooltipControllerTest, TooltipStaysVisibleOnKeyRelease) {}

TEST_F(TooltipControllerTest, TooltipHidesOnTimeoutAndStaysHiddenUntilChange) {}

// Verifies a mouse exit event hides the tooltips.
TEST_F(TooltipControllerTest, HideOnExit) {}

TEST_F(TooltipControllerTest, ReshowOnClickAfterEnterExit) {}

TEST_F(TooltipControllerTest, ShowAndHideTooltipTriggeredFromKeyboard) {}

TEST_F(TooltipControllerTest,
       KeyboardTriggeredTooltipStaysVisibleOnMouseExitedEvent) {}

namespace {

// Returns the index of |window| in its parent's children.
int IndexInParent(const aura::Window* window) {}

}  // namespace

// Verifies when capture is released the TooltipController resets state.
// Flaky on all builders.  http://crbug.com/388268
TEST_F(TooltipControllerTest, DISABLED_CloseOnCaptureLost) {}

// Disabled on Linux as X11ScreenOzone::GetAcceleratedWidgetAtScreenPoint
// and WaylandScreen::GetAcceleratedWidgetAtScreenPoint don't consider z-order.
// Disabled on Windows due to failing bots. http://crbug.com/604479
// Disabled on Lacros due to crash and flakiness.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS_LACROS)
#define MAYBE_Capture
#else
#define MAYBE_Capture
#endif
// Verifies the correct window is found for tooltips when there is a capture.
TEST_F(TooltipControllerTest, MAYBE_Capture) {}

TEST_F(TooltipControllerTest, ShowTooltipOnTooltipTextUpdate) {}

// Disabled on Lacros since TooltipLacros does not have tooltip timer on client
// side so cannot be tested on unittest.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#define MAYBE_TooltipPositionUpdatedWhenTimerRunning
#else
#define MAYBE_TooltipPositionUpdatedWhenTimerRunning
#endif
// This test validates that the TooltipController correctly triggers a position
// update for a tooltip that is about to be shown.
TEST_F(TooltipControllerTest, MAYBE_TooltipPositionUpdatedWhenTimerRunning) {}

// This test validates that tooltips are hidden when the currently active window
// loses focus to another window.
TEST_F(TooltipControllerTest, TooltipHiddenWhenWindowDeactivated) {}

namespace {

#if BUILDFLAG(IS_CHROMEOS_LACROS)
using TestTooltip = TestTooltipLacros;
#else
class TestTooltip : public Tooltip {};
#endif

}  // namespace

// Use for tests that don't depend upon views.
class TooltipControllerTest2 : public aura::test::AuraTestBase {};

TEST_F(TooltipControllerTest2, VerifyLeadingTrailingWhitespaceStripped) {}

// Verifies that tooltip is hidden and tooltip window closed upon cancel mode.
TEST_F(TooltipControllerTest2, CloseOnCancelMode) {}

// Use for tests that need both views and a TestTooltip.
class TooltipControllerTest3 : public ViewsTestBase {};

TEST_F(TooltipControllerTest3, TooltipPositionChangesOnTwoViewsWithSameLabel) {}

class TooltipStateManagerTest : public TooltipControllerTest {};

TEST_F(TooltipStateManagerTest, ShowAndHideTooltip) {}

// Disabled on Lacros since TooltipLacros cannot handle tooltip with delay on
// client side properly. To test with delay, it needs to use Ash server with
// ui_controls in interactive_ui_tests.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#define MAYBE_ShowTooltipWithDelay
#else
#define MAYBE_ShowTooltipWithDelay
#endif
TEST_F(TooltipStateManagerTest, MAYBE_ShowTooltipWithDelay) {}

// Disabled on Lacros since TooltipLacros cannot handle tooltip with delay on
// client side properly. To test with delay, it needs to use Ash server with
// ui_controls in interactive_ui_tests.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#define MAYBE_UpdatePositionIfNeeded
#else
#define MAYBE_UpdatePositionIfNeeded
#endif
// This test ensures that we can update the position of the tooltip after the
// |will_show_tooltip_timer_| has been started. This is needed because the
// cursor might still move between the moment Show is called and the timer
// fires.
TEST_F(TooltipStateManagerTest, MAYBE_UpdatePositionIfNeeded) {}

}  // namespace views::corewm::test