text_elider_unittest.cc 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157
  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. #include "ui/gfx/text_elider.h"
  7. #include <stddef.h>
  8. #include <memory>
  9. #include <vector>
  10. #include "base/files/file_path.h"
  11. #include "base/i18n/rtl.h"
  12. #include "base/logging.h"
  13. #include "base/run_loop.h"
  14. #include "base/strings/string_util.h"
  15. #include "base/strings/utf_string_conversions.h"
  16. #include "base/test/task_environment.h"
  17. #include "build/build_config.h"
  18. #include "build/chromeos_buildflags.h"
  19. #include "testing/gtest/include/gtest/gtest.h"
  20. #include "ui/gfx/font.h"
  21. #include "ui/gfx/font_list.h"
  22. #include "ui/gfx/font_render_params.h"
  23. #include "ui/gfx/text_utils.h"
  24. namespace gfx {
  25. namespace {
  26. struct FileTestcase {
  27. const base::FilePath::StringType input;
  28. const std::u16string output;
  29. // If this value is specified, we will try to cut the path down to the render
  30. // width of this string; if not specified, output will be used.
  31. const std::u16string using_width_of = std::u16string();
  32. };
  33. struct Testcase {
  34. const std::u16string input;
  35. const std::u16string output;
  36. };
  37. } // namespace
  38. TEST(TextEliderTest, ElideEmail) {
  39. // Test emails and their expected elided forms (from which the available
  40. // widths will be derived).
  41. // For elided forms in which both the username and domain must be elided:
  42. // the result (how many characters are left on each side) can be font
  43. // dependent. To avoid this, the username is prefixed with the characters
  44. // expected to remain in the domain.
  45. Testcase testcases[] = {
  46. {u"g@g.c", u"g@g.c"},
  47. {u"g@g.c", u"…"},
  48. {u"ga@co.ca", u"ga@c…a"},
  49. {u"short@small.com", u"s…@s…"},
  50. {u"short@small.com", u"s…@small.com"},
  51. {u"short@longbutlotsofspace.com", u"short@longbutlotsofspace.com"},
  52. {u"short@longbutnotverymuchspace.com", u"short@long….com"},
  53. {u"la_short@longbutverytightspace.ca", u"la…@l…a"},
  54. {u"longusername@gmail.com", u"long…@gmail.com"},
  55. {u"elidetothemax@justfits.com", u"e…@justfits.com"},
  56. {u"thatom_somelongemail@thatdoesntfit.com", u"thatom…@tha…om"},
  57. {u"namefits@butthedomaindoesnt.com", u"namefits@butthedo…snt.com"},
  58. {u"widthtootight@nospace.com", u"…"},
  59. {u"nospaceforusername@l", u"…"},
  60. {u"little@littlespace.com", u"l…@l…"},
  61. {u"l@llllllllllllllllllllllll.com", u"l@lllll….com"},
  62. {u"messed\"up@whyanat\"++@notgoogley.com",
  63. u"messed\"up@whyanat\"++@notgoogley.com"},
  64. {u"messed\"up@whyanat\"++@notgoogley.com",
  65. u"messed\"up@why…@notgoogley.com"},
  66. {u"noca_messed\"up@whyanat\"++@notgoogley.ca", u"noca…@no…ca"},
  67. {u"at\"@@@@@@@@@...@@.@.@.@@@\"@madness.com",
  68. u"at\"@@@@@@@@@...@@.@.…@madness.com"},
  69. // Special case: "m..." takes more than half of the available width; thus
  70. // the domain must elide to "l..." and not "l...l" as it must allow enough
  71. // space for the minimal username elision although its half of the
  72. // available width would normally allow it to elide to "l...l".
  73. {u"mmmmm@llllllllll", u"m…@l…"},
  74. };
  75. const FontList font_list;
  76. for (size_t i = 0; i < std::size(testcases); ++i) {
  77. const std::u16string expected_output = testcases[i].output;
  78. EXPECT_EQ(
  79. expected_output,
  80. ElideText(testcases[i].input, font_list,
  81. GetStringWidthF(expected_output, font_list), ELIDE_EMAIL));
  82. }
  83. }
  84. TEST(TextEliderTest, ElideEmailMoreSpace) {
  85. const int test_widths_extra_spaces[] = {
  86. 10,
  87. 1000,
  88. 100'000,
  89. };
  90. const char16_t* const test_emails[] = {
  91. u"a@c",
  92. u"test@email.com",
  93. u"short@verysuperdupperlongdomain.com",
  94. u"supermegalongusername@withasuperlonnnggggdomain.gouv.qc.ca",
  95. };
  96. const FontList font_list;
  97. for (const auto* test_email : test_emails) {
  98. const int mimimum_width = GetStringWidth(test_email, font_list);
  99. for (int extra_space : test_widths_extra_spaces) {
  100. // Extra space is available: the email should not be elided.
  101. EXPECT_EQ(test_email,
  102. ElideText(test_email, font_list, mimimum_width + extra_space,
  103. ELIDE_EMAIL));
  104. }
  105. }
  106. }
  107. TEST(TextEliderTest, TestFilenameEliding) {
  108. const base::FilePath::StringType kPathSeparator =
  109. base::FilePath::StringType().append(1, base::FilePath::kSeparators[0]);
  110. FileTestcase testcases[] = {
  111. {FILE_PATH_LITERAL(""), u""},
  112. {FILE_PATH_LITERAL("."), u"."},
  113. {FILE_PATH_LITERAL("filename.exe"), u"filename.exe"},
  114. {FILE_PATH_LITERAL(".longext"), u".longext"},
  115. {FILE_PATH_LITERAL("pie"), u"pie"},
  116. {FILE_PATH_LITERAL("c:") + kPathSeparator + FILE_PATH_LITERAL("path") +
  117. kPathSeparator + FILE_PATH_LITERAL("filename.pie"),
  118. u"filename.pie"},
  119. {FILE_PATH_LITERAL("c:") + kPathSeparator + FILE_PATH_LITERAL("path") +
  120. kPathSeparator + FILE_PATH_LITERAL("longfilename.pie"),
  121. u"long….pie"},
  122. {FILE_PATH_LITERAL("http://path.com/filename.pie"), u"filename.pie"},
  123. {FILE_PATH_LITERAL("http://path.com/longfilename.pie"), u"long….pie"},
  124. {FILE_PATH_LITERAL("piesmashingtacularpants"), u"pie…"},
  125. {FILE_PATH_LITERAL(".piesmashingtacularpants"), u".pie…"},
  126. {FILE_PATH_LITERAL("cheese."), u"cheese."},
  127. {FILE_PATH_LITERAL("file name.longext"), u"file….longext"},
  128. {FILE_PATH_LITERAL("fil ename.longext"), u"fil….longext",
  129. u"fil ….longext"},
  130. {FILE_PATH_LITERAL("filename.longext"), u"file….longext"},
  131. {FILE_PATH_LITERAL("filename.middleext.longext"),
  132. u"filename.mid….longext"},
  133. {FILE_PATH_LITERAL("filename.superduperextremelylongext"),
  134. u"filename.sup…emelylongext"},
  135. {FILE_PATH_LITERAL("filenamereallylongtext.superdeduperextremelylongext"),
  136. u"filenamereall…emelylongext"},
  137. {FILE_PATH_LITERAL(
  138. "file.name.really.long.text.superduperextremelylongext"),
  139. u"file.name.re…emelylongext"}};
  140. static const FontList font_list;
  141. for (size_t i = 0; i < std::size(testcases); ++i) {
  142. base::FilePath filepath(testcases[i].input);
  143. std::u16string expected = testcases[i].output;
  144. std::u16string using_width_of = testcases[i].using_width_of.empty()
  145. ? testcases[i].output
  146. : testcases[i].using_width_of;
  147. expected = base::i18n::GetDisplayStringInLTRDirectionality(expected);
  148. EXPECT_EQ(expected,
  149. ElideFilename(filepath, font_list,
  150. GetStringWidthF(using_width_of, font_list)));
  151. }
  152. }
  153. TEST(TextEliderTest, ElideTextTruncate) {
  154. const FontList font_list;
  155. const float kTestWidth = GetStringWidthF(u"Test", font_list);
  156. struct TestData {
  157. const char16_t* input;
  158. float width;
  159. const char16_t* output;
  160. } cases[] = {
  161. {u"", 0, u""},
  162. {u"Test", 0, u""},
  163. {u"", kTestWidth, u""},
  164. {u"Tes", kTestWidth, u"Tes"},
  165. {u"Test", kTestWidth, u"Test"},
  166. {u"Tests", kTestWidth, u"Test"},
  167. };
  168. for (size_t i = 0; i < std::size(cases); ++i) {
  169. std::u16string result =
  170. ElideText(cases[i].input, font_list, cases[i].width, TRUNCATE);
  171. EXPECT_EQ(cases[i].output, result);
  172. }
  173. }
  174. TEST(TextEliderTest, ElideTextEllipsis) {
  175. const FontList font_list;
  176. const float kTestWidth = GetStringWidthF(u"Test", font_list);
  177. const float kEllipsisWidth = GetStringWidthF(u"…", font_list);
  178. struct TestData {
  179. const char16_t* input;
  180. float width;
  181. const char16_t* output;
  182. } cases[] = {
  183. {u"", 0, u""},
  184. {u"Test", 0, u""},
  185. {u"Test", kEllipsisWidth, u"…"},
  186. {u"", kTestWidth, u""},
  187. {u"Tes", kTestWidth, u"Tes"},
  188. {u"Test", kTestWidth, u"Test"},
  189. };
  190. for (size_t i = 0; i < std::size(cases); ++i) {
  191. std::u16string result =
  192. ElideText(cases[i].input, font_list, cases[i].width, ELIDE_TAIL);
  193. EXPECT_EQ(cases[i].output, result);
  194. }
  195. }
  196. TEST(TextEliderTest, ElideTextEllipsisFront) {
  197. const FontList font_list;
  198. const float kTestWidth = GetStringWidthF(u"Test", font_list);
  199. const float kEllipsisWidth = GetStringWidthF(u"…", font_list);
  200. const float kEllipsis23Width = GetStringWidthF(u"…23", font_list);
  201. struct TestData {
  202. const char16_t* input;
  203. float width;
  204. const std::u16string output;
  205. } cases[] = {
  206. {u"", 0, std::u16string()},
  207. {u"Test", 0, std::u16string()},
  208. {u"Test", kEllipsisWidth, u"…"},
  209. {u"", kTestWidth, std::u16string()},
  210. {u"Tes", kTestWidth, u"Tes"},
  211. {u"Test", kTestWidth, u"Test"},
  212. {u"Test123", kEllipsis23Width, u"…23"},
  213. };
  214. for (size_t i = 0; i < std::size(cases); ++i) {
  215. std::u16string result =
  216. ElideText(cases[i].input, font_list, cases[i].width, ELIDE_HEAD);
  217. EXPECT_EQ(cases[i].output, result);
  218. }
  219. }
  220. // Checks that all occurrences of |first_char| are followed by |second_char| and
  221. // all occurrences of |second_char| are preceded by |first_char| in |text|. Can
  222. // be used to test surrogate pairs or two-character combining sequences.
  223. static void CheckCodeUnitPairs(const std::u16string& text,
  224. char16_t first_char,
  225. char16_t second_char) {
  226. for (size_t index = 0; index < text.length(); ++index) {
  227. EXPECT_NE(second_char, text[index]);
  228. if (text[index] == first_char) {
  229. ASSERT_LT(++index, text.length());
  230. EXPECT_EQ(second_char, text[index]);
  231. }
  232. }
  233. }
  234. // Test that both both UTF-16 surrogate pairs and combining character sequences
  235. // do not get split by ElideText.
  236. TEST(TextEliderTest, ElideTextAtomicSequences) {
  237. #if BUILDFLAG(IS_WIN)
  238. // Needed to bypass DCHECK in GetFallbackFont.
  239. base::test::SingleThreadTaskEnvironment task_environment(
  240. base::test::SingleThreadTaskEnvironment::MainThreadType::UI);
  241. #endif
  242. const FontList font_list;
  243. std::vector<std::u16string> pairs;
  244. // The below is 'MUSICAL SYMBOL G CLEF' (U+1D11E), which is represented in
  245. // UTF-16 as two code units forming a surrogate pair: 0xD834 0xDD1E.
  246. pairs.push_back(u"\U0001d11e");
  247. // The below is a Devanagari two-character combining sequence U+0921 U+093F.
  248. // The sequence forms a single display character and should not be separated.
  249. pairs.push_back(u"\u0921\u093f");
  250. for (const std::u16string& pair : pairs) {
  251. char16_t first_char = pair[0];
  252. char16_t second_char = pair[1];
  253. std::u16string test_string = pair + u"x" + pair;
  254. SCOPED_TRACE(test_string);
  255. const float test_string_width = GetStringWidthF(test_string, font_list);
  256. std::u16string result;
  257. // Elide |text_string| to all possible widths and check that no instance of
  258. // |pair| was split in two.
  259. for (float width = 0; width <= test_string_width; width++) {
  260. result = ElideText(test_string, font_list, width, TRUNCATE);
  261. CheckCodeUnitPairs(result, first_char, second_char);
  262. result = ElideText(test_string, font_list, width, ELIDE_TAIL);
  263. CheckCodeUnitPairs(result, first_char, second_char);
  264. result = ElideText(test_string, font_list, width, ELIDE_MIDDLE);
  265. CheckCodeUnitPairs(result, first_char, second_char);
  266. result = ElideText(test_string, font_list, width, ELIDE_HEAD);
  267. CheckCodeUnitPairs(result, first_char, second_char);
  268. }
  269. }
  270. }
  271. TEST(TextEliderTest, ElideTextLongStrings) {
  272. std::u16string data_scheme(u"data:text/plain,");
  273. size_t data_scheme_length = data_scheme.length();
  274. std::u16string ten_a(10, 'a');
  275. std::u16string hundred_a(100, 'a');
  276. std::u16string thousand_a(1000, 'a');
  277. std::u16string ten_thousand_a(10'000, 'a');
  278. std::u16string hundred_thousand_a(100'000, 'a');
  279. std::u16string million_a(1'000'000, 'a');
  280. // TODO(gbillock): Improve these tests by adding more string diversity and
  281. // doing string compares instead of length compares. See bug 338836.
  282. size_t number_of_as = 156;
  283. std::u16string long_string_end(data_scheme +
  284. std::u16string(number_of_as, 'a') + u"…");
  285. Testcase testcases_end[] = {
  286. {data_scheme + ten_a, data_scheme + ten_a},
  287. {data_scheme + hundred_a, data_scheme + hundred_a},
  288. {data_scheme + thousand_a, long_string_end},
  289. {data_scheme + ten_thousand_a, long_string_end},
  290. {data_scheme + hundred_thousand_a, long_string_end},
  291. {data_scheme + million_a, long_string_end},
  292. };
  293. const FontList font_list;
  294. float ellipsis_width = GetStringWidthF(u"…", font_list);
  295. for (size_t i = 0; i < std::size(testcases_end); ++i) {
  296. // Compare sizes rather than actual contents because if the test fails,
  297. // output is rather long.
  298. EXPECT_EQ(testcases_end[i].output.size(),
  299. ElideText(testcases_end[i].input, font_list,
  300. GetStringWidthF(testcases_end[i].output, font_list),
  301. ELIDE_TAIL).size());
  302. EXPECT_EQ(u"…", ElideText(testcases_end[i].input, font_list, ellipsis_width,
  303. ELIDE_TAIL));
  304. }
  305. size_t number_of_trailing_as = (data_scheme_length + number_of_as) / 2;
  306. std::u16string long_string_middle(
  307. data_scheme + std::u16string(number_of_as - number_of_trailing_as, 'a') +
  308. u"…" + std::u16string(number_of_trailing_as, 'a'));
  309. #if !BUILDFLAG(IS_IOS)
  310. long_string_middle += u"…";
  311. #endif
  312. Testcase testcases_middle[] = {
  313. {data_scheme + ten_a, data_scheme + ten_a},
  314. {data_scheme + hundred_a, data_scheme + hundred_a},
  315. {data_scheme + thousand_a, long_string_middle},
  316. {data_scheme + ten_thousand_a, long_string_middle},
  317. {data_scheme + hundred_thousand_a, long_string_middle},
  318. {data_scheme + million_a, long_string_middle},
  319. };
  320. for (size_t i = 0; i < std::size(testcases_middle); ++i) {
  321. // Compare sizes rather than actual contents because if the test fails,
  322. // output is rather long.
  323. EXPECT_EQ(testcases_middle[i].output.size(),
  324. ElideText(testcases_middle[i].input, font_list,
  325. GetStringWidthF(testcases_middle[i].output, font_list),
  326. ELIDE_MIDDLE)
  327. .size());
  328. EXPECT_EQ(u"…", ElideText(testcases_middle[i].input, font_list,
  329. ellipsis_width, ELIDE_MIDDLE));
  330. }
  331. std::u16string long_string_beginning(u"…" +
  332. std::u16string(number_of_as, 'a'));
  333. #if !BUILDFLAG(IS_IOS)
  334. long_string_beginning += u"…";
  335. #endif
  336. Testcase testcases_beginning[] = {
  337. {data_scheme + ten_a, data_scheme + ten_a},
  338. {data_scheme + hundred_a, data_scheme + hundred_a},
  339. {data_scheme + thousand_a, long_string_beginning},
  340. {data_scheme + ten_thousand_a, long_string_beginning},
  341. {data_scheme + hundred_thousand_a, long_string_beginning},
  342. {data_scheme + million_a, long_string_beginning},
  343. };
  344. for (size_t i = 0; i < std::size(testcases_beginning); ++i) {
  345. EXPECT_EQ(testcases_beginning[i].output.size(),
  346. ElideText(
  347. testcases_beginning[i].input, font_list,
  348. GetStringWidthF(testcases_beginning[i].output, font_list),
  349. ELIDE_HEAD).size());
  350. EXPECT_EQ(u"…", ElideText(testcases_beginning[i].input, font_list,
  351. ellipsis_width, ELIDE_HEAD));
  352. }
  353. }
  354. // Detailed tests for StringSlicer. These are faster and test more of the edge
  355. // cases than the above tests which are more end-to-end.
  356. TEST(TextEliderTest, StringSlicerBasicTest) {
  357. // Must store strings in variables (StringSlicer retains a reference to them).
  358. std::u16string text(u"Hello, world!");
  359. std::u16string ellipsis(u"…");
  360. StringSlicer slicer(text, ellipsis, false, false);
  361. EXPECT_EQ(u"", slicer.CutString(0, false));
  362. EXPECT_EQ(u"…", slicer.CutString(0, true));
  363. EXPECT_EQ(u"Hell", slicer.CutString(4, false));
  364. EXPECT_EQ(u"Hell…", slicer.CutString(4, true));
  365. EXPECT_EQ(text, slicer.CutString(text.length(), false));
  366. EXPECT_EQ(text + u"…", slicer.CutString(text.length(), true));
  367. StringSlicer slicer_begin(text, ellipsis, false, true);
  368. EXPECT_EQ(u"rld!", slicer_begin.CutString(4, false));
  369. EXPECT_EQ(u"…rld!", slicer_begin.CutString(4, true));
  370. StringSlicer slicer_mid(text, ellipsis, true, false);
  371. EXPECT_EQ(u"Held!", slicer_mid.CutString(5, false));
  372. EXPECT_EQ(u"Hel…d!", slicer_mid.CutString(5, true));
  373. }
  374. TEST(TextEliderTest, StringSlicerWhitespace_UseDefault) {
  375. // Must store strings in variables (StringSlicer retains a reference to them).
  376. std::u16string text(u"Hello, world!");
  377. std::u16string ellipsis(u"…");
  378. // Eliding the end of a string should result in whitespace being removed
  379. // before the ellipsis by default.
  380. StringSlicer slicer_end(text, ellipsis, false, false);
  381. EXPECT_EQ(u"Hello,…", slicer_end.CutString(6, true));
  382. EXPECT_EQ(u"Hello,…", slicer_end.CutString(7, true));
  383. EXPECT_EQ(u"Hello, w…", slicer_end.CutString(8, true));
  384. // Eliding the start of a string should result in whitespace being removed
  385. // after the ellipsis by default.
  386. StringSlicer slicer_begin(text, ellipsis, false, true);
  387. EXPECT_EQ(u"…world!", slicer_begin.CutString(6, true));
  388. EXPECT_EQ(u"…world!", slicer_begin.CutString(7, true));
  389. EXPECT_EQ(u"…, world!", slicer_begin.CutString(8, true));
  390. // Eliding the middle of a string should *NOT* result in whitespace being
  391. // removed around the ellipsis by default.
  392. StringSlicer slicer_mid(text, ellipsis, true, false);
  393. text = u"Hey world!";
  394. EXPECT_EQ(u"Hey…ld!", slicer_mid.CutString(6, true));
  395. EXPECT_EQ(u"Hey …ld!", slicer_mid.CutString(7, true));
  396. EXPECT_EQ(u"Hey …rld!", slicer_mid.CutString(8, true));
  397. }
  398. TEST(TextEliderTest, StringSlicerWhitespace_NoTrim) {
  399. // Must store strings in variables (StringSlicer retains a reference to them).
  400. std::u16string text(u"Hello, world!");
  401. std::u16string ellipsis(u"…");
  402. // Eliding the end of a string should not result in whitespace being removed
  403. // before the ellipsis in no-trim mode.
  404. StringSlicer slicer_end(text, ellipsis, false, false, false);
  405. EXPECT_EQ(u"Hello,…", slicer_end.CutString(6, true));
  406. EXPECT_EQ(u"Hello, …", slicer_end.CutString(7, true));
  407. EXPECT_EQ(u"Hello, w…", slicer_end.CutString(8, true));
  408. // Eliding the start of a string should not result in whitespace being removed
  409. // after the ellipsis in no-trim mode.
  410. StringSlicer slicer_begin(text, ellipsis, false, true, false);
  411. EXPECT_EQ(u"…world!", slicer_begin.CutString(6, true));
  412. EXPECT_EQ(u"… world!", slicer_begin.CutString(7, true));
  413. EXPECT_EQ(u"…, world!", slicer_begin.CutString(8, true));
  414. // Eliding the middle of a string should *NOT* result in whitespace being
  415. // removed around the ellipsis in no-trim mode.
  416. StringSlicer slicer_mid(text, ellipsis, true, false, false);
  417. text = u"Hey world!";
  418. EXPECT_EQ(u"Hey…ld!", slicer_mid.CutString(6, true));
  419. EXPECT_EQ(u"Hey …ld!", slicer_mid.CutString(7, true));
  420. EXPECT_EQ(u"Hey …rld!", slicer_mid.CutString(8, true));
  421. }
  422. TEST(TextEliderTest, StringSlicerWhitespace_Trim) {
  423. // Must store strings in variables (StringSlicer retains a reference to them).
  424. std::u16string text(u"Hello, world!");
  425. std::u16string ellipsis(u"…");
  426. // Eliding the end of a string should result in whitespace being removed
  427. // before the ellipsis in trim mode.
  428. StringSlicer slicer_end(text, ellipsis, false, false, true);
  429. EXPECT_EQ(u"Hello,…", slicer_end.CutString(6, true));
  430. EXPECT_EQ(u"Hello,…", slicer_end.CutString(7, true));
  431. EXPECT_EQ(u"Hello, w…", slicer_end.CutString(8, true));
  432. // Eliding the start of a string should result in whitespace being removed
  433. // after the ellipsis in trim mode.
  434. StringSlicer slicer_begin(text, ellipsis, false, true, true);
  435. EXPECT_EQ(u"…world!", slicer_begin.CutString(6, true));
  436. EXPECT_EQ(u"…world!", slicer_begin.CutString(7, true));
  437. EXPECT_EQ(u"…, world!", slicer_begin.CutString(8, true));
  438. // Eliding the middle of a string *should* result in whitespace being removed
  439. // around the ellipsis in trim mode.
  440. StringSlicer slicer_mid(text, ellipsis, true, false, true);
  441. text = u"Hey world!";
  442. EXPECT_EQ(u"Hey…ld!", slicer_mid.CutString(6, true));
  443. EXPECT_EQ(u"Hey…ld!", slicer_mid.CutString(7, true));
  444. EXPECT_EQ(u"Hey…rld!", slicer_mid.CutString(8, true));
  445. }
  446. TEST(TextEliderTest, StringSlicer_ElideMiddle_MultipleWhitespace) {
  447. // Must store strings in variables (StringSlicer retains a reference to them).
  448. std::u16string text(u"Hello world!");
  449. std::u16string ellipsis(u"…");
  450. // Eliding the middle of a string should not result in whitespace being
  451. // removed around the ellipsis in default whitespace mode.
  452. StringSlicer slicer_default(text, ellipsis, true, false);
  453. text = u"Hey U man";
  454. EXPECT_EQ(u"Hey…man", slicer_default.CutString(6, true));
  455. EXPECT_EQ(u"Hey …man", slicer_default.CutString(7, true));
  456. EXPECT_EQ(u"Hey … man", slicer_default.CutString(8, true));
  457. EXPECT_EQ(u"Hey … man", slicer_default.CutString(9, true));
  458. EXPECT_EQ(u"Hey … man", slicer_default.CutString(10, true));
  459. // Eliding the middle of a string should not result in whitespace being
  460. // removed around the ellipsis in no-trim mode.
  461. StringSlicer slicer_notrim(text, ellipsis, true, false, false);
  462. text = u"Hey U man";
  463. EXPECT_EQ(u"Hey…man", slicer_notrim.CutString(6, true));
  464. EXPECT_EQ(u"Hey …man", slicer_notrim.CutString(7, true));
  465. EXPECT_EQ(u"Hey … man", slicer_notrim.CutString(8, true));
  466. EXPECT_EQ(u"Hey … man", slicer_notrim.CutString(9, true));
  467. EXPECT_EQ(u"Hey … man", slicer_notrim.CutString(10, true));
  468. // Eliding the middle of a string *should* result in whitespace being removed
  469. // around the ellipsis in trim mode.
  470. StringSlicer slicer_trim(text, ellipsis, true, false, true);
  471. text = u"Hey U man";
  472. EXPECT_EQ(u"Hey…man", slicer_trim.CutString(6, true));
  473. EXPECT_EQ(u"Hey…man", slicer_trim.CutString(7, true));
  474. EXPECT_EQ(u"Hey…man", slicer_trim.CutString(8, true));
  475. EXPECT_EQ(u"Hey…man", slicer_trim.CutString(9, true));
  476. EXPECT_EQ(u"Hey…man", slicer_trim.CutString(10, true));
  477. }
  478. TEST(TextEliderTest, StringSlicerSurrogate) {
  479. // The below is 'MUSICAL SYMBOL G CLEF' (U+1D11E), which is represented in
  480. // UTF-16 as two code units forming a surrogate pair: 0xD834 0xDD1E.
  481. const std::u16string kSurrogate = u"\U0001d11e";
  482. ASSERT_EQ(2u, kSurrogate.size());
  483. ASSERT_EQ(u'\xD834', kSurrogate[0]);
  484. ASSERT_EQ(u'\xDD1E', kSurrogate[1]);
  485. std::u16string text(u"abc" + kSurrogate + u"xyz");
  486. std::u16string ellipsis(u"…");
  487. StringSlicer slicer(text, ellipsis, false, false);
  488. // Cut surrogate on the right. Should round left and exclude the surrogate.
  489. EXPECT_EQ(u"…", slicer.CutString(0, true));
  490. EXPECT_EQ(u"abc…", slicer.CutString(4, true));
  491. EXPECT_EQ(text + u"…", slicer.CutString(text.length(), true));
  492. // Cut surrogate on the left. Should round right and exclude the surrogate.
  493. StringSlicer slicer_begin(text, ellipsis, false, true);
  494. EXPECT_EQ(u"…xyz", slicer_begin.CutString(4, true));
  495. // Cut surrogate in the middle. Should round right and exclude the surrogate.
  496. std::u16string short_text(u"abc" + kSurrogate);
  497. StringSlicer slicer_mid(short_text, ellipsis, true, false);
  498. EXPECT_EQ(u"a…", slicer_mid.CutString(2, true));
  499. // String that starts with a dangling trailing surrogate.
  500. std::u16string dangling_trailing_text = kSurrogate.substr(1);
  501. StringSlicer slicer_dangling_trailing(dangling_trailing_text, ellipsis, false,
  502. false);
  503. EXPECT_EQ(u"…", slicer_dangling_trailing.CutString(0, true));
  504. EXPECT_EQ(dangling_trailing_text + u"…",
  505. slicer_dangling_trailing.CutString(1, true));
  506. }
  507. TEST(TextEliderTest, StringSlicerCombining) {
  508. // The following string contains three combining character sequences (one for
  509. // each category of combining mark):
  510. // LATIN SMALL LETTER E + COMBINING ACUTE ACCENT + COMBINING CEDILLA
  511. // LATIN SMALL LETTER X + COMBINING ENCLOSING KEYCAP
  512. // DEVANAGARI LETTER DDA + DEVANAGARI VOWEL SIGN I
  513. std::u16string text(u"e\u0301\u0327 x\u20e3 \u0921\u093f");
  514. std::u16string ellipsis(u"…");
  515. StringSlicer slicer(text, ellipsis, false, false);
  516. // Attempt to cut the string for all lengths. When a combining sequence is
  517. // cut, it should always round left and exclude the combining sequence.
  518. // Whitespace is also cut adjacent to the ellipsis.
  519. // First sequence:
  520. EXPECT_EQ(u"…", slicer.CutString(0, true));
  521. EXPECT_EQ(u"…", slicer.CutString(1, true));
  522. EXPECT_EQ(u"…", slicer.CutString(2, true));
  523. EXPECT_EQ(text.substr(0, 3) + u"…", slicer.CutString(3, true));
  524. // Second sequence:
  525. EXPECT_EQ(text.substr(0, 3) + u"…", slicer.CutString(4, true));
  526. EXPECT_EQ(text.substr(0, 3) + u"…", slicer.CutString(5, true));
  527. EXPECT_EQ(text.substr(0, 6) + u"…", slicer.CutString(6, true));
  528. // Third sequence:
  529. EXPECT_EQ(text.substr(0, 6) + u"…", slicer.CutString(7, true));
  530. EXPECT_EQ(text.substr(0, 6) + u"…", slicer.CutString(8, true));
  531. EXPECT_EQ(text + u"…", slicer.CutString(9, true));
  532. // Cut string in the middle, splitting the second sequence in half. Should
  533. // round both left and right, excluding the second sequence.
  534. StringSlicer slicer_mid(text, ellipsis, true, false);
  535. EXPECT_EQ(text.substr(0, 4) + u"…" + text.substr(6),
  536. slicer_mid.CutString(9, true));
  537. // String that starts with a dangling combining mark.
  538. char16_t dangling_mark_chars[] = {text[1], 0};
  539. std::u16string dangling_mark_text(dangling_mark_chars);
  540. StringSlicer slicer_dangling_mark(dangling_mark_text, ellipsis, false, false);
  541. EXPECT_EQ(u"…", slicer_dangling_mark.CutString(0, true));
  542. EXPECT_EQ(dangling_mark_text + u"…", slicer_dangling_mark.CutString(1, true));
  543. }
  544. TEST(TextEliderTest, StringSlicerCombiningSurrogate) {
  545. // The ultimate test: combining sequences comprised of surrogate pairs.
  546. // The following string contains a single combining character sequence:
  547. // MUSICAL SYMBOL G CLEF (U+1D11E) + MUSICAL SYMBOL COMBINING FLAG-1 (U+1D16E)
  548. // Represented as four UTF-16 code units.
  549. std::u16string text(u"\U0001d11e\U0001d16e");
  550. std::u16string ellipsis(u"…");
  551. StringSlicer slicer(text, ellipsis, false, false);
  552. // Attempt to cut the string for all lengths. Should always round left and
  553. // exclude the combining sequence.
  554. EXPECT_EQ(u"…", slicer.CutString(0, true));
  555. EXPECT_EQ(u"…", slicer.CutString(1, true));
  556. EXPECT_EQ(u"…", slicer.CutString(2, true));
  557. EXPECT_EQ(u"…", slicer.CutString(3, true));
  558. EXPECT_EQ(text + u"…", slicer.CutString(4, true));
  559. // Cut string in the middle. Should exclude the sequence.
  560. StringSlicer slicer_mid(text, ellipsis, true, false);
  561. EXPECT_EQ(u"…", slicer_mid.CutString(4, true));
  562. }
  563. TEST(TextEliderTest, ElideString) {
  564. struct TestData {
  565. const char16_t* input;
  566. size_t max_len;
  567. bool result;
  568. const char16_t* output;
  569. } cases[] = {
  570. {u"Hello", 0, true, u""},
  571. {u"", 0, false, u""},
  572. {u"Hello, my name is Tom", 1, true, u"H"},
  573. {u"Hello, my name is Tom", 2, true, u"He"},
  574. {u"Hello, my name is Tom", 3, true, u"H.m"},
  575. {u"Hello, my name is Tom", 4, true, u"H..m"},
  576. {u"Hello, my name is Tom", 5, true, u"H...m"},
  577. {u"Hello, my name is Tom", 6, true, u"He...m"},
  578. {u"Hello, my name is Tom", 7, true, u"He...om"},
  579. {u"Hello, my name is Tom", 10, true, u"Hell...Tom"},
  580. {u"Hello, my name is Tom", 100, false, u"Hello, my name is Tom"}};
  581. for (size_t i = 0; i < std::size(cases); ++i) {
  582. std::u16string output;
  583. EXPECT_EQ(cases[i].result,
  584. ElideString(cases[i].input, cases[i].max_len, &output));
  585. EXPECT_EQ(cases[i].output, output);
  586. }
  587. }
  588. TEST(TextEliderTest, ElideRectangleText) {
  589. const FontList font_list;
  590. const int line_height = font_list.GetHeight();
  591. const float test_width = GetStringWidthF(u"Test", font_list);
  592. struct TestData {
  593. const char16_t* input;
  594. float available_pixel_width;
  595. int available_pixel_height;
  596. bool truncated_y;
  597. const char16_t* output;
  598. } cases[] = {
  599. {u"", 0, 0, false, nullptr},
  600. {u"", 1, 1, false, nullptr},
  601. {u"Test", test_width, 0, true, nullptr},
  602. {u"Test", test_width, 1, false, u"Test"},
  603. {u"Test", test_width, line_height, false, u"Test"},
  604. {u"Test Test", test_width, line_height, true, u"Test"},
  605. {u"Test Test", test_width, line_height + 1, false, u"Test|Test"},
  606. {u"Test Test", test_width, line_height * 2, false, u"Test|Test"},
  607. {u"Test Test", test_width, line_height * 3, false, u"Test|Test"},
  608. {u"Test Test", test_width * 2, line_height * 2, false, u"Test|Test"},
  609. {u"Test Test", test_width * 3, line_height, false, u"Test Test"},
  610. {u"Test\nTest", test_width * 3, line_height * 2, false, u"Test|Test"},
  611. {u"Te\nst Te", test_width, line_height * 3, false, u"Te|st|Te"},
  612. {u"\nTest", test_width, line_height * 2, false, u"|Test"},
  613. {u"\nTest", test_width, line_height, true, u""},
  614. {u"\n\nTest", test_width, line_height * 3, false, u"||Test"},
  615. {u"\n\nTest", test_width, line_height * 2, true, u"|"},
  616. {u"Test\n", 2 * test_width, line_height * 5, false, u"Test|"},
  617. {u"Test\n\n", 2 * test_width, line_height * 5, false, u"Test||"},
  618. {u"Test\n\n\n", 2 * test_width, line_height * 5, false, u"Test|||"},
  619. {u"Test\nTest\n\n", 2 * test_width, line_height * 5, false,
  620. u"Test|Test||"},
  621. {u"Test\n\nTest\n", 2 * test_width, line_height * 5, false,
  622. u"Test||Test|"},
  623. {u"Test\n\n\nTest", 2 * test_width, line_height * 5, false,
  624. u"Test|||Test"},
  625. {u"Te ", test_width, line_height, false, u"Te"},
  626. {u"Te Te Test", test_width, 3 * line_height, false, u"Te|Te|Test"},
  627. };
  628. for (size_t i = 0; i < std::size(cases); ++i) {
  629. std::vector<std::u16string> lines;
  630. EXPECT_EQ(cases[i].truncated_y ? INSUFFICIENT_SPACE_VERTICAL : 0,
  631. ElideRectangleText(cases[i].input, font_list,
  632. cases[i].available_pixel_width,
  633. cases[i].available_pixel_height,
  634. TRUNCATE_LONG_WORDS, &lines));
  635. if (cases[i].output) {
  636. const std::u16string result = base::JoinString(lines, u"|");
  637. EXPECT_EQ(cases[i].output, result) << "Case " << i << " failed!";
  638. } else {
  639. EXPECT_TRUE(lines.empty()) << "Case " << i << " failed!";
  640. }
  641. }
  642. }
  643. TEST(TextEliderTest, ElideRectangleTextFirstWordTruncated) {
  644. const FontList font_list;
  645. const int line_height = font_list.GetHeight();
  646. const float test_width = GetStringWidthF(u"Test", font_list);
  647. const float tes_width = GetStringWidthF(u"Tes", font_list);
  648. std::vector<std::u16string> lines;
  649. auto result_for_width = [&](const char16_t* input, float width) {
  650. lines.clear();
  651. return ElideRectangleText(input, font_list, width, line_height * 4,
  652. WRAP_LONG_WORDS, &lines);
  653. };
  654. // Test base case.
  655. EXPECT_EQ(0, result_for_width(u"Test", test_width));
  656. EXPECT_EQ(1u, lines.size());
  657. EXPECT_EQ(u"Test", lines[0]);
  658. // First word truncated.
  659. EXPECT_EQ(INSUFFICIENT_SPACE_FOR_FIRST_WORD,
  660. result_for_width(u"Test", tes_width));
  661. EXPECT_EQ(2u, lines.size());
  662. EXPECT_EQ(u"Tes", lines[0]);
  663. EXPECT_EQ(u"t", lines[1]);
  664. // Two words truncated.
  665. EXPECT_EQ(INSUFFICIENT_SPACE_FOR_FIRST_WORD,
  666. result_for_width(u"Test\nTest", tes_width));
  667. EXPECT_EQ(4u, lines.size());
  668. EXPECT_EQ(u"Tes", lines[0]);
  669. EXPECT_EQ(u"t", lines[1]);
  670. EXPECT_EQ(u"Tes", lines[2]);
  671. EXPECT_EQ(u"t", lines[3]);
  672. // Word truncated, but not the first.
  673. EXPECT_EQ(0, result_for_width(u"T Test", tes_width));
  674. EXPECT_EQ(3u, lines.size());
  675. EXPECT_EQ(u"T", lines[0]);
  676. EXPECT_EQ(u"Tes", lines[1]);
  677. EXPECT_EQ(u"t", lines[2]);
  678. // Leading \n.
  679. EXPECT_EQ(0, result_for_width(u"\nTest", tes_width));
  680. EXPECT_EQ(3u, lines.size());
  681. EXPECT_EQ(u"", lines[0]);
  682. EXPECT_EQ(u"Tes", lines[1]);
  683. EXPECT_EQ(u"t", lines[2]);
  684. }
  685. TEST(TextEliderTest, ElideRectangleTextPunctuation) {
  686. const FontList font_list;
  687. const int line_height = font_list.GetHeight();
  688. const float test_width = GetStringWidthF(u"Test", font_list);
  689. const float test_t_width = GetStringWidthF(u"Test T", font_list);
  690. constexpr int kResultMask =
  691. INSUFFICIENT_SPACE_HORIZONTAL | INSUFFICIENT_SPACE_VERTICAL;
  692. struct TestData {
  693. const char16_t* input;
  694. float available_pixel_width;
  695. int available_pixel_height;
  696. bool wrap_words;
  697. bool truncated_x;
  698. const char16_t* output;
  699. } cases[] = {
  700. {u"Test T.", test_t_width, line_height * 2, false, false, u"Test|T."},
  701. {u"Test T ?", test_t_width, line_height * 2, false, false, u"Test|T ?"},
  702. {u"Test. Test", test_width, line_height * 3, false, true, u"Test|Test"},
  703. {u"Test. Test", test_width, line_height * 3, true, false, u"Test|.|Test"},
  704. };
  705. for (size_t i = 0; i < std::size(cases); ++i) {
  706. std::vector<std::u16string> lines;
  707. const WordWrapBehavior wrap_behavior =
  708. (cases[i].wrap_words ? WRAP_LONG_WORDS : TRUNCATE_LONG_WORDS);
  709. EXPECT_EQ(cases[i].truncated_x ? INSUFFICIENT_SPACE_HORIZONTAL : 0,
  710. ElideRectangleText(
  711. cases[i].input, font_list, cases[i].available_pixel_width,
  712. cases[i].available_pixel_height, wrap_behavior, &lines) &
  713. kResultMask);
  714. if (cases[i].output) {
  715. const std::u16string result = base::JoinString(lines, u"|");
  716. EXPECT_EQ(cases[i].output, result) << "Case " << i << " failed!";
  717. } else {
  718. EXPECT_TRUE(lines.empty()) << "Case " << i << " failed!";
  719. }
  720. }
  721. }
  722. TEST(TextEliderTest, ElideRectangleTextLongWords) {
  723. const FontList font_list;
  724. const int kAvailableHeight = 1000;
  725. const std::u16string kElidedTesting = u"Tes…";
  726. const float elided_width = GetStringWidthF(kElidedTesting, font_list);
  727. const float test_width = GetStringWidthF(u"Test", font_list);
  728. constexpr int kResultMask =
  729. INSUFFICIENT_SPACE_HORIZONTAL | INSUFFICIENT_SPACE_VERTICAL;
  730. struct TestData {
  731. const char16_t* input;
  732. float available_pixel_width;
  733. WordWrapBehavior wrap_behavior;
  734. bool truncated_x;
  735. const char16_t* output;
  736. } cases[] = {
  737. {u"Testing", test_width, IGNORE_LONG_WORDS, false, u"Testing"},
  738. {u"X Testing", test_width, IGNORE_LONG_WORDS, false, u"X|Testing"},
  739. {u"Test Testing", test_width, IGNORE_LONG_WORDS, false, u"Test|Testing"},
  740. {u"Test\nTesting", test_width, IGNORE_LONG_WORDS, false, u"Test|Testing"},
  741. {u"Test Tests ", test_width, IGNORE_LONG_WORDS, false, u"Test|Tests"},
  742. {u"Test Tests T", test_width, IGNORE_LONG_WORDS, false, u"Test|Tests|T"},
  743. {u"Testing", elided_width, ELIDE_LONG_WORDS, true, u"Tes…"},
  744. {u"X Testing", elided_width, ELIDE_LONG_WORDS, true, u"X|Tes…"},
  745. {u"Test Testing", elided_width, ELIDE_LONG_WORDS, true, u"Test|Tes…"},
  746. {u"Test\nTesting", elided_width, ELIDE_LONG_WORDS, true, u"Test|Tes…"},
  747. {u"Testing", test_width, TRUNCATE_LONG_WORDS, true, u"Test"},
  748. {u"X Testing", test_width, TRUNCATE_LONG_WORDS, true, u"X|Test"},
  749. {u"Test Testing", test_width, TRUNCATE_LONG_WORDS, true, u"Test|Test"},
  750. {u"Test\nTesting", test_width, TRUNCATE_LONG_WORDS, true, u"Test|Test"},
  751. {u"Test Tests ", test_width, TRUNCATE_LONG_WORDS, true, u"Test|Test"},
  752. {u"Test Tests T", test_width, TRUNCATE_LONG_WORDS, true, u"Test|Test|T"},
  753. {u"Testing", test_width, WRAP_LONG_WORDS, false, u"Test|ing"},
  754. {u"X Testing", test_width, WRAP_LONG_WORDS, false, u"X|Test|ing"},
  755. {u"Test Testing", test_width, WRAP_LONG_WORDS, false, u"Test|Test|ing"},
  756. {u"Test\nTesting", test_width, WRAP_LONG_WORDS, false, u"Test|Test|ing"},
  757. {u"Test Tests ", test_width, WRAP_LONG_WORDS, false, u"Test|Test|s"},
  758. {u"Test Tests T", test_width, WRAP_LONG_WORDS, false, u"Test|Test|s T"},
  759. {u"TestTestTest", test_width, WRAP_LONG_WORDS, false, u"Test|Test|Test"},
  760. {u"TestTestTestT", test_width, WRAP_LONG_WORDS, false,
  761. u"Test|Test|Test|T"},
  762. };
  763. for (size_t i = 0; i < std::size(cases); ++i) {
  764. std::vector<std::u16string> lines;
  765. EXPECT_EQ(cases[i].truncated_x ? INSUFFICIENT_SPACE_HORIZONTAL : 0,
  766. ElideRectangleText(
  767. cases[i].input, font_list, cases[i].available_pixel_width,
  768. kAvailableHeight, cases[i].wrap_behavior, &lines) &
  769. kResultMask);
  770. std::u16string expected_output(cases[i].output);
  771. const std::u16string result = base::JoinString(lines, u"|");
  772. EXPECT_EQ(expected_output, result) << "Case " << i << " failed!";
  773. }
  774. }
  775. // This test is to make sure that the width of each wrapped line does not
  776. // exceed the available width. On some platform like Mac, this test used to
  777. // fail because the truncated integer width is returned for the string
  778. // and the accumulation of the truncated values causes the elide function
  779. // to wrap incorrectly.
  780. TEST(TextEliderTest, ElideRectangleTextCheckLineWidth) {
  781. FontList font_list;
  782. #if BUILDFLAG(IS_MAC)
  783. // Use a specific font to expose the line width exceeding problem.
  784. font_list = FontList(Font("LucidaGrande", 12));
  785. #endif
  786. const float kAvailableWidth = 235;
  787. const int kAvailableHeight = 1000;
  788. const char16_t text[] = u"that Russian place we used to go to after fencing";
  789. std::vector<std::u16string> lines;
  790. EXPECT_EQ(0, ElideRectangleText(text, font_list, kAvailableWidth,
  791. kAvailableHeight, WRAP_LONG_WORDS, &lines));
  792. ASSERT_EQ(2u, lines.size());
  793. EXPECT_LE(GetStringWidthF(lines[0], font_list), kAvailableWidth);
  794. EXPECT_LE(GetStringWidthF(lines[1], font_list), kAvailableWidth);
  795. }
  796. #if BUILDFLAG(IS_CHROMEOS_ASH)
  797. // This test was created specifically to test a message from crbug.com/415213.
  798. // It tests that width of concatenation of words equals sum of widths of the
  799. // words.
  800. TEST(TextEliderTest, ElideRectangleTextCheckConcatWidthEqualsSumOfWidths) {
  801. FontList font_list;
  802. font_list = FontList("Noto Sans UI,ui-sans, 12px");
  803. SetFontRenderParamsDeviceScaleFactor(1.25f);
  804. #define WIDTH(x) GetStringWidthF((x), font_list)
  805. EXPECT_EQ(WIDTH(u"The administrator for this account has"),
  806. WIDTH(u"The ") + WIDTH(u"administrator ") + WIDTH(u"for ") +
  807. WIDTH(u"this ") + WIDTH(u"account ") + WIDTH(u"has"));
  808. #undef WIDTH
  809. SetFontRenderParamsDeviceScaleFactor(1.0f);
  810. }
  811. #endif // BUILDFLAG(IS_CHROMEOS_ASH)
  812. TEST(TextEliderTest, ElideRectangleString) {
  813. struct TestData {
  814. const char16_t* input;
  815. int max_rows;
  816. int max_cols;
  817. bool result;
  818. const char16_t* output;
  819. } cases[] = {
  820. {u"", 0, 0, false, u""},
  821. {u"", 1, 1, false, u""},
  822. {u"Hi, my name is\nTom", 0, 0, true, u"..."},
  823. {u"Hi, my name is\nTom", 1, 0, true, u"\n..."},
  824. {u"Hi, my name is\nTom", 0, 1, true, u"..."},
  825. {u"Hi, my name is\nTom", 1, 1, true, u"H\n..."},
  826. {u"Hi, my name is\nTom", 2, 1, true, u"H\ni\n..."},
  827. {u"Hi, my name is\nTom", 3, 1, true, u"H\ni\n,\n..."},
  828. {u"Hi, my name is\nTom", 4, 1, true, u"H\ni\n,\n \n..."},
  829. {u"Hi, my name is\nTom", 5, 1, true, u"H\ni\n,\n \nm\n..."},
  830. {u"Hi, my name is\nTom", 0, 2, true, u"..."},
  831. {u"Hi, my name is\nTom", 1, 2, true, u"Hi\n..."},
  832. {u"Hi, my name is\nTom", 2, 2, true, u"Hi\n, \n..."},
  833. {u"Hi, my name is\nTom", 3, 2, true, u"Hi\n, \nmy\n..."},
  834. {u"Hi, my name is\nTom", 4, 2, true, u"Hi\n, \nmy\n n\n..."},
  835. {u"Hi, my name is\nTom", 5, 2, true, u"Hi\n, \nmy\n n\nam\n..."},
  836. {u"Hi, my name is\nTom", 0, 3, true, u"..."},
  837. {u"Hi, my name is\nTom", 1, 3, true, u"Hi,\n..."},
  838. {u"Hi, my name is\nTom", 2, 3, true, u"Hi,\n my\n..."},
  839. {u"Hi, my name is\nTom", 3, 3, true, u"Hi,\n my\n na\n..."},
  840. {u"Hi, my name is\nTom", 4, 3, true, u"Hi,\n my\n na\nme \n..."},
  841. {u"Hi, my name is\nTom", 5, 3, true, u"Hi,\n my\n na\nme \nis\n..."},
  842. {u"Hi, my name is\nTom", 1, 4, true, u"Hi, \n..."},
  843. {u"Hi, my name is\nTom", 2, 4, true, u"Hi, \nmy n\n..."},
  844. {u"Hi, my name is\nTom", 3, 4, true, u"Hi, \nmy n\name \n..."},
  845. {u"Hi, my name is\nTom", 4, 4, true, u"Hi, \nmy n\name \nis\n..."},
  846. {u"Hi, my name is\nTom", 5, 4, false, u"Hi, \nmy n\name \nis\nTom"},
  847. {u"Hi, my name is\nTom", 1, 5, true, u"Hi, \n..."},
  848. {u"Hi, my name is\nTom", 2, 5, true, u"Hi, \nmy na\n..."},
  849. {u"Hi, my name is\nTom", 3, 5, true, u"Hi, \nmy na\nme \n..."},
  850. {u"Hi, my name is\nTom", 4, 5, true, u"Hi, \nmy na\nme \nis\n..."},
  851. {u"Hi, my name is\nTom", 5, 5, false, u"Hi, \nmy na\nme \nis\nTom"},
  852. {u"Hi, my name is\nTom", 1, 6, true, u"Hi, \n..."},
  853. {u"Hi, my name is\nTom", 2, 6, true, u"Hi, \nmy \n..."},
  854. {u"Hi, my name is\nTom", 3, 6, true, u"Hi, \nmy \nname \n..."},
  855. {u"Hi, my name is\nTom", 4, 6, true, u"Hi, \nmy \nname \nis\n..."},
  856. {u"Hi, my name is\nTom", 5, 6, false, u"Hi, \nmy \nname \nis\nTom"},
  857. {u"Hi, my name is\nTom", 1, 7, true, u"Hi, \n..."},
  858. {u"Hi, my name is\nTom", 2, 7, true, u"Hi, \nmy \n..."},
  859. {u"Hi, my name is\nTom", 3, 7, true, u"Hi, \nmy \nname \n..."},
  860. {u"Hi, my name is\nTom", 4, 7, true, u"Hi, \nmy \nname \nis\n..."},
  861. {u"Hi, my name is\nTom", 5, 7, false, u"Hi, \nmy \nname \nis\nTom"},
  862. {u"Hi, my name is\nTom", 1, 8, true, u"Hi, my \n..."},
  863. {u"Hi, my name is\nTom", 2, 8, true, u"Hi, my \nname \n..."},
  864. {u"Hi, my name is\nTom", 3, 8, true, u"Hi, my \nname \nis\n..."},
  865. {u"Hi, my name is\nTom", 4, 8, false, u"Hi, my \nname \nis\nTom"},
  866. {u"Hi, my name is\nTom", 1, 9, true, u"Hi, my \n..."},
  867. {u"Hi, my name is\nTom", 2, 9, true, u"Hi, my \nname is\n..."},
  868. {u"Hi, my name is\nTom", 3, 9, false, u"Hi, my \nname is\nTom"},
  869. {u"Hi, my name is\nTom", 1, 10, true, u"Hi, my \n..."},
  870. {u"Hi, my name is\nTom", 2, 10, true, u"Hi, my \nname is\n..."},
  871. {u"Hi, my name is\nTom", 3, 10, false, u"Hi, my \nname is\nTom"},
  872. {u"Hi, my name is\nTom", 1, 11, true, u"Hi, my \n..."},
  873. {u"Hi, my name is\nTom", 2, 11, true, u"Hi, my \nname is\n..."},
  874. {u"Hi, my name is\nTom", 3, 11, false, u"Hi, my \nname is\nTom"},
  875. {u"Hi, my name is\nTom", 1, 12, true, u"Hi, my \n..."},
  876. {u"Hi, my name is\nTom", 2, 12, true, u"Hi, my \nname is\n..."},
  877. {u"Hi, my name is\nTom", 3, 12, false, u"Hi, my \nname is\nTom"},
  878. {u"Hi, my name is\nTom", 1, 13, true, u"Hi, my name \n..."},
  879. {u"Hi, my name is\nTom", 2, 13, true, u"Hi, my name \nis\n..."},
  880. {u"Hi, my name is\nTom", 3, 13, false, u"Hi, my name \nis\nTom"},
  881. {u"Hi, my name is\nTom", 1, 20, true, u"Hi, my name is\n..."},
  882. {u"Hi, my name is\nTom", 2, 20, false, u"Hi, my name is\nTom"},
  883. {u"Hi, my name is Tom", 1, 40, false, u"Hi, my name is Tom"},
  884. };
  885. std::u16string output;
  886. for (size_t i = 0; i < std::size(cases); ++i) {
  887. EXPECT_EQ(cases[i].result,
  888. ElideRectangleString(cases[i].input, cases[i].max_rows,
  889. cases[i].max_cols, true, &output));
  890. EXPECT_EQ(cases[i].output, output);
  891. }
  892. }
  893. TEST(TextEliderTest, ElideRectangleStringNotStrict) {
  894. struct TestData {
  895. const char16_t* input;
  896. int max_rows;
  897. int max_cols;
  898. bool result;
  899. const char16_t* output;
  900. } cases[] = {
  901. {u"", 0, 0, false, u""},
  902. {u"", 1, 1, false, u""},
  903. {u"Hi, my name_is\nDick", 0, 0, true, u"..."},
  904. {u"Hi, my name_is\nDick", 1, 0, true, u"\n..."},
  905. {u"Hi, my name_is\nDick", 0, 1, true, u"..."},
  906. {u"Hi, my name_is\nDick", 1, 1, true, u"H\n..."},
  907. {u"Hi, my name_is\nDick", 2, 1, true, u"H\ni\n..."},
  908. {u"Hi, my name_is\nDick", 3, 1, true, u"H\ni\n,\n..."},
  909. {u"Hi, my name_is\nDick", 4, 1, true, u"H\ni\n,\n \n..."},
  910. {u"Hi, my name_is\nDick", 5, 1, true, u"H\ni\n,\n \nm\n..."},
  911. {u"Hi, my name_is\nDick", 0, 2, true, u"..."},
  912. {u"Hi, my name_is\nDick", 1, 2, true, u"Hi\n..."},
  913. {u"Hi, my name_is\nDick", 2, 2, true, u"Hi\n, \n..."},
  914. {u"Hi, my name_is\nDick", 3, 2, true, u"Hi\n, \nmy\n..."},
  915. {u"Hi, my name_is\nDick", 4, 2, true, u"Hi\n, \nmy\n n\n..."},
  916. {u"Hi, my name_is\nDick", 5, 2, true, u"Hi\n, \nmy\n n\nam\n..."},
  917. {u"Hi, my name_is\nDick", 0, 3, true, u"..."},
  918. {u"Hi, my name_is\nDick", 1, 3, true, u"Hi,\n..."},
  919. {u"Hi, my name_is\nDick", 2, 3, true, u"Hi,\n my\n..."},
  920. {u"Hi, my name_is\nDick", 3, 3, true, u"Hi,\n my\n na\n..."},
  921. {u"Hi, my name_is\nDick", 4, 3, true, u"Hi,\n my\n na\nme_\n..."},
  922. {u"Hi, my name_is\nDick", 5, 3, true, u"Hi,\n my\n na\nme_\nis\n..."},
  923. {u"Hi, my name_is\nDick", 1, 4, true, u"Hi, ..."},
  924. {u"Hi, my name_is\nDick", 2, 4, true, u"Hi, my n\n..."},
  925. {u"Hi, my name_is\nDick", 3, 4, true, u"Hi, my n\name_\n..."},
  926. {u"Hi, my name_is\nDick", 4, 4, true, u"Hi, my n\name_\nis\n..."},
  927. {u"Hi, my name_is\nDick", 5, 4, false, u"Hi, my n\name_\nis\nDick"},
  928. {u"Hi, my name_is\nDick", 1, 5, true, u"Hi, ..."},
  929. {u"Hi, my name_is\nDick", 2, 5, true, u"Hi, my na\n..."},
  930. {u"Hi, my name_is\nDick", 3, 5, true, u"Hi, my na\nme_is\n..."},
  931. {u"Hi, my name_is\nDick", 4, 5, true, u"Hi, my na\nme_is\n\n..."},
  932. {u"Hi, my name_is\nDick", 5, 5, false, u"Hi, my na\nme_is\n\nDick"},
  933. {u"Hi, my name_is\nDick", 1, 6, true, u"Hi, ..."},
  934. {u"Hi, my name_is\nDick", 2, 6, true, u"Hi, my nam\n..."},
  935. {u"Hi, my name_is\nDick", 3, 6, true, u"Hi, my nam\ne_is\n..."},
  936. {u"Hi, my name_is\nDick", 4, 6, false, u"Hi, my nam\ne_is\nDick"},
  937. {u"Hi, my name_is\nDick", 5, 6, false, u"Hi, my nam\ne_is\nDick"},
  938. {u"Hi, my name_is\nDick", 1, 7, true, u"Hi, ..."},
  939. {u"Hi, my name_is\nDick", 2, 7, true, u"Hi, my name\n..."},
  940. {u"Hi, my name_is\nDick", 3, 7, true, u"Hi, my name\n_is\n..."},
  941. {u"Hi, my name_is\nDick", 4, 7, false, u"Hi, my name\n_is\nDick"},
  942. {u"Hi, my name_is\nDick", 5, 7, false, u"Hi, my name\n_is\nDick"},
  943. {u"Hi, my name_is\nDick", 1, 8, true, u"Hi, my n\n..."},
  944. {u"Hi, my name_is\nDick", 2, 8, true, u"Hi, my n\name_is\n..."},
  945. {u"Hi, my name_is\nDick", 3, 8, false, u"Hi, my n\name_is\nDick"},
  946. {u"Hi, my name_is\nDick", 1, 9, true, u"Hi, my ..."},
  947. {u"Hi, my name_is\nDick", 2, 9, true, u"Hi, my name_is\n..."},
  948. {u"Hi, my name_is\nDick", 3, 9, false, u"Hi, my name_is\nDick"},
  949. {u"Hi, my name_is\nDick", 1, 10, true, u"Hi, my ..."},
  950. {u"Hi, my name_is\nDick", 2, 10, true, u"Hi, my name_is\n..."},
  951. {u"Hi, my name_is\nDick", 3, 10, false, u"Hi, my name_is\nDick"},
  952. {u"Hi, my name_is\nDick", 1, 11, true, u"Hi, my ..."},
  953. {u"Hi, my name_is\nDick", 2, 11, true, u"Hi, my name_is\n..."},
  954. {u"Hi, my name_is\nDick", 3, 11, false, u"Hi, my name_is\nDick"},
  955. {u"Hi, my name_is\nDick", 1, 12, true, u"Hi, my ..."},
  956. {u"Hi, my name_is\nDick", 2, 12, true, u"Hi, my name_is\n..."},
  957. {u"Hi, my name_is\nDick", 3, 12, false, u"Hi, my name_is\nDick"},
  958. {u"Hi, my name_is\nDick", 1, 13, true, u"Hi, my ..."},
  959. {u"Hi, my name_is\nDick", 2, 13, true, u"Hi, my name_is\n..."},
  960. {u"Hi, my name_is\nDick", 3, 13, false, u"Hi, my name_is\nDick"},
  961. {u"Hi, my name_is\nDick", 1, 20, true, u"Hi, my name_is\n..."},
  962. {u"Hi, my name_is\nDick", 2, 20, false, u"Hi, my name_is\nDick"},
  963. {u"Hi, my name_is Dick", 1, 40, false, u"Hi, my name_is Dick"},
  964. };
  965. std::u16string output;
  966. for (size_t i = 0; i < std::size(cases); ++i) {
  967. EXPECT_EQ(cases[i].result,
  968. ElideRectangleString(cases[i].input, cases[i].max_rows,
  969. cases[i].max_cols, false, &output));
  970. EXPECT_EQ(cases[i].output, output);
  971. }
  972. }
  973. TEST(TextEliderTest, ElideRectangleWide16) {
  974. // Two greek words separated by space.
  975. const std::u16string str(u"Παγκόσμιος Ιστός");
  976. const std::u16string out1(u"Παγκ\nόσμι\n...");
  977. const std::u16string out2(u"Παγκόσμιος \nΙστός");
  978. std::u16string output;
  979. EXPECT_TRUE(ElideRectangleString(str, 2, 4, true, &output));
  980. EXPECT_EQ(out1, output);
  981. EXPECT_FALSE(ElideRectangleString(str, 2, 12, true, &output));
  982. EXPECT_EQ(out2, output);
  983. }
  984. TEST(TextEliderTest, ElideRectangleWide32) {
  985. const std::u16string str(u"𝒜𝒜𝒜𝒜 aaaaa");
  986. const std::u16string out(u"𝒜𝒜𝒜\n𝒜 \naaa\n...");
  987. std::u16string output;
  988. EXPECT_TRUE(ElideRectangleString(str, 3, 3, true, &output));
  989. EXPECT_EQ(out, output);
  990. }
  991. TEST(TextEliderTest, TruncateString) {
  992. std::u16string str = u"fooooey bxxxar baz ";
  993. // Test breaking at character 0.
  994. EXPECT_EQ(std::u16string(), TruncateString(str, 0, WORD_BREAK));
  995. EXPECT_EQ(std::u16string(), TruncateString(str, 0, CHARACTER_BREAK));
  996. // Test breaking at character 1.
  997. EXPECT_EQ(u"…", TruncateString(str, 1, WORD_BREAK));
  998. EXPECT_EQ(u"…", TruncateString(str, 1, CHARACTER_BREAK));
  999. // Test breaking in the middle of the first word.
  1000. EXPECT_EQ(u"f…", TruncateString(str, 2, WORD_BREAK));
  1001. EXPECT_EQ(u"f…", TruncateString(str, 2, CHARACTER_BREAK));
  1002. // Test breaking in between words.
  1003. EXPECT_EQ(u"fooooey…", TruncateString(str, 9, WORD_BREAK));
  1004. EXPECT_EQ(u"fooooey…", TruncateString(str, 9, CHARACTER_BREAK));
  1005. // Test breaking at the start of a later word.
  1006. EXPECT_EQ(u"fooooey…", TruncateString(str, 11, WORD_BREAK));
  1007. EXPECT_EQ(u"fooooey…", TruncateString(str, 11, CHARACTER_BREAK));
  1008. // Test breaking in the middle of a word.
  1009. EXPECT_EQ(u"fooooey…", TruncateString(str, 12, WORD_BREAK));
  1010. EXPECT_EQ(u"fooooey…", TruncateString(str, 12, CHARACTER_BREAK));
  1011. EXPECT_EQ(u"fooooey…", TruncateString(str, 14, WORD_BREAK));
  1012. EXPECT_EQ(u"fooooey bx…", TruncateString(str, 14, CHARACTER_BREAK));
  1013. // Test breaking in whitespace at the end of the string.
  1014. EXPECT_EQ(u"fooooey bxxxar baz…", TruncateString(str, 22, WORD_BREAK));
  1015. EXPECT_EQ(u"fooooey bxxxar baz…",
  1016. TruncateString(str, 22, CHARACTER_BREAK));
  1017. // Test breaking at the end of the string.
  1018. EXPECT_EQ(str, TruncateString(str, str.length(), WORD_BREAK));
  1019. EXPECT_EQ(str, TruncateString(str, str.length(), CHARACTER_BREAK));
  1020. // Test breaking past the end of the string.
  1021. EXPECT_EQ(str, TruncateString(str, str.length() + 10, WORD_BREAK));
  1022. EXPECT_EQ(str, TruncateString(str, str.length() + 10, CHARACTER_BREAK));
  1023. // Tests of strings with leading whitespace:
  1024. std::u16string str2 = u" foo";
  1025. // Test breaking in leading whitespace.
  1026. EXPECT_EQ(u"…", TruncateString(str2, 2, WORD_BREAK));
  1027. EXPECT_EQ(u"…", TruncateString(str2, 2, CHARACTER_BREAK));
  1028. // Test breaking at the beginning of the first word, with leading whitespace.
  1029. EXPECT_EQ(u"…", TruncateString(str2, 3, WORD_BREAK));
  1030. EXPECT_EQ(u"…", TruncateString(str2, 3, CHARACTER_BREAK));
  1031. // Test breaking in the middle of the first word, with leading whitespace.
  1032. EXPECT_EQ(u"…", TruncateString(str2, 4, WORD_BREAK));
  1033. EXPECT_EQ(u"…", TruncateString(str2, 4, CHARACTER_BREAK));
  1034. EXPECT_EQ(u" f…", TruncateString(str2, 5, WORD_BREAK));
  1035. EXPECT_EQ(u" f…", TruncateString(str2, 5, CHARACTER_BREAK));
  1036. }
  1037. } // namespace gfx