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// Unit tests for eliding and formatting utility functions.
6
7#include "ui/gfx/text_elider.h"
8
9#include "base/files/file_path.h"
10#include "base/i18n/rtl.h"
11#include "base/memory/scoped_ptr.h"
12#include "base/strings/string_util.h"
13#include "base/strings/utf_string_conversions.h"
14#include "testing/gtest/include/gtest/gtest.h"
15#include "ui/gfx/font.h"
16#include "ui/gfx/font_list.h"
17#include "ui/gfx/text_utils.h"
18#include "url/gurl.h"
19
20namespace gfx {
21
22namespace {
23
24struct Testcase {
25  const std::string input;
26  const std::string output;
27};
28
29struct FileTestcase {
30  const base::FilePath::StringType input;
31  const std::string output;
32};
33
34struct UTF16Testcase {
35  const string16 input;
36  const string16 output;
37};
38
39struct TestData {
40  const std::string a;
41  const std::string b;
42  const int compare_result;
43};
44
45void RunUrlTest(Testcase* testcases, size_t num_testcases) {
46  static const FontList font_list;
47  for (size_t i = 0; i < num_testcases; ++i) {
48    const GURL url(testcases[i].input);
49    // Should we test with non-empty language list?
50    // That's kinda redundant with net_util_unittests.
51    const float available_width =
52        GetStringWidthF(UTF8ToUTF16(testcases[i].output), font_list);
53    EXPECT_EQ(UTF8ToUTF16(testcases[i].output),
54              ElideUrl(url, font_list, available_width, std::string()));
55  }
56}
57
58}  // namespace
59
60// TODO(ios): This test fails on iOS because iOS version of GetStringWidthF
61// that calls [NSString sizeWithFont] returns the rounded string width.
62#if defined(OS_IOS)
63#define MAYBE_ElideEmail DISABLED_ElideEmail
64#else
65#define MAYBE_ElideEmail ElideEmail
66#endif
67TEST(TextEliderTest, MAYBE_ElideEmail) {
68  const std::string kEllipsisStr(kEllipsis);
69
70  // Test emails and their expected elided forms (from which the available
71  // widths will be derived).
72  // For elided forms in which both the username and domain must be elided:
73  // the result (how many characters are left on each side) can be font
74  // dependent. To avoid this, the username is prefixed with the characters
75  // expected to remain in the domain.
76  Testcase testcases[] = {
77      {"g@g.c", "g@g.c"},
78      {"g@g.c", kEllipsisStr},
79      {"ga@co.ca", "ga@c" + kEllipsisStr + "a"},
80      {"short@small.com", "s" + kEllipsisStr + "@s" + kEllipsisStr},
81      {"short@small.com", "s" + kEllipsisStr + "@small.com"},
82      {"short@longbutlotsofspace.com", "short@longbutlotsofspace.com"},
83      {"short@longbutnotverymuchspace.com",
84       "short@long" + kEllipsisStr + ".com"},
85      {"la_short@longbutverytightspace.ca",
86       "la" + kEllipsisStr + "@l" + kEllipsisStr + "a"},
87      {"longusername@gmail.com", "long" + kEllipsisStr + "@gmail.com"},
88      {"elidetothemax@justfits.com", "e" + kEllipsisStr + "@justfits.com"},
89      {"thatom_somelongemail@thatdoesntfit.com",
90       "thatom" + kEllipsisStr + "@tha" + kEllipsisStr + "om"},
91      {"namefits@butthedomaindoesnt.com",
92       "namefits@butthedo" + kEllipsisStr + "snt.com"},
93      {"widthtootight@nospace.com", kEllipsisStr},
94      {"nospaceforusername@l", kEllipsisStr},
95      {"little@littlespace.com", "l" + kEllipsisStr + "@l" + kEllipsisStr},
96      {"l@llllllllllllllllllllllll.com", "l@lllll" + kEllipsisStr + ".com"},
97      {"messed\"up@whyanat\"++@notgoogley.com",
98       "messed\"up@whyanat\"++@notgoogley.com"},
99      {"messed\"up@whyanat\"++@notgoogley.com",
100       "messed\"up@why" + kEllipsisStr + "@notgoogley.com"},
101      {"noca_messed\"up@whyanat\"++@notgoogley.ca",
102       "noca" + kEllipsisStr + "@no" + kEllipsisStr + "ca"},
103      {"at\"@@@@@@@@@...@@.@.@.@@@\"@madness.com",
104       "at\"@@@@@@@@@...@@.@." + kEllipsisStr + "@madness.com"},
105      // Special case: "m..." takes more than half of the available width; thus
106      // the domain must elide to "l..." and not "l...l" as it must allow enough
107      // space for the minimal username elision although its half of the
108      // available width would normally allow it to elide to "l...l".
109      {"mmmmm@llllllllll", "m" + kEllipsisStr + "@l" + kEllipsisStr},
110  };
111
112  const FontList font_list;
113  for (size_t i = 0; i < arraysize(testcases); ++i) {
114    const string16 expected_output = UTF8ToUTF16(testcases[i].output);
115    EXPECT_EQ(expected_output,
116              ElideEmail(
117                  UTF8ToUTF16(testcases[i].input),
118                  font_list,
119                  GetStringWidthF(expected_output, font_list)));
120  }
121}
122
123TEST(TextEliderTest, ElideEmailMoreSpace) {
124  const int test_width_factors[] = {
125      100,
126      10000,
127      1000000,
128  };
129  const std::string test_emails[] = {
130      "a@c",
131      "test@email.com",
132      "short@verysuperdupperlongdomain.com",
133      "supermegalongusername@withasuperlonnnggggdomain.gouv.qc.ca",
134  };
135
136  const FontList font_list;
137  for (size_t i = 0; i < arraysize(test_width_factors); ++i) {
138    const int test_width =
139        font_list.GetExpectedTextWidth(test_width_factors[i]);
140    for (size_t j = 0; j < arraysize(test_emails); ++j) {
141      // Extra space is available: the email should not be elided.
142      const string16 test_email = UTF8ToUTF16(test_emails[j]);
143      EXPECT_EQ(test_email, ElideEmail(test_email, font_list, test_width));
144    }
145  }
146}
147
148// Test eliding of commonplace URLs.
149TEST(TextEliderTest, TestGeneralEliding) {
150  const std::string kEllipsisStr(kEllipsis);
151  Testcase testcases[] = {
152    {"http://www.google.com/intl/en/ads/",
153     "www.google.com/intl/en/ads/"},
154    {"http://www.google.com/intl/en/ads/", "www.google.com/intl/en/ads/"},
155    {"http://www.google.com/intl/en/ads/",
156     "google.com/intl/" + kEllipsisStr + "/ads/"},
157    {"http://www.google.com/intl/en/ads/",
158     "google.com/" + kEllipsisStr + "/ads/"},
159    {"http://www.google.com/intl/en/ads/", "google.com/" + kEllipsisStr},
160    {"http://www.google.com/intl/en/ads/", "goog" + kEllipsisStr},
161    {"https://subdomain.foo.com/bar/filename.html",
162     "subdomain.foo.com/bar/filename.html"},
163    {"https://subdomain.foo.com/bar/filename.html",
164     "subdomain.foo.com/" + kEllipsisStr + "/filename.html"},
165    {"http://subdomain.foo.com/bar/filename.html",
166     kEllipsisStr + "foo.com/" + kEllipsisStr + "/filename.html"},
167    {"http://www.google.com/intl/en/ads/?aLongQueryWhichIsNotRequired",
168     "www.google.com/intl/en/ads/?aLongQ" + kEllipsisStr},
169  };
170
171  RunUrlTest(testcases, arraysize(testcases));
172}
173
174// When there is very little space available, the elision code will shorten
175// both path AND file name to an ellipsis - ".../...". To avoid this result,
176// there is a hack in place that simply treats them as one string in this
177// case.
178TEST(TextEliderTest, TestTrailingEllipsisSlashEllipsisHack) {
179  const std::string kEllipsisStr(kEllipsis);
180
181  // Very little space, would cause double ellipsis.
182  FontList font_list;
183  GURL url("http://battersbox.com/directory/foo/peter_paul_and_mary.html");
184  float available_width = GetStringWidthF(
185      UTF8ToUTF16("battersbox.com/" + kEllipsisStr + "/" + kEllipsisStr),
186      font_list);
187
188  // Create the expected string, after elision. Depending on font size, the
189  // directory might become /dir... or /di... or/d... - it never should be
190  // shorter than that. (If it is, the font considers d... to be longer
191  // than .../... -  that should never happen).
192  ASSERT_GT(GetStringWidthF(UTF8ToUTF16(kEllipsisStr + "/" + kEllipsisStr),
193                            font_list),
194            GetStringWidthF(UTF8ToUTF16("d" + kEllipsisStr), font_list));
195  GURL long_url("http://battersbox.com/directorynameisreallylongtoforcetrunc");
196  string16 expected =
197      ElideUrl(long_url, font_list, available_width, std::string());
198  // Ensure that the expected result still contains part of the directory name.
199  ASSERT_GT(expected.length(), std::string("battersbox.com/d").length());
200  EXPECT_EQ(expected,
201             ElideUrl(url, font_list, available_width, std::string()));
202
203  // More space available - elide directories, partially elide filename.
204  Testcase testcases[] = {
205    {"http://battersbox.com/directory/foo/peter_paul_and_mary.html",
206     "battersbox.com/" + kEllipsisStr + "/peter" + kEllipsisStr},
207  };
208  RunUrlTest(testcases, arraysize(testcases));
209}
210
211// Test eliding of empty strings, URLs with ports, passwords, queries, etc.
212TEST(TextEliderTest, TestMoreEliding) {
213  const std::string kEllipsisStr(kEllipsis);
214  Testcase testcases[] = {
215    {"http://www.google.com/foo?bar", "www.google.com/foo?bar"},
216    {"http://xyz.google.com/foo?bar", "xyz.google.com/foo?" + kEllipsisStr},
217    {"http://xyz.google.com/foo?bar", "xyz.google.com/foo" + kEllipsisStr},
218    {"http://xyz.google.com/foo?bar", "xyz.google.com/fo" + kEllipsisStr},
219    {"http://a.b.com/pathname/c?d", "a.b.com/" + kEllipsisStr + "/c?d"},
220    {"", ""},
221    {"http://foo.bar..example.com...hello/test/filename.html",
222     "foo.bar..example.com...hello/" + kEllipsisStr + "/filename.html"},
223    {"http://foo.bar../", "foo.bar.."},
224    {"http://xn--1lq90i.cn/foo", "\xe5\x8c\x97\xe4\xba\xac.cn/foo"},
225    {"http://me:mypass@secrethost.com:99/foo?bar#baz",
226     "secrethost.com:99/foo?bar#baz"},
227    {"http://me:mypass@ss%xxfdsf.com/foo", "ss%25xxfdsf.com/foo"},
228    {"mailto:elgoato@elgoato.com", "mailto:elgoato@elgoato.com"},
229    {"javascript:click(0)", "javascript:click(0)"},
230    {"https://chess.eecs.berkeley.edu:4430/login/arbitfilename",
231     "chess.eecs.berkeley.edu:4430/login/arbitfilename"},
232    {"https://chess.eecs.berkeley.edu:4430/login/arbitfilename",
233     kEllipsisStr + "berkeley.edu:4430/" + kEllipsisStr + "/arbitfilename"},
234
235    // Unescaping.
236    {"http://www/%E4%BD%A0%E5%A5%BD?q=%E4%BD%A0%E5%A5%BD#\xe4\xbd\xa0",
237     "www/\xe4\xbd\xa0\xe5\xa5\xbd?q=\xe4\xbd\xa0\xe5\xa5\xbd#\xe4\xbd\xa0"},
238
239    // Invalid unescaping for path. The ref will always be valid UTF-8. We don't
240    // bother to do too many edge cases, since these are handled by the escaper
241    // unittest.
242    {"http://www/%E4%A0%E5%A5%BD?q=%E4%BD%A0%E5%A5%BD#\xe4\xbd\xa0",
243     "www/%E4%A0%E5%A5%BD?q=\xe4\xbd\xa0\xe5\xa5\xbd#\xe4\xbd\xa0"},
244  };
245
246  RunUrlTest(testcases, arraysize(testcases));
247}
248
249// Test eliding of file: URLs.
250TEST(TextEliderTest, TestFileURLEliding) {
251  const std::string kEllipsisStr(kEllipsis);
252  Testcase testcases[] = {
253    {"file:///C:/path1/path2/path3/filename",
254     "file:///C:/path1/path2/path3/filename"},
255    {"file:///C:/path1/path2/path3/filename",
256     "C:/path1/path2/path3/filename"},
257// GURL parses "file:///C:path" differently on windows than it does on posix.
258#if defined(OS_WIN)
259    {"file:///C:path1/path2/path3/filename",
260     "C:/path1/path2/" + kEllipsisStr + "/filename"},
261    {"file:///C:path1/path2/path3/filename",
262     "C:/path1/" + kEllipsisStr + "/filename"},
263    {"file:///C:path1/path2/path3/filename",
264     "C:/" + kEllipsisStr + "/filename"},
265#endif
266    {"file://filer/foo/bar/file", "filer/foo/bar/file"},
267    {"file://filer/foo/bar/file", "filer/foo/" + kEllipsisStr + "/file"},
268    {"file://filer/foo/bar/file", "filer/" + kEllipsisStr + "/file"},
269  };
270
271  RunUrlTest(testcases, arraysize(testcases));
272}
273
274// TODO(ios): This test fails on iOS because iOS version of GetStringWidthF
275// that calls [NSString sizeWithFont] returns the rounded string width.
276#if defined(OS_IOS)
277#define MAYBE_TestFilenameEliding DISABLED_TestFilenameEliding
278#else
279#define MAYBE_TestFilenameEliding TestFilenameEliding
280#endif
281TEST(TextEliderTest, MAYBE_TestFilenameEliding) {
282  const std::string kEllipsisStr(kEllipsis);
283  const base::FilePath::StringType kPathSeparator =
284      base::FilePath::StringType().append(1, base::FilePath::kSeparators[0]);
285
286  FileTestcase testcases[] = {
287    {FILE_PATH_LITERAL(""), ""},
288    {FILE_PATH_LITERAL("."), "."},
289    {FILE_PATH_LITERAL("filename.exe"), "filename.exe"},
290    {FILE_PATH_LITERAL(".longext"), ".longext"},
291    {FILE_PATH_LITERAL("pie"), "pie"},
292    {FILE_PATH_LITERAL("c:") + kPathSeparator + FILE_PATH_LITERAL("path") +
293      kPathSeparator + FILE_PATH_LITERAL("filename.pie"),
294      "filename.pie"},
295    {FILE_PATH_LITERAL("c:") + kPathSeparator + FILE_PATH_LITERAL("path") +
296      kPathSeparator + FILE_PATH_LITERAL("longfilename.pie"),
297      "long" + kEllipsisStr + ".pie"},
298    {FILE_PATH_LITERAL("http://path.com/filename.pie"), "filename.pie"},
299    {FILE_PATH_LITERAL("http://path.com/longfilename.pie"),
300      "long" + kEllipsisStr + ".pie"},
301    {FILE_PATH_LITERAL("piesmashingtacularpants"), "pie" + kEllipsisStr},
302    {FILE_PATH_LITERAL(".piesmashingtacularpants"), ".pie" + kEllipsisStr},
303    {FILE_PATH_LITERAL("cheese."), "cheese."},
304    {FILE_PATH_LITERAL("file name.longext"),
305      "file" + kEllipsisStr + ".longext"},
306    {FILE_PATH_LITERAL("fil ename.longext"),
307      "fil " + kEllipsisStr + ".longext"},
308    {FILE_PATH_LITERAL("filename.longext"),
309      "file" + kEllipsisStr + ".longext"},
310    {FILE_PATH_LITERAL("filename.middleext.longext"),
311      "filename.mid" + kEllipsisStr + ".longext"},
312    {FILE_PATH_LITERAL("filename.superduperextremelylongext"),
313      "filename.sup" + kEllipsisStr + "emelylongext"},
314    {FILE_PATH_LITERAL("filenamereallylongtext.superduperextremelylongext"),
315      "filenamereall" + kEllipsisStr + "emelylongext"},
316    {FILE_PATH_LITERAL("file.name.really.long.text.superduperextremelylongext"),
317      "file.name.re" + kEllipsisStr + "emelylongext"}
318  };
319
320  static const FontList font_list;
321  for (size_t i = 0; i < arraysize(testcases); ++i) {
322    base::FilePath filepath(testcases[i].input);
323    string16 expected = UTF8ToUTF16(testcases[i].output);
324    expected = base::i18n::GetDisplayStringInLTRDirectionality(expected);
325    EXPECT_EQ(expected, ElideFilename(filepath, font_list,
326        GetStringWidthF(UTF8ToUTF16(testcases[i].output), font_list)));
327  }
328}
329
330TEST(TextEliderTest, ElideTextTruncate) {
331  const FontList font_list;
332  const float kTestWidth = GetStringWidthF(ASCIIToUTF16("Test"), font_list);
333  struct TestData {
334    const char* input;
335    float width;
336    const char* output;
337  } cases[] = {
338    { "", 0, "" },
339    { "Test", 0, "" },
340    { "", kTestWidth, "" },
341    { "Tes", kTestWidth, "Tes" },
342    { "Test", kTestWidth, "Test" },
343    { "Tests", kTestWidth, "Test" },
344  };
345
346  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
347    string16 result = ElideText(UTF8ToUTF16(cases[i].input), font_list,
348                                cases[i].width, TRUNCATE_AT_END);
349    EXPECT_EQ(cases[i].output, UTF16ToUTF8(result));
350  }
351}
352
353TEST(TextEliderTest, ElideTextEllipsis) {
354  const FontList font_list;
355  const float kTestWidth = GetStringWidthF(ASCIIToUTF16("Test"), font_list);
356  const char* kEllipsis = "\xE2\x80\xA6";
357  const float kEllipsisWidth =
358      GetStringWidthF(UTF8ToUTF16(kEllipsis), font_list);
359  struct TestData {
360    const char* input;
361    float width;
362    const char* output;
363  } cases[] = {
364    { "", 0, "" },
365    { "Test", 0, "" },
366    { "Test", kEllipsisWidth, kEllipsis },
367    { "", kTestWidth, "" },
368    { "Tes", kTestWidth, "Tes" },
369    { "Test", kTestWidth, "Test" },
370  };
371
372  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
373    string16 result = ElideText(UTF8ToUTF16(cases[i].input), font_list,
374                                cases[i].width, ELIDE_AT_END);
375    EXPECT_EQ(cases[i].output, UTF16ToUTF8(result));
376  }
377}
378
379// Checks that all occurrences of |first_char| are followed by |second_char| and
380// all occurrences of |second_char| are preceded by |first_char| in |text|.
381static void CheckSurrogatePairs(const string16& text,
382                                char16 first_char,
383                                char16 second_char) {
384  size_t index = text.find_first_of(first_char);
385  while (index != string16::npos) {
386    EXPECT_LT(index, text.length() - 1);
387    EXPECT_EQ(second_char, text[index + 1]);
388    index = text.find_first_of(first_char, index + 1);
389  }
390  index = text.find_first_of(second_char);
391  while (index != string16::npos) {
392    EXPECT_GT(index, 0U);
393    EXPECT_EQ(first_char, text[index - 1]);
394    index = text.find_first_of(second_char, index + 1);
395  }
396}
397
398TEST(TextEliderTest, ElideTextSurrogatePairs) {
399  const FontList font_list;
400  // The below is 'MUSICAL SYMBOL G CLEF', which is represented in UTF-16 as
401  // two characters forming a surrogate pair 0x0001D11E.
402  const std::string kSurrogate = "\xF0\x9D\x84\x9E";
403  const string16 kTestString =
404      UTF8ToUTF16(kSurrogate + "ab" + kSurrogate + kSurrogate + "cd");
405  const float kTestStringWidth = GetStringWidthF(kTestString, font_list);
406  const char16 kSurrogateFirstChar = kTestString[0];
407  const char16 kSurrogateSecondChar = kTestString[1];
408  string16 result;
409
410  // Elide |kTextString| to all possible widths and check that no instance of
411  // |kSurrogate| was split in two.
412  for (float width = 0; width <= kTestStringWidth; width++) {
413    result = ElideText(kTestString, font_list, width, TRUNCATE_AT_END);
414    CheckSurrogatePairs(result, kSurrogateFirstChar, kSurrogateSecondChar);
415
416    result = ElideText(kTestString, font_list, width, ELIDE_AT_END);
417    CheckSurrogatePairs(result, kSurrogateFirstChar, kSurrogateSecondChar);
418
419    result = ElideText(kTestString, font_list, width, ELIDE_IN_MIDDLE);
420    CheckSurrogatePairs(result, kSurrogateFirstChar, kSurrogateSecondChar);
421  }
422}
423
424TEST(TextEliderTest, ElideTextLongStrings) {
425  const string16 kEllipsisStr = UTF8ToUTF16(kEllipsis);
426  string16 data_scheme(UTF8ToUTF16("data:text/plain,"));
427  size_t data_scheme_length = data_scheme.length();
428
429  string16 ten_a(10, 'a');
430  string16 hundred_a(100, 'a');
431  string16 thousand_a(1000, 'a');
432  string16 ten_thousand_a(10000, 'a');
433  string16 hundred_thousand_a(100000, 'a');
434  string16 million_a(1000000, 'a');
435
436  size_t number_of_as = 156;
437  string16 long_string_end(
438      data_scheme + string16(number_of_as, 'a') + kEllipsisStr);
439  UTF16Testcase testcases_end[] = {
440     {data_scheme + ten_a,              data_scheme + ten_a},
441     {data_scheme + hundred_a,          data_scheme + hundred_a},
442     {data_scheme + thousand_a,         long_string_end},
443     {data_scheme + ten_thousand_a,     long_string_end},
444     {data_scheme + hundred_thousand_a, long_string_end},
445     {data_scheme + million_a,          long_string_end},
446  };
447
448  const FontList font_list;
449  float ellipsis_width = GetStringWidthF(kEllipsisStr, font_list);
450  for (size_t i = 0; i < arraysize(testcases_end); ++i) {
451    // Compare sizes rather than actual contents because if the test fails,
452    // output is rather long.
453    EXPECT_EQ(testcases_end[i].output.size(),
454              ElideText(
455                  testcases_end[i].input,
456                  font_list,
457                  GetStringWidthF(testcases_end[i].output, font_list),
458                  ELIDE_AT_END).size());
459    EXPECT_EQ(kEllipsisStr,
460              ElideText(testcases_end[i].input, font_list, ellipsis_width,
461                        ELIDE_AT_END));
462  }
463
464  size_t number_of_trailing_as = (data_scheme_length + number_of_as) / 2;
465  string16 long_string_middle(data_scheme +
466      string16(number_of_as - number_of_trailing_as, 'a') + kEllipsisStr +
467      string16(number_of_trailing_as, 'a'));
468  UTF16Testcase testcases_middle[] = {
469     {data_scheme + ten_a,              data_scheme + ten_a},
470     {data_scheme + hundred_a,          data_scheme + hundred_a},
471     {data_scheme + thousand_a,         long_string_middle},
472     {data_scheme + ten_thousand_a,     long_string_middle},
473     {data_scheme + hundred_thousand_a, long_string_middle},
474     {data_scheme + million_a,          long_string_middle},
475  };
476
477  for (size_t i = 0; i < arraysize(testcases_middle); ++i) {
478    // Compare sizes rather than actual contents because if the test fails,
479    // output is rather long.
480    EXPECT_EQ(testcases_middle[i].output.size(),
481              ElideText(
482                  testcases_middle[i].input,
483                  font_list,
484                  GetStringWidthF(testcases_middle[i].output, font_list),
485                  ELIDE_AT_END).size());
486    EXPECT_EQ(kEllipsisStr,
487              ElideText(testcases_middle[i].input, font_list, ellipsis_width,
488                        ELIDE_AT_END));
489  }
490}
491
492// Verifies display_url is set correctly.
493TEST(TextEliderTest, SortedDisplayURL) {
494  SortedDisplayURL d_url(GURL("http://www.google.com"), std::string());
495  EXPECT_EQ("www.google.com", UTF16ToASCII(d_url.display_url()));
496}
497
498// Verifies DisplayURL::Compare works correctly.
499TEST(TextEliderTest, SortedDisplayURLCompare) {
500  UErrorCode create_status = U_ZERO_ERROR;
501  scoped_ptr<icu::Collator> collator(
502      icu::Collator::createInstance(create_status));
503  if (!U_SUCCESS(create_status))
504    return;
505
506  TestData tests[] = {
507    // IDN comparison. Hosts equal, so compares on path.
508    { "http://xn--1lq90i.cn/a", "http://xn--1lq90i.cn/b", -1},
509
510    // Because the host and after host match, this compares the full url.
511    { "http://www.x/b", "http://x/b", -1 },
512
513    // Because the host and after host match, this compares the full url.
514    { "http://www.a:1/b", "http://a:1/b", 1 },
515
516    // The hosts match, so these end up comparing on the after host portion.
517    { "http://www.x:0/b", "http://x:1/b", -1 },
518    { "http://www.x/a", "http://x/b", -1 },
519    { "http://x/b", "http://www.x/a", 1 },
520
521    // Trivial Equality.
522    { "http://a/", "http://a/", 0 },
523
524    // Compares just hosts.
525    { "http://www.a/", "http://b/", -1 },
526  };
527
528  for (size_t i = 0; i < arraysize(tests); ++i) {
529    SortedDisplayURL url1(GURL(tests[i].a), std::string());
530    SortedDisplayURL url2(GURL(tests[i].b), std::string());
531    EXPECT_EQ(tests[i].compare_result, url1.Compare(url2, collator.get()));
532    EXPECT_EQ(-tests[i].compare_result, url2.Compare(url1, collator.get()));
533  }
534}
535
536TEST(TextEliderTest, ElideString) {
537  struct TestData {
538    const char* input;
539    int max_len;
540    bool result;
541    const char* output;
542  } cases[] = {
543    { "Hello", 0, true, "" },
544    { "", 0, false, "" },
545    { "Hello, my name is Tom", 1, true, "H" },
546    { "Hello, my name is Tom", 2, true, "He" },
547    { "Hello, my name is Tom", 3, true, "H.m" },
548    { "Hello, my name is Tom", 4, true, "H..m" },
549    { "Hello, my name is Tom", 5, true, "H...m" },
550    { "Hello, my name is Tom", 6, true, "He...m" },
551    { "Hello, my name is Tom", 7, true, "He...om" },
552    { "Hello, my name is Tom", 10, true, "Hell...Tom" },
553    { "Hello, my name is Tom", 100, false, "Hello, my name is Tom" }
554  };
555  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
556    string16 output;
557    EXPECT_EQ(cases[i].result,
558              ElideString(UTF8ToUTF16(cases[i].input),
559                          cases[i].max_len, &output));
560    EXPECT_EQ(cases[i].output, UTF16ToUTF8(output));
561  }
562}
563
564TEST(TextEliderTest, ElideRectangleText) {
565  const FontList font_list;
566  const int line_height = font_list.GetHeight();
567  const float test_width = GetStringWidthF(ASCIIToUTF16("Test"), font_list);
568
569  struct TestData {
570    const char* input;
571    float available_pixel_width;
572    int available_pixel_height;
573    bool truncated_y;
574    const char* output;
575  } cases[] = {
576    { "", 0, 0, false, NULL },
577    { "", 1, 1, false, NULL },
578    { "Test", test_width, 0, true, NULL },
579    { "Test", test_width, 1, false, "Test" },
580    { "Test", test_width, line_height, false, "Test" },
581    { "Test Test", test_width, line_height, true, "Test" },
582    { "Test Test", test_width, line_height + 1, false, "Test|Test" },
583    { "Test Test", test_width, line_height * 2, false, "Test|Test" },
584    { "Test Test", test_width, line_height * 3, false, "Test|Test" },
585    { "Test Test", test_width * 2, line_height * 2, false, "Test|Test" },
586    { "Test Test", test_width * 3, line_height, false, "Test Test" },
587    { "Test\nTest", test_width * 3, line_height * 2, false, "Test|Test" },
588    { "Te\nst Te", test_width, line_height * 3, false, "Te|st|Te" },
589    { "\nTest", test_width, line_height * 2, false, "|Test" },
590    { "\nTest", test_width, line_height, true, "" },
591    { "\n\nTest", test_width, line_height * 3, false, "||Test" },
592    { "\n\nTest", test_width, line_height * 2, true, "|" },
593    { "Test\n", 2 * test_width, line_height * 5, false, "Test|" },
594    { "Test\n\n", 2 * test_width, line_height * 5, false, "Test||" },
595    { "Test\n\n\n", 2 * test_width, line_height * 5, false, "Test|||" },
596    { "Test\nTest\n\n", 2 * test_width, line_height * 5, false, "Test|Test||" },
597    { "Test\n\nTest\n", 2 * test_width, line_height * 5, false, "Test||Test|" },
598    { "Test\n\n\nTest", 2 * test_width, line_height * 5, false, "Test|||Test" },
599    { "Te ", test_width, line_height, false, "Te" },
600    { "Te  Te Test", test_width, 3 * line_height, false, "Te|Te|Test" },
601  };
602
603  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
604    std::vector<string16> lines;
605    EXPECT_EQ(cases[i].truncated_y ? INSUFFICIENT_SPACE_VERTICAL : 0,
606              ElideRectangleText(UTF8ToUTF16(cases[i].input),
607                                 font_list,
608                                 cases[i].available_pixel_width,
609                                 cases[i].available_pixel_height,
610                                 TRUNCATE_LONG_WORDS,
611                                 &lines));
612    if (cases[i].output) {
613      const std::string result = UTF16ToUTF8(JoinString(lines, '|'));
614      EXPECT_EQ(cases[i].output, result) << "Case " << i << " failed!";
615    } else {
616      EXPECT_TRUE(lines.empty()) << "Case " << i << " failed!";
617    }
618  }
619}
620
621TEST(TextEliderTest, ElideRectangleTextPunctuation) {
622  const FontList font_list;
623  const int line_height = font_list.GetHeight();
624  const float test_width = GetStringWidthF(ASCIIToUTF16("Test"), font_list);
625  const float test_t_width = GetStringWidthF(ASCIIToUTF16("Test T"), font_list);
626
627  struct TestData {
628    const char* input;
629    float available_pixel_width;
630    int available_pixel_height;
631    bool wrap_words;
632    bool truncated_x;
633    const char* output;
634  } cases[] = {
635    { "Test T.", test_t_width, line_height * 2, false, false, "Test|T." },
636    { "Test T ?", test_t_width, line_height * 2, false, false, "Test|T ?" },
637    { "Test. Test", test_width, line_height * 3, false, true, "Test|Test" },
638    { "Test. Test", test_width, line_height * 3, true, false, "Test|.|Test" },
639  };
640
641  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
642    std::vector<string16> lines;
643    const WordWrapBehavior wrap_behavior =
644        (cases[i].wrap_words ? WRAP_LONG_WORDS : TRUNCATE_LONG_WORDS);
645    EXPECT_EQ(cases[i].truncated_x ? INSUFFICIENT_SPACE_HORIZONTAL : 0,
646              ElideRectangleText(UTF8ToUTF16(cases[i].input),
647                                 font_list,
648                                 cases[i].available_pixel_width,
649                                 cases[i].available_pixel_height,
650                                 wrap_behavior,
651                                 &lines));
652    if (cases[i].output) {
653      const std::string result = UTF16ToUTF8(JoinString(lines, '|'));
654      EXPECT_EQ(cases[i].output, result) << "Case " << i << " failed!";
655    } else {
656      EXPECT_TRUE(lines.empty()) << "Case " << i << " failed!";
657    }
658  }
659}
660
661TEST(TextEliderTest, ElideRectangleTextLongWords) {
662  const FontList font_list;
663  const int kAvailableHeight = 1000;
664  const string16 kElidedTesting = UTF8ToUTF16(std::string("Tes") + kEllipsis);
665  const float elided_width = GetStringWidthF(kElidedTesting, font_list);
666  const float test_width = GetStringWidthF(ASCIIToUTF16("Test"), font_list);
667
668  struct TestData {
669    const char* input;
670    float available_pixel_width;
671    WordWrapBehavior wrap_behavior;
672    bool truncated_x;
673    const char* output;
674  } cases[] = {
675    { "Testing", test_width, IGNORE_LONG_WORDS, false, "Testing" },
676    { "X Testing", test_width, IGNORE_LONG_WORDS, false, "X|Testing" },
677    { "Test Testing", test_width, IGNORE_LONG_WORDS, false, "Test|Testing" },
678    { "Test\nTesting", test_width, IGNORE_LONG_WORDS, false, "Test|Testing" },
679    { "Test Tests ", test_width, IGNORE_LONG_WORDS, false, "Test|Tests" },
680    { "Test Tests T", test_width, IGNORE_LONG_WORDS, false, "Test|Tests|T" },
681
682    { "Testing", elided_width, ELIDE_LONG_WORDS, true, "Tes..." },
683    { "X Testing", elided_width, ELIDE_LONG_WORDS, true, "X|Tes..." },
684    { "Test Testing", elided_width, ELIDE_LONG_WORDS, true, "Test|Tes..." },
685    { "Test\nTesting", elided_width, ELIDE_LONG_WORDS, true, "Test|Tes..." },
686
687    { "Testing", test_width, TRUNCATE_LONG_WORDS, true, "Test" },
688    { "X Testing", test_width, TRUNCATE_LONG_WORDS, true, "X|Test" },
689    { "Test Testing", test_width, TRUNCATE_LONG_WORDS, true, "Test|Test" },
690    { "Test\nTesting", test_width, TRUNCATE_LONG_WORDS, true, "Test|Test" },
691    { "Test Tests ", test_width, TRUNCATE_LONG_WORDS, true, "Test|Test" },
692    { "Test Tests T", test_width, TRUNCATE_LONG_WORDS, true, "Test|Test|T" },
693
694    { "Testing", test_width, WRAP_LONG_WORDS, false, "Test|ing" },
695    { "X Testing", test_width, WRAP_LONG_WORDS, false, "X|Test|ing" },
696    { "Test Testing", test_width, WRAP_LONG_WORDS, false, "Test|Test|ing" },
697    { "Test\nTesting", test_width, WRAP_LONG_WORDS, false, "Test|Test|ing" },
698    { "Test Tests ", test_width, WRAP_LONG_WORDS, false, "Test|Test|s" },
699    { "Test Tests T", test_width, WRAP_LONG_WORDS, false, "Test|Test|s T" },
700    { "TestTestTest", test_width, WRAP_LONG_WORDS, false, "Test|Test|Test" },
701    { "TestTestTestT", test_width, WRAP_LONG_WORDS, false, "Test|Test|Test|T" },
702  };
703
704  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
705    std::vector<string16> lines;
706    EXPECT_EQ(cases[i].truncated_x ? INSUFFICIENT_SPACE_HORIZONTAL : 0,
707              ElideRectangleText(UTF8ToUTF16(cases[i].input),
708                                 font_list,
709                                 cases[i].available_pixel_width,
710                                 kAvailableHeight,
711                                 cases[i].wrap_behavior,
712                                 &lines));
713    std::string expected_output(cases[i].output);
714    ReplaceSubstringsAfterOffset(&expected_output, 0, "...", kEllipsis);
715    const std::string result = UTF16ToUTF8(JoinString(lines, '|'));
716    EXPECT_EQ(expected_output, result) << "Case " << i << " failed!";
717  }
718}
719
720// This test is to make sure that the width of each wrapped line does not
721// exceed the available width. On some platform like Mac, this test used to
722// fail because the truncated integer width is returned for the string
723// and the accumulation of the truncated values causes the elide function
724// to wrap incorrectly.
725TEST(TextEliderTest, ElideRectangleTextCheckLineWidth) {
726  FontList font_list;
727#if defined(OS_MACOSX) && !defined(OS_IOS)
728  // Use a specific font to expose the line width exceeding problem.
729  font_list = FontList(Font("LucidaGrande", 12));
730#endif
731  const float kAvailableWidth = 235;
732  const int kAvailableHeight = 1000;
733  const char text[] = "that Russian place we used to go to after fencing";
734  std::vector<string16> lines;
735  EXPECT_EQ(0, ElideRectangleText(UTF8ToUTF16(text),
736                                  font_list,
737                                  kAvailableWidth,
738                                  kAvailableHeight,
739                                  WRAP_LONG_WORDS,
740                                  &lines));
741  ASSERT_EQ(2u, lines.size());
742  EXPECT_LE(GetStringWidthF(lines[0], font_list), kAvailableWidth);
743  EXPECT_LE(GetStringWidthF(lines[1], font_list), kAvailableWidth);
744}
745
746TEST(TextEliderTest, ElideRectangleString) {
747  struct TestData {
748    const char* input;
749    int max_rows;
750    int max_cols;
751    bool result;
752    const char* output;
753  } cases[] = {
754    { "", 0, 0, false, "" },
755    { "", 1, 1, false, "" },
756    { "Hi, my name is\nTom", 0, 0,  true,  "..." },
757    { "Hi, my name is\nTom", 1, 0,  true,  "\n..." },
758    { "Hi, my name is\nTom", 0, 1,  true,  "..." },
759    { "Hi, my name is\nTom", 1, 1,  true,  "H\n..." },
760    { "Hi, my name is\nTom", 2, 1,  true,  "H\ni\n..." },
761    { "Hi, my name is\nTom", 3, 1,  true,  "H\ni\n,\n..." },
762    { "Hi, my name is\nTom", 4, 1,  true,  "H\ni\n,\n \n..." },
763    { "Hi, my name is\nTom", 5, 1,  true,  "H\ni\n,\n \nm\n..." },
764    { "Hi, my name is\nTom", 0, 2,  true,  "..." },
765    { "Hi, my name is\nTom", 1, 2,  true,  "Hi\n..." },
766    { "Hi, my name is\nTom", 2, 2,  true,  "Hi\n, \n..." },
767    { "Hi, my name is\nTom", 3, 2,  true,  "Hi\n, \nmy\n..." },
768    { "Hi, my name is\nTom", 4, 2,  true,  "Hi\n, \nmy\n n\n..." },
769    { "Hi, my name is\nTom", 5, 2,  true,  "Hi\n, \nmy\n n\nam\n..." },
770    { "Hi, my name is\nTom", 0, 3,  true,  "..." },
771    { "Hi, my name is\nTom", 1, 3,  true,  "Hi,\n..." },
772    { "Hi, my name is\nTom", 2, 3,  true,  "Hi,\n my\n..." },
773    { "Hi, my name is\nTom", 3, 3,  true,  "Hi,\n my\n na\n..." },
774    { "Hi, my name is\nTom", 4, 3,  true,  "Hi,\n my\n na\nme \n..." },
775    { "Hi, my name is\nTom", 5, 3,  true,  "Hi,\n my\n na\nme \nis\n..." },
776    { "Hi, my name is\nTom", 1, 4,  true,  "Hi, \n..." },
777    { "Hi, my name is\nTom", 2, 4,  true,  "Hi, \nmy n\n..." },
778    { "Hi, my name is\nTom", 3, 4,  true,  "Hi, \nmy n\name \n..." },
779    { "Hi, my name is\nTom", 4, 4,  true,  "Hi, \nmy n\name \nis\n..." },
780    { "Hi, my name is\nTom", 5, 4,  false, "Hi, \nmy n\name \nis\nTom" },
781    { "Hi, my name is\nTom", 1, 5,  true,  "Hi, \n..." },
782    { "Hi, my name is\nTom", 2, 5,  true,  "Hi, \nmy na\n..." },
783    { "Hi, my name is\nTom", 3, 5,  true,  "Hi, \nmy na\nme \n..." },
784    { "Hi, my name is\nTom", 4, 5,  true,  "Hi, \nmy na\nme \nis\n..." },
785    { "Hi, my name is\nTom", 5, 5,  false, "Hi, \nmy na\nme \nis\nTom" },
786    { "Hi, my name is\nTom", 1, 6,  true,  "Hi, \n..." },
787    { "Hi, my name is\nTom", 2, 6,  true,  "Hi, \nmy \n..." },
788    { "Hi, my name is\nTom", 3, 6,  true,  "Hi, \nmy \nname \n..." },
789    { "Hi, my name is\nTom", 4, 6,  true,  "Hi, \nmy \nname \nis\n..." },
790    { "Hi, my name is\nTom", 5, 6,  false, "Hi, \nmy \nname \nis\nTom" },
791    { "Hi, my name is\nTom", 1, 7,  true,  "Hi, \n..." },
792    { "Hi, my name is\nTom", 2, 7,  true,  "Hi, \nmy \n..." },
793    { "Hi, my name is\nTom", 3, 7,  true,  "Hi, \nmy \nname \n..." },
794    { "Hi, my name is\nTom", 4, 7,  true,  "Hi, \nmy \nname \nis\n..." },
795    { "Hi, my name is\nTom", 5, 7,  false, "Hi, \nmy \nname \nis\nTom" },
796    { "Hi, my name is\nTom", 1, 8,  true,  "Hi, my \n..." },
797    { "Hi, my name is\nTom", 2, 8,  true,  "Hi, my \nname \n..." },
798    { "Hi, my name is\nTom", 3, 8,  true,  "Hi, my \nname \nis\n..." },
799    { "Hi, my name is\nTom", 4, 8,  false, "Hi, my \nname \nis\nTom" },
800    { "Hi, my name is\nTom", 1, 9,  true,  "Hi, my \n..." },
801    { "Hi, my name is\nTom", 2, 9,  true,  "Hi, my \nname is\n..." },
802    { "Hi, my name is\nTom", 3, 9,  false, "Hi, my \nname is\nTom" },
803    { "Hi, my name is\nTom", 1, 10, true,  "Hi, my \n..." },
804    { "Hi, my name is\nTom", 2, 10, true,  "Hi, my \nname is\n..." },
805    { "Hi, my name is\nTom", 3, 10, false, "Hi, my \nname is\nTom" },
806    { "Hi, my name is\nTom", 1, 11, true,  "Hi, my \n..." },
807    { "Hi, my name is\nTom", 2, 11, true,  "Hi, my \nname is\n..." },
808    { "Hi, my name is\nTom", 3, 11, false, "Hi, my \nname is\nTom" },
809    { "Hi, my name is\nTom", 1, 12, true,  "Hi, my \n..." },
810    { "Hi, my name is\nTom", 2, 12, true,  "Hi, my \nname is\n..." },
811    { "Hi, my name is\nTom", 3, 12, false, "Hi, my \nname is\nTom" },
812    { "Hi, my name is\nTom", 1, 13, true,  "Hi, my name \n..." },
813    { "Hi, my name is\nTom", 2, 13, true,  "Hi, my name \nis\n..." },
814    { "Hi, my name is\nTom", 3, 13, false, "Hi, my name \nis\nTom" },
815    { "Hi, my name is\nTom", 1, 20, true,  "Hi, my name is\n..." },
816    { "Hi, my name is\nTom", 2, 20, false, "Hi, my name is\nTom" },
817    { "Hi, my name is Tom",  1, 40, false, "Hi, my name is Tom" },
818  };
819  string16 output;
820  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
821    EXPECT_EQ(cases[i].result,
822              ElideRectangleString(UTF8ToUTF16(cases[i].input),
823                                   cases[i].max_rows, cases[i].max_cols,
824                                   true, &output));
825    EXPECT_EQ(cases[i].output, UTF16ToUTF8(output));
826  }
827}
828
829TEST(TextEliderTest, ElideRectangleStringNotStrict) {
830  struct TestData {
831    const char* input;
832    int max_rows;
833    int max_cols;
834    bool result;
835    const char* output;
836  } cases[] = {
837    { "", 0, 0, false, "" },
838    { "", 1, 1, false, "" },
839    { "Hi, my name_is\nDick", 0, 0,  true,  "..." },
840    { "Hi, my name_is\nDick", 1, 0,  true,  "\n..." },
841    { "Hi, my name_is\nDick", 0, 1,  true,  "..." },
842    { "Hi, my name_is\nDick", 1, 1,  true,  "H\n..." },
843    { "Hi, my name_is\nDick", 2, 1,  true,  "H\ni\n..." },
844    { "Hi, my name_is\nDick", 3, 1,  true,  "H\ni\n,\n..." },
845    { "Hi, my name_is\nDick", 4, 1,  true,  "H\ni\n,\n \n..." },
846    { "Hi, my name_is\nDick", 5, 1,  true,  "H\ni\n,\n \nm\n..." },
847    { "Hi, my name_is\nDick", 0, 2,  true,  "..." },
848    { "Hi, my name_is\nDick", 1, 2,  true,  "Hi\n..." },
849    { "Hi, my name_is\nDick", 2, 2,  true,  "Hi\n, \n..." },
850    { "Hi, my name_is\nDick", 3, 2,  true,  "Hi\n, \nmy\n..." },
851    { "Hi, my name_is\nDick", 4, 2,  true,  "Hi\n, \nmy\n n\n..." },
852    { "Hi, my name_is\nDick", 5, 2,  true,  "Hi\n, \nmy\n n\nam\n..." },
853    { "Hi, my name_is\nDick", 0, 3,  true,  "..." },
854    { "Hi, my name_is\nDick", 1, 3,  true,  "Hi,\n..." },
855    { "Hi, my name_is\nDick", 2, 3,  true,  "Hi,\n my\n..." },
856    { "Hi, my name_is\nDick", 3, 3,  true,  "Hi,\n my\n na\n..." },
857    { "Hi, my name_is\nDick", 4, 3,  true,  "Hi,\n my\n na\nme_\n..." },
858    { "Hi, my name_is\nDick", 5, 3,  true,  "Hi,\n my\n na\nme_\nis\n..." },
859    { "Hi, my name_is\nDick", 1, 4,  true,  "Hi, ..." },
860    { "Hi, my name_is\nDick", 2, 4,  true,  "Hi, my n\n..." },
861    { "Hi, my name_is\nDick", 3, 4,  true,  "Hi, my n\name_\n..." },
862    { "Hi, my name_is\nDick", 4, 4,  true,  "Hi, my n\name_\nis\n..." },
863    { "Hi, my name_is\nDick", 5, 4,  false, "Hi, my n\name_\nis\nDick" },
864    { "Hi, my name_is\nDick", 1, 5,  true,  "Hi, ..." },
865    { "Hi, my name_is\nDick", 2, 5,  true,  "Hi, my na\n..." },
866    { "Hi, my name_is\nDick", 3, 5,  true,  "Hi, my na\nme_is\n..." },
867    { "Hi, my name_is\nDick", 4, 5,  true,  "Hi, my na\nme_is\n\n..." },
868    { "Hi, my name_is\nDick", 5, 5,  false, "Hi, my na\nme_is\n\nDick" },
869    { "Hi, my name_is\nDick", 1, 6,  true,  "Hi, ..." },
870    { "Hi, my name_is\nDick", 2, 6,  true,  "Hi, my nam\n..." },
871    { "Hi, my name_is\nDick", 3, 6,  true,  "Hi, my nam\ne_is\n..." },
872    { "Hi, my name_is\nDick", 4, 6,  false, "Hi, my nam\ne_is\nDick" },
873    { "Hi, my name_is\nDick", 5, 6,  false, "Hi, my nam\ne_is\nDick" },
874    { "Hi, my name_is\nDick", 1, 7,  true,  "Hi, ..." },
875    { "Hi, my name_is\nDick", 2, 7,  true,  "Hi, my name\n..." },
876    { "Hi, my name_is\nDick", 3, 7,  true,  "Hi, my name\n_is\n..." },
877    { "Hi, my name_is\nDick", 4, 7,  false, "Hi, my name\n_is\nDick" },
878    { "Hi, my name_is\nDick", 5, 7,  false, "Hi, my name\n_is\nDick" },
879    { "Hi, my name_is\nDick", 1, 8,  true,  "Hi, my n\n..." },
880    { "Hi, my name_is\nDick", 2, 8,  true,  "Hi, my n\name_is\n..." },
881    { "Hi, my name_is\nDick", 3, 8,  false, "Hi, my n\name_is\nDick" },
882    { "Hi, my name_is\nDick", 1, 9,  true,  "Hi, my ..." },
883    { "Hi, my name_is\nDick", 2, 9,  true,  "Hi, my name_is\n..." },
884    { "Hi, my name_is\nDick", 3, 9,  false, "Hi, my name_is\nDick" },
885    { "Hi, my name_is\nDick", 1, 10, true,  "Hi, my ..." },
886    { "Hi, my name_is\nDick", 2, 10, true,  "Hi, my name_is\n..." },
887    { "Hi, my name_is\nDick", 3, 10, false, "Hi, my name_is\nDick" },
888    { "Hi, my name_is\nDick", 1, 11, true,  "Hi, my ..." },
889    { "Hi, my name_is\nDick", 2, 11, true,  "Hi, my name_is\n..." },
890    { "Hi, my name_is\nDick", 3, 11, false, "Hi, my name_is\nDick" },
891    { "Hi, my name_is\nDick", 1, 12, true,  "Hi, my ..." },
892    { "Hi, my name_is\nDick", 2, 12, true,  "Hi, my name_is\n..." },
893    { "Hi, my name_is\nDick", 3, 12, false, "Hi, my name_is\nDick" },
894    { "Hi, my name_is\nDick", 1, 13, true,  "Hi, my ..." },
895    { "Hi, my name_is\nDick", 2, 13, true,  "Hi, my name_is\n..." },
896    { "Hi, my name_is\nDick", 3, 13, false, "Hi, my name_is\nDick" },
897    { "Hi, my name_is\nDick", 1, 20, true,  "Hi, my name_is\n..." },
898    { "Hi, my name_is\nDick", 2, 20, false, "Hi, my name_is\nDick" },
899    { "Hi, my name_is Dick",  1, 40, false, "Hi, my name_is Dick" },
900  };
901  string16 output;
902  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
903    EXPECT_EQ(cases[i].result,
904              ElideRectangleString(UTF8ToUTF16(cases[i].input),
905                                   cases[i].max_rows, cases[i].max_cols,
906                                   false, &output));
907    EXPECT_EQ(cases[i].output, UTF16ToUTF8(output));
908  }
909}
910
911TEST(TextEliderTest, ElideRectangleWide16) {
912  // Two greek words separated by space.
913  const string16 str(WideToUTF16(
914      L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9"
915      L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2"));
916  const string16 out1(WideToUTF16(
917      L"\x03a0\x03b1\x03b3\x03ba\n"
918      L"\x03cc\x03c3\x03bc\x03b9\n"
919      L"..."));
920  const string16 out2(WideToUTF16(
921      L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9\x03bf\x03c2\x0020\n"
922      L"\x0399\x03c3\x03c4\x03cc\x03c2"));
923  string16 output;
924  EXPECT_TRUE(ElideRectangleString(str, 2, 4, true, &output));
925  EXPECT_EQ(out1, output);
926  EXPECT_FALSE(ElideRectangleString(str, 2, 12, true, &output));
927  EXPECT_EQ(out2, output);
928}
929
930TEST(TextEliderTest, ElideRectangleWide32) {
931  // Four U+1D49C MATHEMATICAL SCRIPT CAPITAL A followed by space "aaaaa".
932  const string16 str(UTF8ToUTF16(
933      "\xF0\x9D\x92\x9C\xF0\x9D\x92\x9C\xF0\x9D\x92\x9C\xF0\x9D\x92\x9C"
934      " aaaaa"));
935  const string16 out(UTF8ToUTF16(
936      "\xF0\x9D\x92\x9C\xF0\x9D\x92\x9C\xF0\x9D\x92\x9C\n"
937      "\xF0\x9D\x92\x9C \naaa\n..."));
938  string16 output;
939  EXPECT_TRUE(ElideRectangleString(str, 3, 3, true, &output));
940  EXPECT_EQ(out, output);
941}
942
943TEST(TextEliderTest, TruncateString) {
944  string16 string = ASCIIToUTF16("foooooey    bxxxar baz");
945
946  // Make sure it doesn't modify the string if length > string length.
947  EXPECT_EQ(string, TruncateString(string, 100));
948
949  // Test no characters.
950  EXPECT_EQ(L"", UTF16ToWide(TruncateString(string, 0)));
951
952  // Test 1 character.
953  EXPECT_EQ(L"\x2026", UTF16ToWide(TruncateString(string, 1)));
954
955  // Test adds ... at right spot when there is enough room to break at a
956  // word boundary.
957  EXPECT_EQ(L"foooooey\x2026", UTF16ToWide(TruncateString(string, 14)));
958
959  // Test adds ... at right spot when there is not enough space in first word.
960  EXPECT_EQ(L"f\x2026", UTF16ToWide(TruncateString(string, 2)));
961
962  // Test adds ... at right spot when there is not enough room to break at a
963  // word boundary.
964  EXPECT_EQ(L"foooooey\x2026", UTF16ToWide(TruncateString(string, 11)));
965
966  // Test completely truncates string if break is on initial whitespace.
967  EXPECT_EQ(L"\x2026", UTF16ToWide(TruncateString(ASCIIToUTF16("   "), 2)));
968}
969
970}  // namespace gfx
971