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