1 // Copyright (c) 2012 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/controls/label.h"
6 
7 #include <stddef.h>
8 
9 #include <string>
10 #include <utility>
11 #include <vector>
12 
13 #include "base/command_line.h"
14 #include "base/i18n/rtl.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "build/build_config.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include "ui/accessibility/ax_enums.mojom.h"
19 #include "ui/accessibility/ax_node_data.h"
20 #include "ui/base/clipboard/clipboard.h"
21 #include "ui/base/l10n/l10n_util.h"
22 #include "ui/base/ui_base_switches.h"
23 #include "ui/compositor/canvas_painter.h"
24 #include "ui/events/base_event_utils.h"
25 #include "ui/events/test/event_generator.h"
26 #include "ui/gfx/canvas.h"
27 #include "ui/gfx/render_text.h"
28 #include "ui/gfx/text_constants.h"
29 #include "ui/gfx/text_elider.h"
30 #include "ui/strings/grit/ui_strings.h"
31 #include "ui/views/border.h"
32 #include "ui/views/controls/link.h"
33 #include "ui/views/style/typography.h"
34 #include "ui/views/test/focus_manager_test.h"
35 #include "ui/views/test/views_test_base.h"
36 #include "ui/views/widget/widget.h"
37 #include "ui/views/widget/widget_utils.h"
38 
39 using base::ASCIIToUTF16;
40 using base::WideToUTF16;
41 
42 #define EXPECT_STR_EQ(ascii, utf16) EXPECT_EQ(ASCIIToUTF16(ascii), utf16)
43 
44 namespace views {
45 
46 namespace {
47 
48 #if defined(OS_MACOSX)
49 const int kControlCommandModifier = ui::EF_COMMAND_DOWN;
50 #else
51 const int kControlCommandModifier = ui::EF_CONTROL_DOWN;
52 #endif
53 
54 // All text sizing measurements (width and height) should be greater than this.
55 const int kMinTextDimension = 4;
56 
57 class TestLabel : public Label {
58  public:
TestLabel()59   TestLabel() : Label(ASCIIToUTF16("TestLabel")) { SizeToPreferredSize(); }
60 
schedule_paint_count() const61   int schedule_paint_count() const { return schedule_paint_count_; }
62 
SimulatePaint()63   void SimulatePaint() {
64     SkBitmap bitmap;
65     SkColor color = SK_ColorTRANSPARENT;
66     Paint(PaintInfo::CreateRootPaintInfo(
67         ui::CanvasPainter(&bitmap, bounds().size(), 1.f, color, false)
68             .context(),
69         bounds().size()));
70   }
71 
72   // View:
OnDidSchedulePaint(const gfx::Rect & r)73   void OnDidSchedulePaint(const gfx::Rect& r) override {
74     ++schedule_paint_count_;
75     Label::OnDidSchedulePaint(r);
76   }
77 
78  private:
79   int schedule_paint_count_ = 0;
80 
81   DISALLOW_COPY_AND_ASSIGN(TestLabel);
82 };
83 
84 // A test utility function to set the application default text direction.
SetRTL(bool rtl)85 void SetRTL(bool rtl) {
86   // Override the current locale/direction.
87   base::i18n::SetICUDefaultLocale(rtl ? "he" : "en");
88   EXPECT_EQ(rtl, base::i18n::IsRTL());
89 }
90 
91 // Returns true if |current| is bigger than |last|. Sets |last| to |current|.
Increased(int current,int * last)92 bool Increased(int current, int* last) {
93   bool increased = current > *last;
94   *last = current;
95   return increased;
96 }
97 
GetClipboardText(ui::ClipboardBuffer clipboard_buffer)98 base::string16 GetClipboardText(ui::ClipboardBuffer clipboard_buffer) {
99   base::string16 clipboard_text;
100   ui::Clipboard::GetForCurrentThread()->ReadText(clipboard_buffer,
101                                                  &clipboard_text);
102   return clipboard_text;
103 }
104 
105 // Makes an RTL string by mapping 0..6 to [א,ב,ג,ד,ה,ו,ז].
ToRTL(const char * ascii)106 base::string16 ToRTL(const char* ascii) {
107   base::string16 rtl;
108   for (const char* c = ascii; *c; ++c) {
109     if (*c >= '0' && *c <= '6')
110       rtl += L'\x5d0' + (*c - '0');
111     else
112       rtl += static_cast<base::string16::value_type>(*c);
113   }
114   return rtl;
115 }
116 
117 }  // namespace
118 
119 class LabelTest : public ViewsTestBase {
120  public:
121   LabelTest() = default;
122 
123   // ViewsTestBase:
SetUp()124   void SetUp() override {
125     ViewsTestBase::SetUp();
126 
127     Widget::InitParams params =
128         CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
129     params.bounds = gfx::Rect(200, 200);
130     params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
131     widget_.Init(std::move(params));
132     View* container = new View();
133     widget_.SetContentsView(container);
134 
135     label_ = new Label();
136     container->AddChildView(label_);
137 
138     widget_.Show();
139   }
140 
TearDown()141   void TearDown() override {
142     widget_.Close();
143     ViewsTestBase::TearDown();
144   }
145 
146  protected:
label()147   Label* label() { return label_; }
148 
widget()149   Widget* widget() { return &widget_; }
150 
151  private:
152   Label* label_ = nullptr;
153   Widget widget_;
154 
155   DISALLOW_COPY_AND_ASSIGN(LabelTest);
156 };
157 
158 // Test fixture for text selection related tests.
159 class LabelSelectionTest : public LabelTest {
160  public:
161   // Alias this long identifier for more readable tests.
162   static constexpr bool kExtends =
163       gfx::RenderText::kDragToEndIfOutsideVerticalBounds;
164 
165   // Some tests use cardinal directions to index an array of points above and
166   // below the label in either visual direction.
167   enum { NW, NORTH, NE, SE, SOUTH, SW };
168 
169   LabelSelectionTest() = default;
170 
171   // LabelTest overrides:
SetUp()172   void SetUp() override {
173     LabelTest::SetUp();
174     event_generator_ =
175         std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget()));
176   }
177 
178  protected:
GetFocusedView()179   View* GetFocusedView() {
180     return widget()->GetFocusManager()->GetFocusedView();
181   }
182 
PerformMousePress(const gfx::Point & point)183   void PerformMousePress(const gfx::Point& point) {
184     ui::MouseEvent pressed_event = ui::MouseEvent(
185         ui::ET_MOUSE_PRESSED, point, point, ui::EventTimeForNow(),
186         ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
187     label()->OnMousePressed(pressed_event);
188   }
189 
PerformMouseRelease(const gfx::Point & point)190   void PerformMouseRelease(const gfx::Point& point) {
191     ui::MouseEvent released_event = ui::MouseEvent(
192         ui::ET_MOUSE_RELEASED, point, point, ui::EventTimeForNow(),
193         ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
194     label()->OnMouseReleased(released_event);
195   }
196 
PerformClick(const gfx::Point & point)197   void PerformClick(const gfx::Point& point) {
198     PerformMousePress(point);
199     PerformMouseRelease(point);
200   }
201 
PerformMouseDragTo(const gfx::Point & point)202   void PerformMouseDragTo(const gfx::Point& point) {
203     ui::MouseEvent drag(ui::ET_MOUSE_DRAGGED, point, point,
204                         ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, 0);
205     label()->OnMouseDragged(drag);
206   }
207 
208   // Used to force layout on the underlying RenderText instance.
SimulatePaint()209   void SimulatePaint() {
210     gfx::Canvas canvas;
211     label()->OnPaint(&canvas);
212   }
213 
GetCursorPoint(int index)214   gfx::Point GetCursorPoint(int index) {
215     SimulatePaint();
216     gfx::RenderText* render_text =
217         label()->GetRenderTextForSelectionController();
218     const gfx::Range range(index, index + 1);
219     const std::vector<gfx::Rect> bounds =
220         render_text->GetSubstringBounds(range);
221     DCHECK_EQ(1u, bounds.size());
222     const int mid_y = bounds[0].y() + bounds[0].height() / 2;
223 
224     // For single-line text, use the glyph bounds since it gives a better
225     // representation of the midpoint between glyphs when considering selection.
226     // TODO(tapted): When GetCursorSpan() supports returning a vertical range
227     // as well as a horizontal range, just use that here.
228     if (!render_text->multiline()) {
229       return gfx::Point(render_text->GetCursorSpan(range).Round().start(),
230                         mid_y);
231     }
232 
233     // Otherwise, GetCursorSpan() will give incorrect results. Multiline
234     // editing is not supported (http://crbug.com/248597) so there hasn't been
235     // a need to draw a cursor. Instead, derive a point from the selection
236     // bounds, which always rounds up to an integer after the end of a glyph.
237     // This rounding differs to the glyph bounds, which rounds to nearest
238     // integer. See http://crbug.com/735346.
239     const bool rtl =
240         render_text->GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT;
241     // Return Point corresponding to the leading edge of the character.
242     return gfx::Point(rtl ? bounds[0].right() - 1 : bounds[0].x() + 1, mid_y);
243   }
244 
GetLineCount()245   size_t GetLineCount() {
246     SimulatePaint();
247     return label()->GetRenderTextForSelectionController()->GetNumLines();
248   }
249 
GetSelectedText()250   base::string16 GetSelectedText() { return label()->GetSelectedText(); }
251 
event_generator()252   ui::test::EventGenerator* event_generator() { return event_generator_.get(); }
253 
IsMenuCommandEnabled(int command_id)254   bool IsMenuCommandEnabled(int command_id) {
255     return label()->IsCommandIdEnabled(command_id);
256   }
257 
258  private:
259   std::unique_ptr<ui::test::EventGenerator> event_generator_;
260 
261   DISALLOW_COPY_AND_ASSIGN(LabelSelectionTest);
262 };
263 
TEST_F(LabelTest,FontPropertySymbol)264 TEST_F(LabelTest, FontPropertySymbol) {
265 #if defined(OS_LINUX)
266   // On linux, the fonts are mocked with a custom FontConfig. The "Courier New"
267   // family name is mapped to Cousine-Regular.ttf (see: $build/test_fonts/*).
268   std::string font_name("Courier New");
269 #else
270   std::string font_name("symbol");
271 #endif
272   gfx::Font font(font_name, 26);
273   label()->SetFontList(gfx::FontList(font));
274   gfx::Font font_used = label()->font_list().GetPrimaryFont();
275   EXPECT_EQ(font_name, font_used.GetFontName());
276   EXPECT_EQ(26, font_used.GetFontSize());
277 }
278 
TEST_F(LabelTest,FontPropertyArial)279 TEST_F(LabelTest, FontPropertyArial) {
280   std::string font_name("arial");
281   gfx::Font font(font_name, 30);
282   label()->SetFontList(gfx::FontList(font));
283   gfx::Font font_used = label()->font_list().GetPrimaryFont();
284   EXPECT_EQ(font_name, font_used.GetFontName());
285   EXPECT_EQ(30, font_used.GetFontSize());
286 }
287 
TEST_F(LabelTest,TextProperty)288 TEST_F(LabelTest, TextProperty) {
289   base::string16 test_text(ASCIIToUTF16("A random string."));
290   label()->SetText(test_text);
291   EXPECT_EQ(test_text, label()->GetText());
292 }
293 
TEST_F(LabelTest,ColorProperty)294 TEST_F(LabelTest, ColorProperty) {
295   SkColor color = SkColorSetARGB(20, 40, 10, 5);
296   label()->SetAutoColorReadabilityEnabled(false);
297   label()->SetEnabledColor(color);
298   EXPECT_EQ(color, label()->GetEnabledColor());
299 }
300 
TEST_F(LabelTest,AlignmentProperty)301 TEST_F(LabelTest, AlignmentProperty) {
302   const bool was_rtl = base::i18n::IsRTL();
303 
304   for (size_t i = 0; i < 2; ++i) {
305     // Toggle the application default text direction (to try each direction).
306     SetRTL(!base::i18n::IsRTL());
307     bool reverse_alignment = base::i18n::IsRTL();
308 
309     // The alignment should be flipped in RTL UI.
310     label()->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
311     EXPECT_EQ(reverse_alignment ? gfx::ALIGN_LEFT : gfx::ALIGN_RIGHT,
312               label()->GetHorizontalAlignment());
313     label()->SetHorizontalAlignment(gfx::ALIGN_LEFT);
314     EXPECT_EQ(reverse_alignment ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT,
315               label()->GetHorizontalAlignment());
316     label()->SetHorizontalAlignment(gfx::ALIGN_CENTER);
317     EXPECT_EQ(gfx::ALIGN_CENTER, label()->GetHorizontalAlignment());
318 
319     for (size_t j = 0; j < 2; ++j) {
320       label()->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
321       const bool rtl = j == 0;
322       label()->SetText(rtl ? base::WideToUTF16(L"\x5d0") : ASCIIToUTF16("A"));
323       EXPECT_EQ(gfx::ALIGN_TO_HEAD, label()->GetHorizontalAlignment());
324     }
325   }
326 
327   EXPECT_EQ(was_rtl, base::i18n::IsRTL());
328 }
329 
TEST_F(LabelTest,MinimumSizeRespectsLineHeight)330 TEST_F(LabelTest, MinimumSizeRespectsLineHeight) {
331   base::string16 text(ASCIIToUTF16("This is example text."));
332   label()->SetText(text);
333 
334   const gfx::Size minimum_size = label()->GetMinimumSize();
335   const int expected_height = minimum_size.height() + 10;
336   label()->SetLineHeight(expected_height);
337   EXPECT_EQ(expected_height, label()->GetMinimumSize().height());
338 }
339 
TEST_F(LabelTest,MinimumSizeRespectsLineHeightMultiline)340 TEST_F(LabelTest, MinimumSizeRespectsLineHeightMultiline) {
341   base::string16 text(ASCIIToUTF16("This is example text."));
342   label()->SetText(text);
343   label()->SetMultiLine(true);
344 
345   const gfx::Size minimum_size = label()->GetMinimumSize();
346   const int expected_height = minimum_size.height() + 10;
347   label()->SetLineHeight(expected_height);
348   EXPECT_EQ(expected_height, label()->GetMinimumSize().height());
349 }
350 
TEST_F(LabelTest,MinimumSizeRespectsLineHeightWithInsets)351 TEST_F(LabelTest, MinimumSizeRespectsLineHeightWithInsets) {
352   base::string16 text(ASCIIToUTF16("This is example text."));
353   label()->SetText(text);
354 
355   const gfx::Size minimum_size = label()->GetMinimumSize();
356   int expected_height = minimum_size.height() + 10;
357   label()->SetLineHeight(expected_height);
358   constexpr gfx::Insets kInsets{2, 3, 4, 5};
359   expected_height += kInsets.height();
360   label()->SetBorder(CreateEmptyBorder(kInsets));
361   EXPECT_EQ(expected_height, label()->GetMinimumSize().height());
362 }
363 
TEST_F(LabelTest,MinimumSizeRespectsLineHeightMultilineWithInsets)364 TEST_F(LabelTest, MinimumSizeRespectsLineHeightMultilineWithInsets) {
365   base::string16 text(ASCIIToUTF16("This is example text."));
366   label()->SetText(text);
367   label()->SetMultiLine(true);
368 
369   const gfx::Size minimum_size = label()->GetMinimumSize();
370   int expected_height = minimum_size.height() + 10;
371   label()->SetLineHeight(expected_height);
372   constexpr gfx::Insets kInsets{2, 3, 4, 5};
373   expected_height += kInsets.height();
374   label()->SetBorder(CreateEmptyBorder(kInsets));
375   EXPECT_EQ(expected_height, label()->GetMinimumSize().height());
376 }
377 
TEST_F(LabelTest,ElideBehavior)378 TEST_F(LabelTest, ElideBehavior) {
379   base::string16 text(ASCIIToUTF16("This is example text."));
380   label()->SetText(text);
381   EXPECT_EQ(gfx::ELIDE_TAIL, label()->GetElideBehavior());
382   gfx::Size size = label()->GetPreferredSize();
383   label()->SetBoundsRect(gfx::Rect(size));
384   EXPECT_EQ(text, label()->GetDisplayTextForTesting());
385 
386   size.set_width(size.width() / 2);
387   label()->SetBoundsRect(gfx::Rect(size));
388   EXPECT_GT(text.size(), label()->GetDisplayTextForTesting().size());
389 
390   label()->SetElideBehavior(gfx::NO_ELIDE);
391   EXPECT_EQ(text, label()->GetDisplayTextForTesting());
392 }
393 
394 // Test the minimum width of a Label is correct depending on its ElideBehavior,
395 // including |gfx::NO_ELIDE|.
TEST_F(LabelTest,ElideBehaviorMinimumWidth)396 TEST_F(LabelTest, ElideBehaviorMinimumWidth) {
397   base::string16 text(ASCIIToUTF16("This is example text."));
398   label()->SetText(text);
399 
400   // Default should be |gfx::ELIDE_TAIL|.
401   EXPECT_EQ(gfx::ELIDE_TAIL, label()->GetElideBehavior());
402   gfx::Size size = label()->GetMinimumSize();
403   // Elidable labels have a minimum width that fits |gfx::kEllipsisUTF16|.
404   EXPECT_EQ(gfx::Canvas::GetStringWidth(base::string16(gfx::kEllipsisUTF16),
405                                         label()->font_list()),
406             size.width());
407   label()->SetSize(label()->GetMinimumSize());
408   EXPECT_GT(text.length(), label()->GetDisplayTextForTesting().length());
409 
410   // Truncated labels can take up the size they are given, but not exceed that
411   // if the text can't fit.
412   label()->SetElideBehavior(gfx::TRUNCATE);
413   label()->SetSize(gfx::Size(10, 10));
414   size = label()->GetMinimumSize();
415   EXPECT_LT(size.width(), label()->size().width());
416   EXPECT_GT(text.length(), label()->GetDisplayTextForTesting().length());
417 
418   // Non-elidable single-line labels should take up their full text size, since
419   // this behavior implies the text should not be cut off.
420   EXPECT_FALSE(label()->GetMultiLine());
421   label()->SetElideBehavior(gfx::NO_ELIDE);
422   size = label()->GetMinimumSize();
423   EXPECT_EQ(text.length(), label()->GetDisplayTextForTesting().length());
424 
425   label()->SetSize(label()->GetMinimumSize());
426   EXPECT_EQ(text, label()->GetDisplayTextForTesting());
427 }
428 
TEST_F(LabelTest,MultiLineProperty)429 TEST_F(LabelTest, MultiLineProperty) {
430   EXPECT_FALSE(label()->GetMultiLine());
431   label()->SetMultiLine(true);
432   EXPECT_TRUE(label()->GetMultiLine());
433   label()->SetMultiLine(false);
434   EXPECT_FALSE(label()->GetMultiLine());
435 }
436 
TEST_F(LabelTest,ObscuredProperty)437 TEST_F(LabelTest, ObscuredProperty) {
438   base::string16 test_text(ASCIIToUTF16("Password!"));
439   label()->SetText(test_text);
440   label()->SizeToPreferredSize();
441 
442   // The text should be unobscured by default.
443   EXPECT_FALSE(label()->GetObscured());
444   EXPECT_EQ(test_text, label()->GetDisplayTextForTesting());
445   EXPECT_EQ(test_text, label()->GetText());
446 
447   label()->SetObscured(true);
448   label()->SizeToPreferredSize();
449   EXPECT_TRUE(label()->GetObscured());
450   EXPECT_EQ(base::string16(test_text.size(),
451                            gfx::RenderText::kPasswordReplacementChar),
452             label()->GetDisplayTextForTesting());
453   EXPECT_EQ(test_text, label()->GetText());
454 
455   label()->SetText(test_text + test_text);
456   label()->SizeToPreferredSize();
457   EXPECT_EQ(base::string16(test_text.size() * 2,
458                            gfx::RenderText::kPasswordReplacementChar),
459             label()->GetDisplayTextForTesting());
460   EXPECT_EQ(test_text + test_text, label()->GetText());
461 
462   label()->SetObscured(false);
463   label()->SizeToPreferredSize();
464   EXPECT_FALSE(label()->GetObscured());
465   EXPECT_EQ(test_text + test_text, label()->GetDisplayTextForTesting());
466   EXPECT_EQ(test_text + test_text, label()->GetText());
467 }
468 
TEST_F(LabelTest,ObscuredSurrogatePair)469 TEST_F(LabelTest, ObscuredSurrogatePair) {
470   // 'MUSICAL SYMBOL G CLEF': represented in UTF-16 as two characters
471   // forming the surrogate pair 0x0001D11E.
472   base::string16 test_text = base::UTF8ToUTF16("\xF0\x9D\x84\x9E");
473   label()->SetText(test_text);
474   label()->SetObscured(true);
475   label()->SizeToPreferredSize();
476   EXPECT_EQ(base::string16(1, gfx::RenderText::kPasswordReplacementChar),
477             label()->GetDisplayTextForTesting());
478   EXPECT_EQ(test_text, label()->GetText());
479 }
480 
481 // This test case verifies the label preferred size will change based on the
482 // current layout, which may seem wrong. However many of our code base assumes
483 // this behavior, therefore this behavior will have to be kept until the code
484 // with this assumption is fixed. See http://crbug.com/468494 and
485 // http://crbug.com/467526.
486 // TODO(mukai): fix the code assuming this behavior and then fix Label
487 // implementation, and remove this test case.
TEST_F(LabelTest,MultilinePreferredSizeTest)488 TEST_F(LabelTest, MultilinePreferredSizeTest) {
489   label()->SetText(ASCIIToUTF16("This is an example."));
490 
491   gfx::Size single_line_size = label()->GetPreferredSize();
492 
493   label()->SetMultiLine(true);
494   gfx::Size multi_line_size = label()->GetPreferredSize();
495   EXPECT_EQ(single_line_size, multi_line_size);
496 
497   int new_width = multi_line_size.width() / 2;
498   label()->SetBounds(0, 0, new_width, label()->GetHeightForWidth(new_width));
499   gfx::Size new_size = label()->GetPreferredSize();
500   EXPECT_GT(multi_line_size.width(), new_size.width());
501   EXPECT_LT(multi_line_size.height(), new_size.height());
502 }
503 
TEST_F(LabelTest,TooltipProperty)504 TEST_F(LabelTest, TooltipProperty) {
505   label()->SetText(ASCIIToUTF16("My cool string."));
506 
507   // Initially, label has no bounds, its text does not fit, and therefore its
508   // text should be returned as the tooltip text.
509   EXPECT_EQ(label()->GetText(), label()->GetTooltipText(gfx::Point()));
510 
511   // While tooltip handling is disabled, GetTooltipText() should fail.
512   label()->SetHandlesTooltips(false);
513   EXPECT_TRUE(label()->GetTooltipText(gfx::Point()).empty());
514   label()->SetHandlesTooltips(true);
515 
516   // When set, custom tooltip text should be returned instead of the label's
517   // text.
518   base::string16 tooltip_text(ASCIIToUTF16("The tooltip!"));
519   label()->SetTooltipText(tooltip_text);
520   EXPECT_EQ(tooltip_text, label()->GetTooltipText(gfx::Point()));
521 
522   // While tooltip handling is disabled, GetTooltipText() should fail.
523   label()->SetHandlesTooltips(false);
524   EXPECT_TRUE(label()->GetTooltipText(gfx::Point()).empty());
525   label()->SetHandlesTooltips(true);
526 
527   // When the tooltip text is set to an empty string, the original behavior is
528   // restored.
529   label()->SetTooltipText(base::string16());
530   EXPECT_EQ(label()->GetText(), label()->GetTooltipText(gfx::Point()));
531 
532   // While tooltip handling is disabled, GetTooltipText() should fail.
533   label()->SetHandlesTooltips(false);
534   EXPECT_TRUE(label()->GetTooltipText(gfx::Point()).empty());
535   label()->SetHandlesTooltips(true);
536 
537   // Make the label big enough to hold the text
538   // and expect there to be no tooltip.
539   label()->SetBounds(0, 0, 1000, 40);
540   EXPECT_TRUE(label()->GetTooltipText(gfx::Point()).empty());
541 
542   // Shrinking the single-line label's height shouldn't trigger a tooltip.
543   label()->SetBounds(0, 0, 1000, label()->GetPreferredSize().height() / 2);
544   EXPECT_TRUE(label()->GetTooltipText(gfx::Point()).empty());
545 
546   // Verify that explicitly set tooltip text is shown, regardless of size.
547   label()->SetTooltipText(tooltip_text);
548   EXPECT_EQ(tooltip_text, label()->GetTooltipText(gfx::Point()));
549   // Clear out the explicitly set tooltip text.
550   label()->SetTooltipText(base::string16());
551 
552   // Shrink the bounds and the tooltip should come back.
553   label()->SetBounds(0, 0, 10, 10);
554   EXPECT_FALSE(label()->GetTooltipText(gfx::Point()).empty());
555 
556   // Make the label obscured and there is no tooltip.
557   label()->SetObscured(true);
558   EXPECT_TRUE(label()->GetTooltipText(gfx::Point()).empty());
559 
560   // Obscuring the text shouldn't permanently clobber the tooltip.
561   label()->SetObscured(false);
562   EXPECT_FALSE(label()->GetTooltipText(gfx::Point()).empty());
563 
564   // Making the label multiline shouldn't eliminate the tooltip.
565   label()->SetMultiLine(true);
566   EXPECT_FALSE(label()->GetTooltipText(gfx::Point()).empty());
567   // Expanding the multiline label bounds should eliminate the tooltip.
568   label()->SetBounds(0, 0, 1000, 1000);
569   EXPECT_TRUE(label()->GetTooltipText(gfx::Point()).empty());
570 
571   // Verify that setting the tooltip still shows it.
572   label()->SetTooltipText(tooltip_text);
573   EXPECT_EQ(tooltip_text, label()->GetTooltipText(gfx::Point()));
574   // Clear out the tooltip.
575   label()->SetTooltipText(base::string16());
576 }
577 
TEST_F(LabelTest,Accessibility)578 TEST_F(LabelTest, Accessibility) {
579   label()->SetText(ASCIIToUTF16("My special text."));
580 
581   ui::AXNodeData node_data;
582   label()->GetAccessibleNodeData(&node_data);
583   EXPECT_EQ(ax::mojom::Role::kStaticText, node_data.role);
584   EXPECT_EQ(label()->GetText(),
585             node_data.GetString16Attribute(ax::mojom::StringAttribute::kName));
586   EXPECT_FALSE(
587       node_data.HasIntAttribute(ax::mojom::IntAttribute::kRestriction));
588 }
589 
TEST_F(LabelTest,TextChangeWithoutLayout)590 TEST_F(LabelTest, TextChangeWithoutLayout) {
591   label()->SetText(ASCIIToUTF16("Example"));
592   label()->SetBounds(0, 0, 200, 200);
593 
594   gfx::Canvas canvas(gfx::Size(200, 200), 1.0f, true);
595   label()->OnPaint(&canvas);
596   EXPECT_TRUE(label()->display_text_);
597   EXPECT_EQ(ASCIIToUTF16("Example"), label()->display_text_->GetDisplayText());
598 
599   label()->SetText(ASCIIToUTF16("Altered"));
600   // The altered text should be painted even though Layout() or SetBounds() are
601   // not called.
602   label()->OnPaint(&canvas);
603   EXPECT_TRUE(label()->display_text_);
604   EXPECT_EQ(ASCIIToUTF16("Altered"), label()->display_text_->GetDisplayText());
605 }
606 
TEST_F(LabelTest,EmptyLabelSizing)607 TEST_F(LabelTest, EmptyLabelSizing) {
608   const gfx::Size expected_size(0, label()->font_list().GetHeight());
609   EXPECT_EQ(expected_size, label()->GetPreferredSize());
610   label()->SetMultiLine(!label()->GetMultiLine());
611   EXPECT_EQ(expected_size, label()->GetPreferredSize());
612 }
613 
TEST_F(LabelTest,SingleLineSizing)614 TEST_F(LabelTest, SingleLineSizing) {
615   label()->SetText(ASCIIToUTF16("A not so random string in one line."));
616   const gfx::Size size = label()->GetPreferredSize();
617   EXPECT_GT(size.height(), kMinTextDimension);
618   EXPECT_GT(size.width(), kMinTextDimension);
619 
620   // Setting a size smaller than preferred should not change the preferred size.
621   label()->SetSize(gfx::Size(size.width() / 2, size.height() / 2));
622   EXPECT_EQ(size, label()->GetPreferredSize());
623 
624   const gfx::Insets border(10, 20, 30, 40);
625   label()->SetBorder(CreateEmptyBorder(border));
626   const gfx::Size size_with_border = label()->GetPreferredSize();
627   EXPECT_EQ(size_with_border.height(), size.height() + border.height());
628   EXPECT_EQ(size_with_border.width(), size.width() + border.width());
629   EXPECT_EQ(size.height() + border.height(),
630             label()->GetHeightForWidth(size_with_border.width()));
631 }
632 
TEST_F(LabelTest,MultilineSmallAvailableWidthSizing)633 TEST_F(LabelTest, MultilineSmallAvailableWidthSizing) {
634   label()->SetMultiLine(true);
635   label()->SetAllowCharacterBreak(true);
636   label()->SetText(ASCIIToUTF16("Too Wide."));
637 
638   // Check that Label can be laid out at a variety of small sizes,
639   // splitting the words into up to one character per line if necessary.
640   // Incorrect word splitting may cause infinite loops in text layout.
641   gfx::Size required_size = label()->GetPreferredSize();
642   for (int i = 1; i < required_size.width(); ++i)
643     EXPECT_GT(label()->GetHeightForWidth(i), 0);
644 }
645 
646 // Verifies if SetAllowCharacterBreak(true) doesn't change the preferred size.
647 // See crbug.com/469559
TEST_F(LabelTest,PreferredSizeForAllowCharacterBreak)648 TEST_F(LabelTest, PreferredSizeForAllowCharacterBreak) {
649   label()->SetText(base::ASCIIToUTF16("Example"));
650   gfx::Size preferred_size = label()->GetPreferredSize();
651 
652   label()->SetMultiLine(true);
653   label()->SetAllowCharacterBreak(true);
654   EXPECT_EQ(preferred_size, label()->GetPreferredSize());
655 }
656 
TEST_F(LabelTest,MultiLineSizing)657 TEST_F(LabelTest, MultiLineSizing) {
658   label()->SetText(
659       ASCIIToUTF16("A random string\nwith multiple lines\nand returns!"));
660   label()->SetMultiLine(true);
661 
662   // GetPreferredSize
663   gfx::Size required_size = label()->GetPreferredSize();
664   EXPECT_GT(required_size.height(), kMinTextDimension);
665   EXPECT_GT(required_size.width(), kMinTextDimension);
666 
667   // SizeToFit with unlimited width.
668   label()->SizeToFit(0);
669   int required_width = label()->GetLocalBounds().width();
670   EXPECT_GT(required_width, kMinTextDimension);
671 
672   // SizeToFit with limited width.
673   label()->SizeToFit(required_width - 1);
674   int constrained_width = label()->GetLocalBounds().width();
675 #if defined(OS_WIN)
676   // Canvas::SizeStringInt (in ui/gfx/canvas_linux.cc)
677   // has to be fixed to return the size that fits to given width/height.
678   EXPECT_LT(constrained_width, required_width);
679 #endif
680   EXPECT_GT(constrained_width, kMinTextDimension);
681 
682   // Change the width back to the desire width.
683   label()->SizeToFit(required_width);
684   EXPECT_EQ(required_width, label()->GetLocalBounds().width());
685 
686   // General tests for GetHeightForWidth.
687   int required_height = label()->GetHeightForWidth(required_width);
688   EXPECT_GT(required_height, kMinTextDimension);
689   int height_for_constrained_width =
690       label()->GetHeightForWidth(constrained_width);
691 #if defined(OS_WIN)
692   // Canvas::SizeStringInt (in ui/gfx/canvas_linux.cc)
693   // has to be fixed to return the size that fits to given width/height.
694   EXPECT_GT(height_for_constrained_width, required_height);
695 #endif
696   // Using the constrained width or the required_width - 1 should give the
697   // same result for the height because the constrainted width is the tight
698   // width when given "required_width - 1" as the max width.
699   EXPECT_EQ(height_for_constrained_width,
700             label()->GetHeightForWidth(required_width - 1));
701 
702   // Test everything with borders.
703   gfx::Insets border(10, 20, 30, 40);
704   label()->SetBorder(CreateEmptyBorder(border));
705 
706   // SizeToFit and borders.
707   label()->SizeToFit(0);
708   int required_width_with_border = label()->GetLocalBounds().width();
709   EXPECT_EQ(required_width_with_border, required_width + border.width());
710 
711   // GetHeightForWidth and borders.
712   int required_height_with_border =
713       label()->GetHeightForWidth(required_width_with_border);
714   EXPECT_EQ(required_height_with_border, required_height + border.height());
715 
716   // Test that the border width is subtracted before doing the height
717   // calculation.  If it is, then the height will grow when width
718   // is shrunk.
719   int height1 = label()->GetHeightForWidth(required_width_with_border - 1);
720 #if defined(OS_WIN)
721   // Canvas::SizeStringInt (in ui/gfx/canvas_linux.cc)
722   // has to be fixed to return the size that fits to given width/height.
723   EXPECT_GT(height1, required_height_with_border);
724 #endif
725   EXPECT_EQ(height1, height_for_constrained_width + border.height());
726 
727   // GetPreferredSize and borders.
728   label()->SetBounds(0, 0, 0, 0);
729   gfx::Size required_size_with_border = label()->GetPreferredSize();
730   EXPECT_EQ(required_size_with_border.height(),
731             required_size.height() + border.height());
732   EXPECT_EQ(required_size_with_border.width(),
733             required_size.width() + border.width());
734 }
735 
736 #if !defined(OS_MACOSX)
737 // TODO(warx): Remove !defined(OS_MACOSX) once SetMaxLines() is applied to MAC
738 // (crbug.com/758720).
TEST_F(LabelTest,MultiLineSetMaxLines)739 TEST_F(LabelTest, MultiLineSetMaxLines) {
740   // Ensure SetMaxLines clamps the line count of a string with returns.
741   label()->SetText(ASCIIToUTF16("first line\nsecond line\nthird line"));
742   label()->SetMultiLine(true);
743   gfx::Size string_size = label()->GetPreferredSize();
744   label()->SetMaxLines(2);
745   gfx::Size two_line_size = label()->GetPreferredSize();
746   EXPECT_EQ(string_size.width(), two_line_size.width());
747   EXPECT_GT(string_size.height(), two_line_size.height());
748 
749   // Ensure GetHeightForWidth also respects SetMaxLines.
750   int height = label()->GetHeightForWidth(string_size.width() / 2);
751   EXPECT_EQ(height, two_line_size.height());
752 
753   // Ensure SetMaxLines also works with line wrapping for SizeToFit.
754   label()->SetText(ASCIIToUTF16("A long string that will be wrapped"));
755   label()->SetMaxLines(0);  // Used to get the uncapped height.
756   label()->SizeToFit(0);    // Used to get the uncapped width.
757   label()->SizeToFit(label()->GetPreferredSize().width() / 4);
758   string_size = label()->GetPreferredSize();
759   label()->SetMaxLines(2);
760   two_line_size = label()->GetPreferredSize();
761   EXPECT_EQ(string_size.width(), two_line_size.width());
762   EXPECT_GT(string_size.height(), two_line_size.height());
763 
764   // Ensure SetMaxLines also works with line wrapping for SetMaximumWidth.
765   label()->SetMaxLines(0);  // Used to get the uncapped height.
766   label()->SizeToFit(0);    // Used to get the uncapped width.
767   label()->SetMaximumWidth(label()->GetPreferredSize().width() / 4);
768   string_size = label()->GetPreferredSize();
769   label()->SetMaxLines(2);
770   two_line_size = label()->GetPreferredSize();
771   EXPECT_EQ(string_size.width(), two_line_size.width());
772   EXPECT_GT(string_size.height(), two_line_size.height());
773 
774   // Ensure SetMaxLines respects the requested inset height.
775   const gfx::Insets border(1, 2, 3, 4);
776   label()->SetBorder(CreateEmptyBorder(border));
777   EXPECT_EQ(two_line_size.height() + border.height(),
778             label()->GetPreferredSize().height());
779 }
780 #endif
781 
782 // Verifies if the combination of text eliding and multiline doesn't cause
783 // any side effects of size / layout calculation.
TEST_F(LabelTest,MultiLineSizingWithElide)784 TEST_F(LabelTest, MultiLineSizingWithElide) {
785   const base::string16 text =
786       ASCIIToUTF16("A random string\nwith multiple lines\nand returns!");
787   label()->SetText(text);
788   label()->SetMultiLine(true);
789 
790   gfx::Size required_size = label()->GetPreferredSize();
791   EXPECT_GT(required_size.height(), kMinTextDimension);
792   EXPECT_GT(required_size.width(), kMinTextDimension);
793   label()->SetBoundsRect(gfx::Rect(required_size));
794 
795   label()->SetElideBehavior(gfx::ELIDE_TAIL);
796   EXPECT_EQ(required_size, label()->GetPreferredSize());
797   EXPECT_EQ(text, label()->GetDisplayTextForTesting());
798 
799   label()->SizeToFit(required_size.width() - 1);
800   gfx::Size narrow_size = label()->GetPreferredSize();
801   EXPECT_GT(required_size.width(), narrow_size.width());
802   EXPECT_LT(required_size.height(), narrow_size.height());
803 
804   // SetBounds() doesn't change the preferred size.
805   label()->SetBounds(0, 0, narrow_size.width() - 1, narrow_size.height());
806   EXPECT_EQ(narrow_size, label()->GetPreferredSize());
807 
808   // Paint() doesn't change the preferred size.
809   gfx::Canvas canvas;
810   label()->OnPaint(&canvas);
811   EXPECT_EQ(narrow_size, label()->GetPreferredSize());
812 }
813 
814 // Check that labels support GetTooltipHandlerForPoint.
TEST_F(LabelTest,GetTooltipHandlerForPoint)815 TEST_F(LabelTest, GetTooltipHandlerForPoint) {
816   label()->SetText(
817       ASCIIToUTF16("A string that's long enough to exceed the bounds"));
818   label()->SetBounds(0, 0, 10, 10);
819 
820   // By default, labels start out as tooltip handlers.
821   ASSERT_TRUE(label()->GetHandlesTooltips());
822 
823   // There's a default tooltip if the text is too big to fit.
824   EXPECT_EQ(label(), label()->GetTooltipHandlerForPoint(gfx::Point(2, 2)));
825 
826   // If tooltip handling is disabled, the label should not provide a tooltip
827   // handler.
828   label()->SetHandlesTooltips(false);
829   EXPECT_FALSE(label()->GetTooltipHandlerForPoint(gfx::Point(2, 2)));
830   label()->SetHandlesTooltips(true);
831 
832   // If there's no default tooltip, this should return NULL.
833   label()->SetBounds(0, 0, 500, 50);
834   EXPECT_FALSE(label()->GetTooltipHandlerForPoint(gfx::Point(2, 2)));
835 
836   label()->SetTooltipText(ASCIIToUTF16("a tooltip"));
837   // If the point hits the label, and tooltip is set, the label should be
838   // returned as its tooltip handler.
839   EXPECT_EQ(label(), label()->GetTooltipHandlerForPoint(gfx::Point(2, 2)));
840 
841   // Additionally, GetTooltipHandlerForPoint should verify that the label
842   // actually contains the point.
843   EXPECT_FALSE(label()->GetTooltipHandlerForPoint(gfx::Point(2, 51)));
844   EXPECT_FALSE(label()->GetTooltipHandlerForPoint(gfx::Point(-1, 20)));
845 
846   // Again, if tooltip handling is disabled, the label should not provide a
847   // tooltip handler.
848   label()->SetHandlesTooltips(false);
849   EXPECT_FALSE(label()->GetTooltipHandlerForPoint(gfx::Point(2, 2)));
850   EXPECT_FALSE(label()->GetTooltipHandlerForPoint(gfx::Point(2, 51)));
851   EXPECT_FALSE(label()->GetTooltipHandlerForPoint(gfx::Point(-1, 20)));
852   label()->SetHandlesTooltips(true);
853 
854   // GetTooltipHandlerForPoint works should work in child bounds.
855   label()->SetBounds(2, 2, 10, 10);
856   EXPECT_EQ(label(), label()->GetTooltipHandlerForPoint(gfx::Point(1, 5)));
857   EXPECT_FALSE(label()->GetTooltipHandlerForPoint(gfx::Point(3, 11)));
858 }
859 
860 // Check that label releases its internal layout data when it's unnecessary.
TEST_F(LabelTest,ResetRenderTextData)861 TEST_F(LabelTest, ResetRenderTextData) {
862   label()->SetText(ASCIIToUTF16("Example"));
863   label()->SizeToPreferredSize();
864   gfx::Size preferred_size = label()->GetPreferredSize();
865 
866   EXPECT_NE(gfx::Size(), preferred_size);
867   EXPECT_FALSE(label()->display_text_);
868 
869   gfx::Canvas canvas(preferred_size, 1.0f, true);
870   label()->OnPaint(&canvas);
871   EXPECT_TRUE(label()->display_text_);
872 
873   // Label should recreate its RenderText object when it's invisible, to release
874   // the layout structures and data.
875   label()->SetVisible(false);
876   EXPECT_FALSE(label()->display_text_);
877 
878   // Querying fields or size information should not recompute the layout
879   // unnecessarily.
880   EXPECT_EQ(ASCIIToUTF16("Example"), label()->GetText());
881   EXPECT_FALSE(label()->display_text_);
882 
883   EXPECT_EQ(preferred_size, label()->GetPreferredSize());
884   EXPECT_FALSE(label()->display_text_);
885 
886   // RenderText data should be back when it's necessary.
887   label()->SetVisible(true);
888   EXPECT_FALSE(label()->display_text_);
889 
890   label()->OnPaint(&canvas);
891   EXPECT_TRUE(label()->display_text_);
892 
893   // Changing layout just resets |display_text_|. It'll recover next time it's
894   // drawn.
895   label()->SetBounds(0, 0, 10, 10);
896   EXPECT_FALSE(label()->display_text_);
897 
898   label()->OnPaint(&canvas);
899   EXPECT_TRUE(label()->display_text_);
900 }
901 
TEST_F(LabelTest,MultilineSupportedRenderText)902 TEST_F(LabelTest, MultilineSupportedRenderText) {
903   label()->SetText(ASCIIToUTF16("Example of\nmultilined label"));
904   label()->SetMultiLine(true);
905   label()->SizeToPreferredSize();
906 
907   gfx::Canvas canvas(label()->GetPreferredSize(), 1.0f, true);
908   label()->OnPaint(&canvas);
909 
910   // There's only RenderText instance, which should have multiple lines.
911   ASSERT_TRUE(label()->display_text_);
912   EXPECT_EQ(2u, label()->display_text_->GetNumLines());
913 }
914 
915 // Ensures SchedulePaint() calls are not made in OnPaint().
TEST_F(LabelTest,NoSchedulePaintInOnPaint)916 TEST_F(LabelTest, NoSchedulePaintInOnPaint) {
917   TestLabel label;
918 
919   // Initialization should schedule at least one paint, but the precise number
920   // doesn't really matter.
921   int count = label.schedule_paint_count();
922   EXPECT_LT(0, count);
923 
924   // Painting should never schedule another paint.
925   label.SimulatePaint();
926   EXPECT_EQ(count, label.schedule_paint_count());
927 
928   // Test a few things that should schedule paints. Multiple times is OK.
929   label.SetEnabled(false);
930   EXPECT_TRUE(Increased(label.schedule_paint_count(), &count));
931 
932   label.SetText(label.GetText() + ASCIIToUTF16("Changed"));
933   EXPECT_TRUE(Increased(label.schedule_paint_count(), &count));
934 
935   label.SizeToPreferredSize();
936   EXPECT_TRUE(Increased(label.schedule_paint_count(), &count));
937 
938   label.SetEnabledColor(SK_ColorBLUE);
939   EXPECT_TRUE(Increased(label.schedule_paint_count(), &count));
940 
941   label.SimulatePaint();
942   EXPECT_EQ(count, label.schedule_paint_count());  // Unchanged.
943 }
944 
TEST_F(LabelTest,EmptyLabel)945 TEST_F(LabelTest, EmptyLabel) {
946   label()->SetFocusBehavior(View::FocusBehavior::ALWAYS);
947   label()->RequestFocus();
948   label()->SizeToPreferredSize();
949   EXPECT_TRUE(label()->size().IsEmpty());
950 
951   // With no text, neither links nor labels have a size in any dimension.
952   Link concrete_link((base::string16()));
953   EXPECT_TRUE(concrete_link.GetPreferredSize().IsEmpty());
954 }
955 
TEST_F(LabelTest,CanForceDirectionality)956 TEST_F(LabelTest, CanForceDirectionality) {
957   Label bidi_text_force_url(ToRTL("0123456") + base::ASCIIToUTF16(".com"), 0,
958                             style::STYLE_PRIMARY,
959                             gfx::DirectionalityMode::DIRECTIONALITY_AS_URL);
960   EXPECT_EQ(base::i18n::TextDirection::LEFT_TO_RIGHT,
961             bidi_text_force_url.GetTextDirectionForTesting());
962 
963   Label rtl_text_force_ltr(ToRTL("0123456"), 0, style::STYLE_PRIMARY,
964                            gfx::DirectionalityMode::DIRECTIONALITY_FORCE_LTR);
965   EXPECT_EQ(base::i18n::TextDirection::LEFT_TO_RIGHT,
966             rtl_text_force_ltr.GetTextDirectionForTesting());
967 
968   Label ltr_text_force_rtl(base::ASCIIToUTF16("0123456"), 0,
969                            style::STYLE_PRIMARY,
970                            gfx::DirectionalityMode::DIRECTIONALITY_FORCE_RTL);
971   EXPECT_EQ(base::i18n::TextDirection::RIGHT_TO_LEFT,
972             ltr_text_force_rtl.GetTextDirectionForTesting());
973 
974   SetRTL(true);
975   Label ltr_use_ui(base::ASCIIToUTF16("0123456"), 0, style::STYLE_PRIMARY,
976                    gfx::DirectionalityMode::DIRECTIONALITY_FROM_UI);
977   EXPECT_EQ(base::i18n::TextDirection::RIGHT_TO_LEFT,
978             ltr_use_ui.GetTextDirectionForTesting());
979 
980   SetRTL(false);
981   Label rtl_use_ui(ToRTL("0123456"), 0, style::STYLE_PRIMARY,
982                    gfx::DirectionalityMode::DIRECTIONALITY_FROM_UI);
983   EXPECT_EQ(base::i18n::TextDirection::LEFT_TO_RIGHT,
984             rtl_use_ui.GetTextDirectionForTesting());
985 }
986 
TEST_F(LabelTest,DefaultDirectionalityIsFromText)987 TEST_F(LabelTest, DefaultDirectionalityIsFromText) {
988   Label ltr(base::ASCIIToUTF16("Foo"));
989   EXPECT_EQ(base::i18n::TextDirection::LEFT_TO_RIGHT,
990             ltr.GetTextDirectionForTesting());
991 
992   Label rtl(ToRTL("0123456"));
993   EXPECT_EQ(base::i18n::TextDirection::RIGHT_TO_LEFT,
994             rtl.GetTextDirectionForTesting());
995 }
996 
TEST_F(LabelTest,IsDisplayTextTruncated)997 TEST_F(LabelTest, IsDisplayTextTruncated) {
998   const base::string16 text = ASCIIToUTF16("A random string");
999   label()->SetText(text);
1000 
1001   gfx::Size zero_size;
1002   label()->SetElideBehavior(gfx::ELIDE_TAIL);
1003   label()->SetBoundsRect(gfx::Rect(zero_size));
1004   EXPECT_TRUE(label()->IsDisplayTextTruncated());
1005 
1006   label()->SetElideBehavior(gfx::NO_ELIDE);
1007   EXPECT_TRUE(label()->IsDisplayTextTruncated());
1008 
1009   gfx::Size minimum_size(1, 1);
1010   label()->SetBoundsRect(gfx::Rect(minimum_size));
1011   EXPECT_TRUE(label()->IsDisplayTextTruncated());
1012 
1013   gfx::Size enough_size(100, 100);
1014   label()->SetBoundsRect(gfx::Rect(enough_size));
1015   EXPECT_FALSE(label()->IsDisplayTextTruncated());
1016 
1017   const base::string16 empty_text;
1018   label()->SetText(empty_text);
1019   EXPECT_FALSE(label()->IsDisplayTextTruncated());
1020   label()->SetBoundsRect(gfx::Rect(zero_size));
1021   EXPECT_FALSE(label()->IsDisplayTextTruncated());
1022 }
1023 
TEST_F(LabelTest,TextChangedCallback)1024 TEST_F(LabelTest, TextChangedCallback) {
1025   bool text_changed = false;
1026   auto subscription = label()->AddTextChangedCallback(base::BindRepeating(
1027       [](bool* text_changed) { *text_changed = true; }, &text_changed));
1028 
1029   label()->SetText(ASCIIToUTF16("abc"));
1030   EXPECT_TRUE(text_changed);
1031 }
1032 
TEST_F(LabelSelectionTest,Selectable)1033 TEST_F(LabelSelectionTest, Selectable) {
1034   // By default, labels don't support text selection.
1035   EXPECT_FALSE(label()->GetSelectable());
1036 
1037   ASSERT_TRUE(label()->SetSelectable(true));
1038   EXPECT_TRUE(label()->GetSelectable());
1039 
1040   // Verify that making a label multiline still causes the label to support text
1041   // selection.
1042   label()->SetMultiLine(true);
1043   EXPECT_TRUE(label()->GetSelectable());
1044 
1045   // Verify that obscuring the label text causes the label to not support text
1046   // selection.
1047   label()->SetObscured(true);
1048   EXPECT_FALSE(label()->GetSelectable());
1049 }
1050 
1051 // Verify that labels supporting text selection get focus on clicks.
TEST_F(LabelSelectionTest,FocusOnClick)1052 TEST_F(LabelSelectionTest, FocusOnClick) {
1053   label()->SetText(ASCIIToUTF16("text"));
1054   label()->SizeToPreferredSize();
1055 
1056   // By default, labels don't get focus on click.
1057   PerformClick(gfx::Point());
1058   EXPECT_NE(label(), GetFocusedView());
1059 
1060   ASSERT_TRUE(label()->SetSelectable(true));
1061   PerformClick(gfx::Point());
1062   EXPECT_EQ(label(), GetFocusedView());
1063 }
1064 
1065 // Verify that labels supporting text selection do not get focus on tab
1066 // traversal by default.
TEST_F(LabelSelectionTest,FocusTraversal)1067 TEST_F(LabelSelectionTest, FocusTraversal) {
1068   // Add another view before |label()|.
1069   View* view = new View();
1070   view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
1071   widget()->GetContentsView()->AddChildViewAt(view, 0);
1072 
1073   // By default, labels are not focusable.
1074   view->RequestFocus();
1075   EXPECT_EQ(view, GetFocusedView());
1076   widget()->GetFocusManager()->AdvanceFocus(false);
1077   EXPECT_NE(label(), GetFocusedView());
1078 
1079   // On enabling text selection, labels can get focus on clicks but not via tab
1080   // traversal.
1081   view->RequestFocus();
1082   EXPECT_EQ(view, GetFocusedView());
1083   EXPECT_TRUE(label()->SetSelectable(true));
1084   widget()->GetFocusManager()->AdvanceFocus(false);
1085   EXPECT_NE(label(), GetFocusedView());
1086 
1087   // A label with FocusBehavior::ALWAYS should get focus via tab traversal.
1088   view->RequestFocus();
1089   EXPECT_EQ(view, GetFocusedView());
1090   EXPECT_TRUE(label()->SetSelectable(false));
1091   label()->SetFocusBehavior(View::FocusBehavior::ALWAYS);
1092   widget()->GetFocusManager()->AdvanceFocus(false);
1093   EXPECT_EQ(label(), GetFocusedView());
1094 }
1095 
1096 // Verify label text selection behavior on double and triple clicks.
TEST_F(LabelSelectionTest,DoubleTripleClick)1097 TEST_F(LabelSelectionTest, DoubleTripleClick) {
1098   label()->SetText(ASCIIToUTF16("Label double click"));
1099   label()->SizeToPreferredSize();
1100   ASSERT_TRUE(label()->SetSelectable(true));
1101 
1102   PerformClick(GetCursorPoint(0));
1103   EXPECT_TRUE(GetSelectedText().empty());
1104 
1105   // Double clicking should select the word under cursor.
1106   PerformClick(GetCursorPoint(0));
1107   EXPECT_STR_EQ("Label", GetSelectedText());
1108 
1109   // Triple clicking should select all the text.
1110   PerformClick(GetCursorPoint(0));
1111   EXPECT_EQ(label()->GetText(), GetSelectedText());
1112 
1113   // Clicking again should alternate to double click.
1114   PerformClick(GetCursorPoint(0));
1115   EXPECT_STR_EQ("Label", GetSelectedText());
1116 
1117   // Clicking at another location should clear the selection.
1118   PerformClick(GetCursorPoint(8));
1119   EXPECT_TRUE(GetSelectedText().empty());
1120   PerformClick(GetCursorPoint(8));
1121   EXPECT_STR_EQ("double", GetSelectedText());
1122 }
1123 
1124 // Verify label text selection behavior on mouse drag.
TEST_F(LabelSelectionTest,MouseDrag)1125 TEST_F(LabelSelectionTest, MouseDrag) {
1126   label()->SetText(ASCIIToUTF16("Label mouse drag"));
1127   label()->SizeToPreferredSize();
1128   ASSERT_TRUE(label()->SetSelectable(true));
1129 
1130   PerformMousePress(GetCursorPoint(5));
1131   PerformMouseDragTo(GetCursorPoint(0));
1132   EXPECT_STR_EQ("Label", GetSelectedText());
1133 
1134   PerformMouseDragTo(GetCursorPoint(8));
1135   EXPECT_STR_EQ(" mo", GetSelectedText());
1136 
1137   PerformMouseDragTo(gfx::Point(200, GetCursorPoint(0).y()));
1138   PerformMouseRelease(gfx::Point(200, GetCursorPoint(0).y()));
1139   EXPECT_STR_EQ(" mouse drag", GetSelectedText());
1140 
1141   event_generator()->PressKey(ui::VKEY_C, kControlCommandModifier);
1142   EXPECT_STR_EQ(" mouse drag",
1143                 GetClipboardText(ui::ClipboardBuffer::kCopyPaste));
1144 }
1145 
TEST_F(LabelSelectionTest,MouseDragMultilineLTR)1146 TEST_F(LabelSelectionTest, MouseDragMultilineLTR) {
1147   label()->SetMultiLine(true);
1148   label()->SetText(ASCIIToUTF16("abcd\nefgh"));
1149   label()->SizeToPreferredSize();
1150   ASSERT_TRUE(label()->SetSelectable(true));
1151   ASSERT_EQ(2u, GetLineCount());
1152 
1153   PerformMousePress(GetCursorPoint(2));
1154   PerformMouseDragTo(GetCursorPoint(0));
1155   EXPECT_STR_EQ("ab", GetSelectedText());
1156 
1157   PerformMouseDragTo(GetCursorPoint(7));
1158   EXPECT_STR_EQ("cd\nef", GetSelectedText());
1159 
1160   PerformMouseDragTo(gfx::Point(-5, GetCursorPoint(6).y()));
1161   EXPECT_STR_EQ("cd\n", GetSelectedText());
1162 
1163   PerformMouseDragTo(gfx::Point(100, GetCursorPoint(6).y()));
1164   EXPECT_STR_EQ("cd\nefgh", GetSelectedText());
1165 
1166   const gfx::Point points[] = {
1167       {GetCursorPoint(1).x(), -5},   // NW.
1168       {GetCursorPoint(2).x(), -5},   // NORTH.
1169       {GetCursorPoint(3).x(), -5},   // NE.
1170       {GetCursorPoint(8).x(), 100},  // SE.
1171       {GetCursorPoint(7).x(), 100},  // SOUTH.
1172       {GetCursorPoint(6).x(), 100},  // SW.
1173   };
1174   constexpr const char* kExtendLeft = "ab";
1175   constexpr const char* kExtendRight = "cd\nefgh";
1176 
1177   // For multiline, N* extends left, S* extends right.
1178   PerformMouseDragTo(points[NW]);
1179   EXPECT_STR_EQ(kExtends ? kExtendLeft : "b", GetSelectedText());
1180   PerformMouseDragTo(points[NORTH]);
1181   EXPECT_STR_EQ(kExtends ? kExtendLeft : "", GetSelectedText());
1182   PerformMouseDragTo(points[NE]);
1183   EXPECT_STR_EQ(kExtends ? kExtendLeft : "c", GetSelectedText());
1184   PerformMouseDragTo(points[SE]);
1185   EXPECT_STR_EQ(kExtends ? kExtendRight : "cd\nefg", GetSelectedText());
1186   PerformMouseDragTo(points[SOUTH]);
1187   EXPECT_STR_EQ(kExtends ? kExtendRight : "cd\nef", GetSelectedText());
1188   PerformMouseDragTo(points[SW]);
1189   EXPECT_STR_EQ(kExtends ? kExtendRight : "cd\ne", GetSelectedText());
1190 }
1191 
1192 // Single line fields consider the x offset as well. Ties go to the right.
TEST_F(LabelSelectionTest,MouseDragSingleLineLTR)1193 TEST_F(LabelSelectionTest, MouseDragSingleLineLTR) {
1194   label()->SetText(ASCIIToUTF16("abcdef"));
1195   label()->SizeToPreferredSize();
1196   ASSERT_TRUE(label()->SetSelectable(true));
1197   PerformMousePress(GetCursorPoint(2));
1198   const gfx::Point points[] = {
1199       {GetCursorPoint(1).x(), -5},   // NW.
1200       {GetCursorPoint(2).x(), -5},   // NORTH.
1201       {GetCursorPoint(3).x(), -5},   // NE.
1202       {GetCursorPoint(3).x(), 100},  // SE.
1203       {GetCursorPoint(2).x(), 100},  // SOUTH.
1204       {GetCursorPoint(1).x(), 100},  // SW.
1205   };
1206   constexpr const char* kExtendLeft = "ab";
1207   constexpr const char* kExtendRight = "cdef";
1208 
1209   // For single line, western directions extend left, all others extend right.
1210   PerformMouseDragTo(points[NW]);
1211   EXPECT_STR_EQ(kExtends ? kExtendLeft : "b", GetSelectedText());
1212   PerformMouseDragTo(points[NORTH]);
1213   EXPECT_STR_EQ(kExtends ? kExtendRight : "", GetSelectedText());
1214   PerformMouseDragTo(points[NE]);
1215   EXPECT_STR_EQ(kExtends ? kExtendRight : "c", GetSelectedText());
1216   PerformMouseDragTo(points[SE]);
1217   EXPECT_STR_EQ(kExtends ? kExtendRight : "c", GetSelectedText());
1218   PerformMouseDragTo(points[SOUTH]);
1219   EXPECT_STR_EQ(kExtends ? kExtendRight : "", GetSelectedText());
1220   PerformMouseDragTo(points[SW]);
1221   EXPECT_STR_EQ(kExtends ? kExtendLeft : "b", GetSelectedText());
1222 }
1223 
TEST_F(LabelSelectionTest,MouseDragMultilineRTL)1224 TEST_F(LabelSelectionTest, MouseDragMultilineRTL) {
1225   label()->SetMultiLine(true);
1226   label()->SetText(ToRTL("012\n345"));
1227   // Sanity check.
1228   EXPECT_EQ(WideToUTF16(L"\x5d0\x5d1\x5d2\n\x5d3\x5d4\x5d5"),
1229             label()->GetText());
1230 
1231   label()->SizeToPreferredSize();
1232   ASSERT_TRUE(label()->SetSelectable(true));
1233   ASSERT_EQ(2u, GetLineCount());
1234 
1235   PerformMousePress(GetCursorPoint(1));  // Note: RTL drag starts at 1, not 2.
1236   PerformMouseDragTo(GetCursorPoint(0));
1237   EXPECT_EQ(ToRTL("0"), GetSelectedText());
1238 
1239   PerformMouseDragTo(GetCursorPoint(6));
1240   EXPECT_EQ(ToRTL("12\n34"), GetSelectedText());
1241 
1242   PerformMouseDragTo(gfx::Point(-5, GetCursorPoint(6).y()));
1243   EXPECT_EQ(ToRTL("12\n345"), GetSelectedText());
1244 
1245   PerformMouseDragTo(gfx::Point(100, GetCursorPoint(6).y()));
1246   EXPECT_EQ(ToRTL("12\n"), GetSelectedText());
1247 
1248   const gfx::Point points[] = {
1249       {GetCursorPoint(2).x(), -5},   // NW: Now towards the end of the string.
1250       {GetCursorPoint(1).x(), -5},   // NORTH,
1251       {GetCursorPoint(0).x(), -5},   // NE: Towards the start.
1252       {GetCursorPoint(4).x(), 100},  // SE.
1253       {GetCursorPoint(5).x(), 100},  // SOUTH.
1254       {GetCursorPoint(6).x(), 100},  // SW.
1255   };
1256 
1257   // Visual right, so to the beginning of the string for RTL.
1258   const base::string16 extend_right = ToRTL("0");
1259   const base::string16 extend_left = ToRTL("12\n345");
1260 
1261   // For multiline, N* extends right, S* extends left.
1262   PerformMouseDragTo(points[NW]);
1263   EXPECT_EQ(kExtends ? extend_right : ToRTL("1"), GetSelectedText());
1264   PerformMouseDragTo(points[NORTH]);
1265   EXPECT_EQ(kExtends ? extend_right : ToRTL(""), GetSelectedText());
1266   PerformMouseDragTo(points[NE]);
1267   EXPECT_EQ(kExtends ? extend_right : ToRTL("0"), GetSelectedText());
1268   PerformMouseDragTo(points[SE]);
1269   EXPECT_EQ(kExtends ? extend_left : ToRTL("12\n"), GetSelectedText());
1270   PerformMouseDragTo(points[SOUTH]);
1271   EXPECT_EQ(kExtends ? extend_left : ToRTL("12\n3"), GetSelectedText());
1272   PerformMouseDragTo(points[SW]);
1273   EXPECT_EQ(kExtends ? extend_left : ToRTL("12\n34"), GetSelectedText());
1274 }
1275 
TEST_F(LabelSelectionTest,MouseDragSingleLineRTL)1276 TEST_F(LabelSelectionTest, MouseDragSingleLineRTL) {
1277   label()->SetText(ToRTL("0123456"));
1278   label()->SizeToPreferredSize();
1279   ASSERT_TRUE(label()->SetSelectable(true));
1280 
1281   PerformMousePress(GetCursorPoint(1));
1282   const gfx::Point points[] = {
1283       {GetCursorPoint(2).x(), -5},   // NW.
1284       {GetCursorPoint(1).x(), -5},   // NORTH.
1285       {GetCursorPoint(0).x(), -5},   // NE.
1286       {GetCursorPoint(0).x(), 100},  // SE.
1287       {GetCursorPoint(1).x(), 100},  // SOUTH.
1288       {GetCursorPoint(2).x(), 100},  // SW.
1289   };
1290 
1291   // Visual right, so to the beginning of the string for RTL.
1292   const base::string16 extend_right = ToRTL("0");
1293   const base::string16 extend_left = ToRTL("123456");
1294 
1295   // For single line, western directions extend left, all others extend right.
1296   PerformMouseDragTo(points[NW]);
1297   EXPECT_EQ(kExtends ? extend_left : ToRTL("1"), GetSelectedText());
1298   PerformMouseDragTo(points[NORTH]);
1299   EXPECT_EQ(kExtends ? extend_right : ToRTL(""), GetSelectedText());
1300   PerformMouseDragTo(points[NE]);
1301   EXPECT_EQ(kExtends ? extend_right : ToRTL("0"), GetSelectedText());
1302   PerformMouseDragTo(points[SE]);
1303   EXPECT_EQ(kExtends ? extend_right : ToRTL("0"), GetSelectedText());
1304   PerformMouseDragTo(points[SOUTH]);
1305   EXPECT_EQ(kExtends ? extend_right : ToRTL(""), GetSelectedText());
1306   PerformMouseDragTo(points[SW]);
1307   EXPECT_EQ(kExtends ? extend_left : ToRTL("1"), GetSelectedText());
1308 }
1309 
1310 // Verify the initially selected word on a double click, remains selected on
1311 // mouse dragging.
TEST_F(LabelSelectionTest,MouseDragWord)1312 TEST_F(LabelSelectionTest, MouseDragWord) {
1313   label()->SetText(ASCIIToUTF16("Label drag word"));
1314   label()->SizeToPreferredSize();
1315   ASSERT_TRUE(label()->SetSelectable(true));
1316 
1317   PerformClick(GetCursorPoint(8));
1318   PerformMousePress(GetCursorPoint(8));
1319   EXPECT_STR_EQ("drag", GetSelectedText());
1320 
1321   PerformMouseDragTo(GetCursorPoint(0));
1322   EXPECT_STR_EQ("Label drag", GetSelectedText());
1323 
1324   PerformMouseDragTo(gfx::Point(200, GetCursorPoint(0).y()));
1325   PerformMouseRelease(gfx::Point(200, GetCursorPoint(0).y()));
1326   EXPECT_STR_EQ("drag word", GetSelectedText());
1327 }
1328 
1329 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
1330 // Verify selection clipboard behavior on text selection.
TEST_F(LabelSelectionTest,SelectionClipboard)1331 TEST_F(LabelSelectionTest, SelectionClipboard) {
1332   label()->SetText(ASCIIToUTF16("Label selection clipboard"));
1333   label()->SizeToPreferredSize();
1334   ASSERT_TRUE(label()->SetSelectable(true));
1335 
1336   // Verify programmatic modification of selection, does not modify the
1337   // selection clipboard.
1338   label()->SelectRange(gfx::Range(2, 5));
1339   EXPECT_STR_EQ("bel", GetSelectedText());
1340   EXPECT_TRUE(GetClipboardText(ui::ClipboardBuffer::kSelection).empty());
1341 
1342   // Verify text selection using the mouse updates the selection clipboard.
1343   PerformMousePress(GetCursorPoint(5));
1344   PerformMouseDragTo(GetCursorPoint(0));
1345   PerformMouseRelease(GetCursorPoint(0));
1346   EXPECT_STR_EQ("Label", GetSelectedText());
1347   EXPECT_STR_EQ("Label", GetClipboardText(ui::ClipboardBuffer::kSelection));
1348 }
1349 #endif
1350 
1351 // Verify that keyboard shortcuts for Copy and Select All work when a selectable
1352 // label is focused.
TEST_F(LabelSelectionTest,KeyboardActions)1353 TEST_F(LabelSelectionTest, KeyboardActions) {
1354   const base::string16 initial_text = ASCIIToUTF16("Label keyboard actions");
1355   label()->SetText(initial_text);
1356   label()->SizeToPreferredSize();
1357   ASSERT_TRUE(label()->SetSelectable(true));
1358 
1359   PerformClick(gfx::Point());
1360   EXPECT_EQ(label(), GetFocusedView());
1361 
1362   event_generator()->PressKey(ui::VKEY_A, kControlCommandModifier);
1363   EXPECT_EQ(initial_text, GetSelectedText());
1364 
1365   event_generator()->PressKey(ui::VKEY_C, kControlCommandModifier);
1366   EXPECT_EQ(initial_text, GetClipboardText(ui::ClipboardBuffer::kCopyPaste));
1367 
1368   // The selection should get cleared on changing the text, but focus should not
1369   // be affected.
1370   const base::string16 new_text = ASCIIToUTF16("Label obscured text");
1371   label()->SetText(new_text);
1372   EXPECT_FALSE(label()->HasSelection());
1373   EXPECT_EQ(label(), GetFocusedView());
1374 
1375   // Obscured labels do not support text selection.
1376   label()->SetObscured(true);
1377   EXPECT_FALSE(label()->GetSelectable());
1378   event_generator()->PressKey(ui::VKEY_A, kControlCommandModifier);
1379   EXPECT_EQ(base::string16(), GetSelectedText());
1380 }
1381 
1382 // Verify the context menu options are enabled and disabled appropriately.
TEST_F(LabelSelectionTest,ContextMenuContents)1383 TEST_F(LabelSelectionTest, ContextMenuContents) {
1384   label()->SetText(ASCIIToUTF16("Label context menu"));
1385   label()->SizeToPreferredSize();
1386 
1387   // A non-selectable label would not show a context menu and both COPY and
1388   // SELECT_ALL context menu items should be disabled for it.
1389   EXPECT_FALSE(IsMenuCommandEnabled(IDS_APP_COPY));
1390   EXPECT_FALSE(IsMenuCommandEnabled(IDS_APP_SELECT_ALL));
1391 
1392   // For a selectable label with no selection, only SELECT_ALL should be
1393   // enabled.
1394   ASSERT_TRUE(label()->SetSelectable(true));
1395   EXPECT_FALSE(IsMenuCommandEnabled(IDS_APP_COPY));
1396   EXPECT_TRUE(IsMenuCommandEnabled(IDS_APP_SELECT_ALL));
1397 
1398   // For a selectable label with a selection, both COPY and SELECT_ALL should be
1399   // enabled.
1400   label()->SelectRange(gfx::Range(0, 4));
1401   EXPECT_TRUE(IsMenuCommandEnabled(IDS_APP_COPY));
1402   EXPECT_TRUE(IsMenuCommandEnabled(IDS_APP_SELECT_ALL));
1403   // Ensure unsupported commands like PASTE are not enabled.
1404   EXPECT_FALSE(IsMenuCommandEnabled(IDS_APP_PASTE));
1405 
1406   // An obscured label would not show a context menu and both COPY and
1407   // SELECT_ALL should be disabled for it.
1408   label()->SetObscured(true);
1409   EXPECT_FALSE(label()->GetSelectable());
1410   EXPECT_FALSE(IsMenuCommandEnabled(IDS_APP_COPY));
1411   EXPECT_FALSE(IsMenuCommandEnabled(IDS_APP_SELECT_ALL));
1412   label()->SetObscured(false);
1413 
1414   // For an empty label, both COPY and SELECT_ALL should be disabled.
1415   label()->SetText(base::string16());
1416   ASSERT_TRUE(label()->SetSelectable(true));
1417   EXPECT_FALSE(IsMenuCommandEnabled(IDS_APP_COPY));
1418   EXPECT_FALSE(IsMenuCommandEnabled(IDS_APP_SELECT_ALL));
1419 }
1420 
1421 }  // namespace views
1422