1// Copyright (c) 2013 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 "ui/views/corewm/tooltip_controller.h"
6
7#include "base/strings/utf_string_conversions.h"
8#include "ui/aura/client/cursor_client.h"
9#include "ui/aura/client/tooltip_client.h"
10#include "ui/aura/env.h"
11#include "ui/aura/root_window.h"
12#include "ui/aura/test/aura_test_base.h"
13#include "ui/aura/test/event_generator.h"
14#include "ui/aura/window.h"
15#include "ui/base/resource/resource_bundle.h"
16#include "ui/base/text/text_elider.h"
17#include "ui/gfx/font.h"
18#include "ui/gfx/point.h"
19#include "ui/views/corewm/tooltip_controller_test_helper.h"
20#include "ui/views/view.h"
21#include "ui/views/widget/widget.h"
22
23#if defined(OS_WIN)
24#include "ui/base/win/scoped_ole_initializer.h"
25#endif
26#if !defined(OS_CHROMEOS)
27#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
28#endif
29
30namespace views {
31namespace corewm {
32namespace test {
33namespace {
34
35views::Widget* CreateWidget(aura::RootWindow* root) {
36  views::Widget* widget = new views::Widget;
37  views::Widget::InitParams params;
38  params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
39  params.accept_events = true;
40  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
41#if defined(OS_CHROMEOS)
42  params.parent = root;
43#else
44  params.native_widget = new DesktopNativeWidgetAura(widget);
45#endif
46  params.bounds = gfx::Rect(0, 0, 200, 100);
47  widget->Init(params);
48  widget->Show();
49  return widget;
50}
51
52gfx::Font GetDefaultFont() {
53  return ui::ResourceBundle::GetSharedInstance().GetFont(
54      ui::ResourceBundle::BaseFont);
55}
56
57TooltipController* GetController(Widget* widget) {
58  return static_cast<TooltipController*>(
59      aura::client::GetTooltipClient(
60          widget->GetNativeWindow()->GetRootWindow()));
61}
62
63}  // namespace
64
65class TooltipControllerTest : public aura::test::AuraTestBase {
66 public:
67  TooltipControllerTest() : view_(NULL) {}
68  virtual ~TooltipControllerTest() {}
69
70  virtual void SetUp() OVERRIDE {
71    aura::test::AuraTestBase::SetUp();
72#if defined(OS_CHROMEOS)
73    controller_.reset(new TooltipController(gfx::SCREEN_TYPE_ALTERNATE));
74    root_window()->AddPreTargetHandler(controller_.get());
75    SetTooltipClient(root_window(), controller_.get());
76#endif
77    widget_.reset(CreateWidget(root_window()));
78    widget_->SetContentsView(new View);
79    view_ = new TooltipTestView;
80    widget_->GetContentsView()->AddChildView(view_);
81    view_->SetBoundsRect(widget_->GetContentsView()->GetLocalBounds());
82    helper_.reset(new TooltipControllerTestHelper(
83                      GetController(widget_.get())));
84    generator_.reset(new aura::test::EventGenerator(GetRootWindow()));
85  }
86
87  virtual void TearDown() OVERRIDE {
88#if defined(OS_CHROMEOS)
89    root_window()->RemovePreTargetHandler(controller_.get());
90    SetTooltipClient(root_window(), NULL);
91    controller_.reset();
92#endif
93    generator_.reset();
94    helper_.reset();
95    widget_.reset();
96    aura::test::AuraTestBase::TearDown();
97  }
98
99 protected:
100  aura::Window* GetWindow() {
101    return widget_->GetNativeWindow();
102  }
103
104  aura::RootWindow* GetRootWindow() {
105    return GetWindow()->GetRootWindow();
106  }
107
108  TooltipTestView* PrepareSecondView() {
109    TooltipTestView* view2 = new TooltipTestView;
110    widget_->GetContentsView()->AddChildView(view2);
111    view_->SetBounds(0, 0, 100, 100);
112    view2->SetBounds(100, 0, 100, 100);
113    return view2;
114  }
115
116  scoped_ptr<views::Widget> widget_;
117  TooltipTestView* view_;
118  scoped_ptr<TooltipControllerTestHelper> helper_;
119  scoped_ptr<aura::test::EventGenerator> generator_;
120
121 private:
122  scoped_ptr<TooltipController> controller_;
123#if defined(OS_WIN)
124  ui::ScopedOleInitializer ole_initializer_;
125#endif
126
127  DISALLOW_COPY_AND_ASSIGN(TooltipControllerTest);
128};
129
130TEST_F(TooltipControllerTest, ViewTooltip) {
131  view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text"));
132  EXPECT_EQ(string16(), helper_->GetTooltipText());
133  EXPECT_EQ(NULL, helper_->GetTooltipWindow());
134  generator_->MoveMouseToCenterOf(GetWindow());
135
136  EXPECT_EQ(GetWindow(), GetRootWindow()->GetEventHandlerForPoint(
137      generator_->current_location()));
138  string16 expected_tooltip = ASCIIToUTF16("Tooltip Text");
139  EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(GetWindow()));
140  EXPECT_EQ(string16(), helper_->GetTooltipText());
141  EXPECT_EQ(GetWindow(), helper_->GetTooltipWindow());
142
143  // Fire tooltip timer so tooltip becomes visible.
144  helper_->FireTooltipTimer();
145
146  EXPECT_TRUE(helper_->IsTooltipVisible());
147  generator_->MoveMouseBy(1, 0);
148
149  EXPECT_TRUE(helper_->IsTooltipVisible());
150  EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(GetWindow()));
151  EXPECT_EQ(expected_tooltip, helper_->GetTooltipText());
152  EXPECT_EQ(GetWindow(), helper_->GetTooltipWindow());
153}
154
155TEST_F(TooltipControllerTest, TooltipsInMultipleViews) {
156  view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text"));
157  EXPECT_EQ(string16(), helper_->GetTooltipText());
158  EXPECT_EQ(NULL, helper_->GetTooltipWindow());
159
160  PrepareSecondView();
161  aura::Window* window = GetWindow();
162  aura::RootWindow* root_window = GetRootWindow();
163
164  // Fire tooltip timer so tooltip becomes visible.
165  generator_->MoveMouseRelativeTo(window, view_->bounds().CenterPoint());
166  helper_->FireTooltipTimer();
167  EXPECT_TRUE(helper_->IsTooltipVisible());
168  for (int i = 0; i < 49; ++i) {
169    generator_->MoveMouseBy(1, 0);
170    EXPECT_TRUE(helper_->IsTooltipVisible());
171    EXPECT_EQ(window, root_window->GetEventHandlerForPoint(
172            generator_->current_location()));
173    string16 expected_tooltip = ASCIIToUTF16("Tooltip Text");
174    EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(window));
175    EXPECT_EQ(expected_tooltip, helper_->GetTooltipText());
176    EXPECT_EQ(window, helper_->GetTooltipWindow());
177  }
178  for (int i = 0; i < 49; ++i) {
179    generator_->MoveMouseBy(1, 0);
180    EXPECT_FALSE(helper_->IsTooltipVisible());
181    EXPECT_EQ(window, root_window->GetEventHandlerForPoint(
182            generator_->current_location()));
183    string16 expected_tooltip;  // = ""
184    EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(window));
185    EXPECT_EQ(expected_tooltip, helper_->GetTooltipText());
186    EXPECT_EQ(window, helper_->GetTooltipWindow());
187  }
188}
189
190TEST_F(TooltipControllerTest, EnableOrDisableTooltips) {
191  view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text"));
192  EXPECT_EQ(string16(), helper_->GetTooltipText());
193  EXPECT_EQ(NULL, helper_->GetTooltipWindow());
194
195  generator_->MoveMouseRelativeTo(GetWindow(), view_->bounds().CenterPoint());
196  string16 expected_tooltip = ASCIIToUTF16("Tooltip Text");
197
198  // Fire tooltip timer so tooltip becomes visible.
199  helper_->FireTooltipTimer();
200  EXPECT_TRUE(helper_->IsTooltipVisible());
201
202  // Diable tooltips and check again.
203  helper_->controller()->SetTooltipsEnabled(false);
204  EXPECT_FALSE(helper_->IsTooltipVisible());
205  helper_->FireTooltipTimer();
206  EXPECT_FALSE(helper_->IsTooltipVisible());
207
208  // Enable tooltips back and check again.
209  helper_->controller()->SetTooltipsEnabled(true);
210  EXPECT_FALSE(helper_->IsTooltipVisible());
211  helper_->FireTooltipTimer();
212  EXPECT_TRUE(helper_->IsTooltipVisible());
213}
214
215TEST_F(TooltipControllerTest, TrimTooltipToFitTests) {
216  const int max_width = 4000;
217  string16 tooltip;
218  int width, line_count, expect_lines;
219  int max_pixel_width = 400;  // copied from constants in tooltip_controller.cc
220  int max_lines = 10;  // copied from constants in tooltip_controller.cc
221  gfx::Font font = GetDefaultFont();
222  size_t tooltip_len;
223
224  // Error in computed size vs. expected size should not be greater than the
225  // size of the longest word.
226  int error_in_pixel_width = font.GetStringWidth(ASCIIToUTF16("tooltip"));
227
228  // Long tooltips should wrap to next line
229  tooltip.clear();
230  width = line_count = -1;
231  expect_lines = 3;
232  for (; font.GetStringWidth(tooltip) <= (expect_lines - 1) * max_pixel_width;)
233    tooltip.append(ASCIIToUTF16("This is part of the tooltip"));
234  tooltip_len = tooltip.length();
235  TooltipControllerTestHelper::TrimTooltipToFit(
236      max_width, &tooltip, &width, &line_count);
237  EXPECT_NEAR(max_pixel_width, width, error_in_pixel_width);
238  EXPECT_EQ(expect_lines, line_count);
239  EXPECT_EQ(tooltip_len + expect_lines - 1, tooltip.length());
240
241  // More than |max_lines| lines should get truncated at 10 lines.
242  tooltip.clear();
243  width = line_count = -1;
244  expect_lines = 13;
245  for (; font.GetStringWidth(tooltip) <= (expect_lines - 1) * max_pixel_width;)
246    tooltip.append(ASCIIToUTF16("This is part of the tooltip"));
247  TooltipControllerTestHelper::TrimTooltipToFit(
248      max_width, &tooltip, &width, &line_count);
249  EXPECT_NEAR(max_pixel_width, width, error_in_pixel_width);
250  EXPECT_EQ(max_lines, line_count);
251
252  // Long multi line tooltips should wrap individual lines.
253  tooltip.clear();
254  width = line_count = -1;
255  expect_lines = 4;
256  for (; font.GetStringWidth(tooltip) <= (expect_lines - 2) * max_pixel_width;)
257    tooltip.append(ASCIIToUTF16("This is part of the tooltip"));
258  tooltip.insert(tooltip.length() / 2, ASCIIToUTF16("\n"));
259  tooltip_len = tooltip.length();
260  TooltipControllerTestHelper::TrimTooltipToFit(
261      max_width, &tooltip, &width, &line_count);
262  EXPECT_NEAR(max_pixel_width, width, error_in_pixel_width);
263  EXPECT_EQ(expect_lines, line_count);
264  // We may have inserted the line break above near a space which will get
265  // trimmed. Hence we may be off by 1 in the final tooltip length calculation.
266  EXPECT_NEAR(tooltip_len + expect_lines - 2, tooltip.length(), 1);
267
268#if !defined(OS_WIN)
269  // Tooltip with really long word gets elided.
270  tooltip.clear();
271  width = line_count = -1;
272  tooltip = UTF8ToUTF16(std::string('a', max_pixel_width));
273  TooltipControllerTestHelper::TrimTooltipToFit(
274      max_width, &tooltip, &width, &line_count);
275  EXPECT_NEAR(max_pixel_width, width, 5);
276  EXPECT_EQ(1, line_count);
277  EXPECT_EQ(ui::ElideText(UTF8ToUTF16(std::string('a', max_pixel_width)), font,
278                          max_pixel_width, ui::ELIDE_AT_END), tooltip);
279#endif
280
281  // Normal small tooltip should stay as is.
282  tooltip.clear();
283  width = line_count = -1;
284  tooltip = ASCIIToUTF16("Small Tooltip");
285  TooltipControllerTestHelper::TrimTooltipToFit(
286      max_width, &tooltip, &width, &line_count);
287  EXPECT_EQ(font.GetStringWidth(ASCIIToUTF16("Small Tooltip")), width);
288  EXPECT_EQ(1, line_count);
289  EXPECT_EQ(ASCIIToUTF16("Small Tooltip"), tooltip);
290
291  // Normal small multi-line tooltip should stay as is.
292  tooltip.clear();
293  width = line_count = -1;
294  tooltip = ASCIIToUTF16("Multi line\nTooltip");
295  TooltipControllerTestHelper::TrimTooltipToFit(
296      max_width, &tooltip, &width, &line_count);
297  int expected_width = font.GetStringWidth(ASCIIToUTF16("Multi line"));
298  expected_width = std::max(expected_width,
299                            font.GetStringWidth(ASCIIToUTF16("Tooltip")));
300  EXPECT_EQ(expected_width, width);
301  EXPECT_EQ(2, line_count);
302  EXPECT_EQ(ASCIIToUTF16("Multi line\nTooltip"), tooltip);
303
304  // Whitespaces in tooltips are preserved.
305  tooltip.clear();
306  width = line_count = -1;
307  tooltip = ASCIIToUTF16("Small  Tool  t\tip");
308  TooltipControllerTestHelper::TrimTooltipToFit(
309      max_width, &tooltip, &width, &line_count);
310  EXPECT_EQ(font.GetStringWidth(ASCIIToUTF16("Small  Tool  t\tip")), width);
311  EXPECT_EQ(1, line_count);
312  EXPECT_EQ(ASCIIToUTF16("Small  Tool  t\tip"), tooltip);
313}
314
315TEST_F(TooltipControllerTest, TooltipHidesOnKeyPressAndStaysHiddenUntilChange) {
316  view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text for view 1"));
317  EXPECT_EQ(string16(), helper_->GetTooltipText());
318  EXPECT_EQ(NULL, helper_->GetTooltipWindow());
319
320  TooltipTestView* view2 = PrepareSecondView();
321  view2->set_tooltip_text(ASCIIToUTF16("Tooltip Text for view 2"));
322
323  aura::Window* window = GetWindow();
324
325  // Fire tooltip timer so tooltip becomes visible.
326  generator_->MoveMouseRelativeTo(window, view_->bounds().CenterPoint());
327  helper_->FireTooltipTimer();
328  EXPECT_TRUE(helper_->IsTooltipVisible());
329  EXPECT_TRUE(helper_->IsTooltipShownTimerRunning());
330
331  generator_->PressKey(ui::VKEY_1, 0);
332  EXPECT_FALSE(helper_->IsTooltipVisible());
333  EXPECT_FALSE(helper_->IsTooltipTimerRunning());
334  EXPECT_FALSE(helper_->IsTooltipShownTimerRunning());
335
336  // Moving the mouse inside |view1| should not change the state of the tooltip
337  // or the timers.
338  for (int i = 0; i < 49; i++) {
339    generator_->MoveMouseBy(1, 0);
340    EXPECT_FALSE(helper_->IsTooltipVisible());
341    EXPECT_FALSE(helper_->IsTooltipTimerRunning());
342    EXPECT_FALSE(helper_->IsTooltipShownTimerRunning());
343    EXPECT_EQ(window,
344              GetRootWindow()->GetEventHandlerForPoint(
345                  generator_->current_location()));
346    string16 expected_tooltip = ASCIIToUTF16("Tooltip Text for view 1");
347    EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(window));
348    EXPECT_EQ(expected_tooltip, helper_->GetTooltipText());
349    EXPECT_EQ(window, helper_->GetTooltipWindow());
350  }
351
352  // Now we move the mouse on to |view2|. It should re-start the tooltip timer.
353  generator_->MoveMouseBy(1, 0);
354  EXPECT_TRUE(helper_->IsTooltipTimerRunning());
355  helper_->FireTooltipTimer();
356  EXPECT_TRUE(helper_->IsTooltipVisible());
357  EXPECT_TRUE(helper_->IsTooltipShownTimerRunning());
358  string16 expected_tooltip = ASCIIToUTF16("Tooltip Text for view 2");
359  EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(window));
360  EXPECT_EQ(expected_tooltip, helper_->GetTooltipText());
361  EXPECT_EQ(window, helper_->GetTooltipWindow());
362}
363
364TEST_F(TooltipControllerTest, TooltipHidesOnTimeoutAndStaysHiddenUntilChange) {
365  view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text for view 1"));
366  EXPECT_EQ(string16(), helper_->GetTooltipText());
367  EXPECT_EQ(NULL, helper_->GetTooltipWindow());
368
369  TooltipTestView* view2 = PrepareSecondView();
370  view2->set_tooltip_text(ASCIIToUTF16("Tooltip Text for view 2"));
371
372  aura::Window* window = GetWindow();
373
374  // Fire tooltip timer so tooltip becomes visible.
375  generator_->MoveMouseRelativeTo(window, view_->bounds().CenterPoint());
376  helper_->FireTooltipTimer();
377  EXPECT_TRUE(helper_->IsTooltipVisible());
378  EXPECT_TRUE(helper_->IsTooltipShownTimerRunning());
379
380  helper_->FireTooltipShownTimer();
381  EXPECT_FALSE(helper_->IsTooltipVisible());
382  EXPECT_FALSE(helper_->IsTooltipTimerRunning());
383  EXPECT_FALSE(helper_->IsTooltipShownTimerRunning());
384
385  // Moving the mouse inside |view1| should not change the state of the tooltip
386  // or the timers.
387  for (int i = 0; i < 49; ++i) {
388    generator_->MoveMouseBy(1, 0);
389    EXPECT_FALSE(helper_->IsTooltipVisible());
390    EXPECT_FALSE(helper_->IsTooltipTimerRunning());
391    EXPECT_FALSE(helper_->IsTooltipShownTimerRunning());
392    EXPECT_EQ(window, GetRootWindow()->GetEventHandlerForPoint(
393                  generator_->current_location()));
394    string16 expected_tooltip = ASCIIToUTF16("Tooltip Text for view 1");
395    EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(window));
396    EXPECT_EQ(expected_tooltip, helper_->GetTooltipText());
397    EXPECT_EQ(window, helper_->GetTooltipWindow());
398  }
399
400  // Now we move the mouse on to |view2|. It should re-start the tooltip timer.
401  generator_->MoveMouseBy(1, 0);
402  EXPECT_TRUE(helper_->IsTooltipTimerRunning());
403  helper_->FireTooltipTimer();
404  EXPECT_TRUE(helper_->IsTooltipVisible());
405  EXPECT_TRUE(helper_->IsTooltipShownTimerRunning());
406  string16 expected_tooltip = ASCIIToUTF16("Tooltip Text for view 2");
407  EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(window));
408  EXPECT_EQ(expected_tooltip, helper_->GetTooltipText());
409  EXPECT_EQ(window, helper_->GetTooltipWindow());
410}
411
412}  // namespace test
413}  // namespace corewm
414}  // namespace views
415