1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.text; 18 19import static org.junit.Assert.assertEquals; 20import static org.junit.Assert.assertFalse; 21import static org.junit.Assert.assertNotNull; 22import static org.junit.Assert.assertNull; 23import static org.junit.Assert.assertTrue; 24import static org.junit.Assert.fail; 25 26import android.support.test.runner.AndroidJUnit4; 27import com.google.android.collect.Lists; 28 29import android.os.Parcel; 30import android.support.test.filters.LargeTest; 31import android.support.test.filters.SmallTest; 32import android.test.MoreAsserts; 33import android.text.style.StyleSpan; 34import android.text.util.Rfc822Token; 35import android.text.util.Rfc822Tokenizer; 36import android.view.View; 37 38import java.util.ArrayList; 39import java.util.List; 40import java.util.Locale; 41import org.junit.Test; 42import org.junit.runner.RunWith; 43 44/** 45 * TextUtilsTest tests {@link TextUtils}. 46 */ 47@SmallTest 48@RunWith(AndroidJUnit4.class) 49public class TextUtilsTest { 50 51 @Test 52 public void testBasic() { 53 assertEquals("", TextUtils.concat()); 54 assertEquals("foo", TextUtils.concat("foo")); 55 assertEquals("foobar", TextUtils.concat("foo", "bar")); 56 assertEquals("foobarbaz", TextUtils.concat("foo", "bar", "baz")); 57 58 SpannableString foo = new SpannableString("foo"); 59 foo.setSpan("foo", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); 60 61 SpannableString bar = new SpannableString("bar"); 62 bar.setSpan("bar", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); 63 64 SpannableString baz = new SpannableString("baz"); 65 baz.setSpan("baz", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); 66 67 assertEquals("foo", TextUtils.concat(foo).toString()); 68 assertEquals("foobar", TextUtils.concat(foo, bar).toString()); 69 assertEquals("foobarbaz", TextUtils.concat(foo, bar, baz).toString()); 70 71 assertEquals(1, ((Spanned) TextUtils.concat(foo)).getSpanStart("foo")); 72 73 assertEquals(1, ((Spanned) TextUtils.concat(foo, bar)).getSpanStart("foo")); 74 assertEquals(4, ((Spanned) TextUtils.concat(foo, bar)).getSpanStart("bar")); 75 76 assertEquals(1, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("foo")); 77 assertEquals(4, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("bar")); 78 assertEquals(7, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("baz")); 79 80 assertTrue(TextUtils.concat("foo", "bar") instanceof String); 81 assertTrue(TextUtils.concat(foo, bar) instanceof SpannedString); 82 } 83 84 @Test 85 public void testTemplateString() { 86 CharSequence result; 87 88 result = TextUtils.expandTemplate("This is a ^1 of the ^2 broadcast ^3.", 89 "test", "emergency", "system"); 90 assertEquals("This is a test of the emergency broadcast system.", 91 result.toString()); 92 93 result = TextUtils.expandTemplate("^^^1^^^2^3^a^1^^b^^^c", 94 "one", "two", "three"); 95 assertEquals("^one^twothree^aone^b^^c", 96 result.toString()); 97 98 result = TextUtils.expandTemplate("^"); 99 assertEquals("^", result.toString()); 100 101 result = TextUtils.expandTemplate("^^"); 102 assertEquals("^", result.toString()); 103 104 result = TextUtils.expandTemplate("^^^"); 105 assertEquals("^^", result.toString()); 106 107 result = TextUtils.expandTemplate("shorter ^1 values ^2.", "a", ""); 108 assertEquals("shorter a values .", result.toString()); 109 110 try { 111 TextUtils.expandTemplate("Only ^1 value given, but ^2 used.", "foo"); 112 fail(); 113 } catch (IllegalArgumentException e) { 114 } 115 116 try { 117 TextUtils.expandTemplate("^1 value given, and ^0 used.", "foo"); 118 fail(); 119 } catch (IllegalArgumentException e) { 120 } 121 122 result = TextUtils.expandTemplate("^1 value given, and ^9 used.", 123 "one", "two", "three", "four", "five", 124 "six", "seven", "eight", "nine"); 125 assertEquals("one value given, and nine used.", result.toString()); 126 127 try { 128 TextUtils.expandTemplate("^1 value given, and ^10 used.", 129 "one", "two", "three", "four", "five", 130 "six", "seven", "eight", "nine", "ten"); 131 fail(); 132 } catch (IllegalArgumentException e) { 133 } 134 135 // putting carets in the values: expansion is not recursive. 136 137 result = TextUtils.expandTemplate("^2", "foo", "^^"); 138 assertEquals("^^", result.toString()); 139 140 result = TextUtils.expandTemplate("^^2", "foo", "1"); 141 assertEquals("^2", result.toString()); 142 143 result = TextUtils.expandTemplate("^1", "value with ^2 in it", "foo"); 144 assertEquals("value with ^2 in it", result.toString()); 145 } 146 147 /** Fail unless text+spans contains a span 'spanName' with the given start and end. */ 148 private void checkContains(Spanned text, String[] spans, String spanName, 149 int start, int end) { 150 for (String i: spans) { 151 if (i.equals(spanName)) { 152 assertEquals(start, text.getSpanStart(i)); 153 assertEquals(end, text.getSpanEnd(i)); 154 return; 155 } 156 } 157 fail(); 158 } 159 160 @Test 161 public void testTemplateSpan() { 162 SpannableString template; 163 Spanned result; 164 String[] spans; 165 166 // ordinary replacement 167 168 template = new SpannableString("a^1b"); 169 template.setSpan("before", 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 170 template.setSpan("during", 1, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 171 template.setSpan("after", 3, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 172 template.setSpan("during+after", 1, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 173 174 result = (Spanned) TextUtils.expandTemplate(template, "foo"); 175 assertEquals(5, result.length()); 176 spans = result.getSpans(0, result.length(), String.class); 177 178 // value is one character longer, so span endpoints should change. 179 assertEquals(4, spans.length); 180 checkContains(result, spans, "before", 0, 1); 181 checkContains(result, spans, "during", 1, 4); 182 checkContains(result, spans, "after", 4, 5); 183 checkContains(result, spans, "during+after", 1, 5); 184 185 186 // replacement with empty string 187 188 result = (Spanned) TextUtils.expandTemplate(template, ""); 189 assertEquals(2, result.length()); 190 spans = result.getSpans(0, result.length(), String.class); 191 192 // the "during" span should disappear. 193 assertEquals(3, spans.length); 194 checkContains(result, spans, "before", 0, 1); 195 checkContains(result, spans, "after", 1, 2); 196 checkContains(result, spans, "during+after", 1, 2); 197 } 198 199 @Test 200 public void testStringSplitterSimple() { 201 stringSplitterTestHelper("a,b,cde", new String[] {"a", "b", "cde"}); 202 } 203 204 @Test 205 public void testStringSplitterEmpty() { 206 stringSplitterTestHelper("", new String[] {}); 207 } 208 209 @Test 210 public void testStringSplitterWithLeadingEmptyString() { 211 stringSplitterTestHelper(",a,b,cde", new String[] {"", "a", "b", "cde"}); 212 } 213 214 @Test 215 public void testStringSplitterWithInternalEmptyString() { 216 stringSplitterTestHelper("a,b,,cde", new String[] {"a", "b", "", "cde"}); 217 } 218 219 @Test 220 public void testStringSplitterWithTrailingEmptyString() { 221 // A single trailing emtpy string should be ignored. 222 stringSplitterTestHelper("a,b,cde,", new String[] {"a", "b", "cde"}); 223 } 224 225 private void stringSplitterTestHelper(String string, String[] expectedStrings) { 226 TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(','); 227 splitter.setString(string); 228 List<String> strings = Lists.newArrayList(); 229 for (String s : splitter) { 230 strings.add(s); 231 } 232 MoreAsserts.assertEquals(expectedStrings, strings.toArray(new String[]{})); 233 } 234 235 @Test 236 public void testTrim() { 237 String[] strings = { "abc", " abc", " abc", "abc ", "abc ", 238 " abc ", " abc ", "\nabc\n", "\nabc", "abc\n" }; 239 240 for (String s : strings) { 241 assertEquals(s.trim().length(), TextUtils.getTrimmedLength(s)); 242 } 243 } 244 245 @Test 246 public void testRfc822TokenizerFullAddress() { 247 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize("Foo Bar (something) <foo@google.com>"); 248 assertNotNull(tokens); 249 assertEquals(1, tokens.length); 250 assertEquals("foo@google.com", tokens[0].getAddress()); 251 assertEquals("Foo Bar", tokens[0].getName()); 252 assertEquals("something",tokens[0].getComment()); 253 } 254 255 @Test 256 public void testRfc822TokenizeItemWithError() { 257 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize("\"Foo Bar\\"); 258 assertNotNull(tokens); 259 assertEquals(1, tokens.length); 260 assertEquals("Foo Bar", tokens[0].getAddress()); 261 } 262 263 @Test 264 public void testRfc822FindToken() { 265 Rfc822Tokenizer tokenizer = new Rfc822Tokenizer(); 266 // 0 1 2 3 4 267 // 0 1234 56789012345678901234 5678 90123456789012345 268 String address = "\"Foo\" <foo@google.com>, \"Bar\" <bar@google.com>"; 269 assertEquals(0, tokenizer.findTokenStart(address, 21)); 270 assertEquals(22, tokenizer.findTokenEnd(address, 21)); 271 assertEquals(24, tokenizer.findTokenStart(address, 25)); 272 assertEquals(46, tokenizer.findTokenEnd(address, 25)); 273 } 274 275 @Test 276 public void testRfc822FindTokenWithError() { 277 assertEquals(9, new Rfc822Tokenizer().findTokenEnd("\"Foo Bar\\", 0)); 278 } 279 280 @LargeTest 281 @Test 282 public void testEllipsize() { 283 CharSequence s1 = "The quick brown fox jumps over \u00FEhe lazy dog."; 284 CharSequence s2 = new Wrapper(s1); 285 Spannable s3 = new SpannableString(s1); 286 s3.setSpan(new StyleSpan(0), 5, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 287 TextPaint p = new TextPaint(); 288 p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG); 289 290 for (int i = 0; i < 100; i++) { 291 for (int j = 0; j < 3; j++) { 292 TextUtils.TruncateAt kind = null; 293 294 switch (j) { 295 case 0: 296 kind = TextUtils.TruncateAt.START; 297 break; 298 299 case 1: 300 kind = TextUtils.TruncateAt.END; 301 break; 302 303 case 2: 304 kind = TextUtils.TruncateAt.MIDDLE; 305 break; 306 } 307 308 String out1 = TextUtils.ellipsize(s1, p, i, kind).toString(); 309 String out2 = TextUtils.ellipsize(s2, p, i, kind).toString(); 310 String out3 = TextUtils.ellipsize(s3, p, i, kind).toString(); 311 312 String keep1 = TextUtils.ellipsize(s1, p, i, kind, true, null).toString(); 313 String keep2 = TextUtils.ellipsize(s2, p, i, kind, true, null).toString(); 314 String keep3 = TextUtils.ellipsize(s3, p, i, kind, true, null).toString(); 315 316 String trim1 = keep1.replace("\uFEFF", ""); 317 318 // Are all normal output strings identical? 319 assertEquals("wid " + i + " pass " + j, out1, out2); 320 assertEquals("wid " + i + " pass " + j, out2, out3); 321 322 // Are preserved output strings identical? 323 assertEquals("wid " + i + " pass " + j, keep1, keep2); 324 assertEquals("wid " + i + " pass " + j, keep2, keep3); 325 326 // Does trimming padding from preserved yield normal? 327 assertEquals("wid " + i + " pass " + j, out1, trim1); 328 329 // Did preserved output strings preserve length? 330 assertEquals("wid " + i + " pass " + j, keep1.length(), s1.length()); 331 332 // Does the output string actually fit in the space? 333 assertTrue("wid " + i + " pass " + j, p.measureText(out1) <= i); 334 335 // Is the padded output the same width as trimmed output? 336 assertTrue("wid " + i + " pass " + j, p.measureText(keep1) == p.measureText(out1)); 337 } 338 } 339 } 340 341 @Test 342 public void testDelimitedStringContains() { 343 assertFalse(TextUtils.delimitedStringContains("", ',', null)); 344 assertFalse(TextUtils.delimitedStringContains(null, ',', "")); 345 // Whole match 346 assertTrue(TextUtils.delimitedStringContains("gps", ',', "gps")); 347 // At beginning. 348 assertTrue(TextUtils.delimitedStringContains("gps,gpsx,network,mock", ',', "gps")); 349 assertTrue(TextUtils.delimitedStringContains("gps,network,mock", ',', "gps")); 350 // In middle, both without, before & after a false match. 351 assertTrue(TextUtils.delimitedStringContains("network,gps,mock", ',', "gps")); 352 assertTrue(TextUtils.delimitedStringContains("network,gps,gpsx,mock", ',', "gps")); 353 assertTrue(TextUtils.delimitedStringContains("network,gpsx,gps,mock", ',', "gps")); 354 // At the end. 355 assertTrue(TextUtils.delimitedStringContains("network,mock,gps", ',', "gps")); 356 assertTrue(TextUtils.delimitedStringContains("network,mock,gpsx,gps", ',', "gps")); 357 // Not present (but with a false match) 358 assertFalse(TextUtils.delimitedStringContains("network,mock,gpsx", ',', "gps")); 359 } 360 361 @Test 362 public void testCharSequenceCreator() { 363 Parcel p = Parcel.obtain(); 364 TextUtils.writeToParcel(null, p, 0); 365 CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); 366 assertNull("null CharSequence should generate null from parcel", text); 367 p = Parcel.obtain(); 368 TextUtils.writeToParcel("test", p, 0); 369 p.setDataPosition(0); 370 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); 371 assertEquals("conversion to/from parcel failed", "test", text); 372 } 373 374 @Test 375 public void testCharSequenceCreatorNull() { 376 Parcel p; 377 CharSequence text; 378 p = Parcel.obtain(); 379 TextUtils.writeToParcel(null, p, 0); 380 p.setDataPosition(0); 381 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); 382 assertNull("null CharSequence should generate null from parcel", text); 383 } 384 385 @Test 386 public void testCharSequenceCreatorSpannable() { 387 Parcel p; 388 CharSequence text; 389 p = Parcel.obtain(); 390 TextUtils.writeToParcel(new SpannableString("test"), p, 0); 391 p.setDataPosition(0); 392 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); 393 assertEquals("conversion to/from parcel failed", "test", text.toString()); 394 } 395 396 @Test 397 public void testCharSequenceCreatorString() { 398 Parcel p; 399 CharSequence text; 400 p = Parcel.obtain(); 401 TextUtils.writeToParcel("test", p, 0); 402 p.setDataPosition(0); 403 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); 404 assertEquals("conversion to/from parcel failed", "test", text.toString()); 405 } 406 407 /** 408 * CharSequence wrapper for testing the cases where text is copied into 409 * a char array instead of working from a String or a Spanned. 410 */ 411 private static class Wrapper implements CharSequence { 412 private CharSequence mString; 413 414 public Wrapper(CharSequence s) { 415 mString = s; 416 } 417 418 @Override 419 public int length() { 420 return mString.length(); 421 } 422 423 @Override 424 public char charAt(int off) { 425 return mString.charAt(off); 426 } 427 428 @Override 429 public String toString() { 430 return mString.toString(); 431 } 432 433 @Override 434 public CharSequence subSequence(int start, int end) { 435 return new Wrapper(mString.subSequence(start, end)); 436 } 437 } 438 439 @Test 440 public void testRemoveEmptySpans() { 441 MockSpanned spanned = new MockSpanned(); 442 443 spanned.test(); 444 spanned.addSpan().test(); 445 spanned.addSpan().test(); 446 spanned.addSpan().test(); 447 spanned.addEmptySpan().test(); 448 spanned.addSpan().test(); 449 spanned.addEmptySpan().test(); 450 spanned.addEmptySpan().test(); 451 spanned.addSpan().test(); 452 453 spanned.clear(); 454 spanned.addEmptySpan().test(); 455 spanned.addEmptySpan().test(); 456 spanned.addEmptySpan().test(); 457 spanned.addSpan().test(); 458 spanned.addEmptySpan().test(); 459 spanned.addSpan().test(); 460 461 spanned.clear(); 462 spanned.addSpan().test(); 463 spanned.addEmptySpan().test(); 464 spanned.addSpan().test(); 465 spanned.addEmptySpan().test(); 466 spanned.addSpan().test(); 467 spanned.addSpan().test(); 468 } 469 470 protected static class MockSpanned implements Spanned { 471 472 private List<Object> allSpans = new ArrayList<Object>(); 473 private List<Object> nonEmptySpans = new ArrayList<Object>(); 474 475 public void clear() { 476 allSpans.clear(); 477 nonEmptySpans.clear(); 478 } 479 480 public MockSpanned addSpan() { 481 Object o = new Object(); 482 allSpans.add(o); 483 nonEmptySpans.add(o); 484 return this; 485 } 486 487 public MockSpanned addEmptySpan() { 488 Object o = new Object(); 489 allSpans.add(o); 490 return this; 491 } 492 493 public void test() { 494 Object[] nonEmpty = TextUtils.removeEmptySpans(allSpans.toArray(), this, Object.class); 495 assertEquals("Mismatched array size", nonEmptySpans.size(), nonEmpty.length); 496 for (int i=0; i<nonEmpty.length; i++) { 497 assertEquals("Span differ", nonEmptySpans.get(i), nonEmpty[i]); 498 } 499 } 500 501 @Override 502 public char charAt(int arg0) { 503 return 0; 504 } 505 506 @Override 507 public int length() { 508 return 0; 509 } 510 511 @Override 512 public CharSequence subSequence(int arg0, int arg1) { 513 return null; 514 } 515 516 @Override 517 public <T> T[] getSpans(int start, int end, Class<T> type) { 518 return null; 519 } 520 521 @Override 522 public int getSpanStart(Object tag) { 523 return 0; 524 } 525 526 @Override 527 public int getSpanEnd(Object tag) { 528 return nonEmptySpans.contains(tag) ? 1 : 0; 529 } 530 531 @Override 532 public int getSpanFlags(Object tag) { 533 return 0; 534 } 535 536 @Override 537 public int nextSpanTransition(int start, int limit, Class type) { 538 return 0; 539 } 540 } 541 542 @Test 543 public void testGetLayoutDirectionFromLocale() { 544 assertEquals(View.LAYOUT_DIRECTION_LTR, TextUtils.getLayoutDirectionFromLocale(null)); 545 assertEquals(View.LAYOUT_DIRECTION_LTR, 546 TextUtils.getLayoutDirectionFromLocale(Locale.ROOT)); 547 assertEquals(View.LAYOUT_DIRECTION_LTR, 548 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("en"))); 549 assertEquals(View.LAYOUT_DIRECTION_LTR, 550 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("en-US"))); 551 assertEquals(View.LAYOUT_DIRECTION_LTR, 552 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az"))); 553 assertEquals(View.LAYOUT_DIRECTION_LTR, 554 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-AZ"))); 555 assertEquals(View.LAYOUT_DIRECTION_LTR, 556 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-Latn"))); 557 assertEquals(View.LAYOUT_DIRECTION_LTR, 558 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("en-EG"))); 559 assertEquals(View.LAYOUT_DIRECTION_LTR, 560 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("ar-Latn"))); 561 562 assertEquals(View.LAYOUT_DIRECTION_RTL, 563 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("ar"))); 564 assertEquals(View.LAYOUT_DIRECTION_RTL, 565 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("fa"))); 566 assertEquals(View.LAYOUT_DIRECTION_RTL, 567 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("he"))); 568 assertEquals(View.LAYOUT_DIRECTION_RTL, 569 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("iw"))); 570 assertEquals(View.LAYOUT_DIRECTION_RTL, 571 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("ur"))); 572 assertEquals(View.LAYOUT_DIRECTION_RTL, 573 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("dv"))); 574 assertEquals(View.LAYOUT_DIRECTION_RTL, 575 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-Arab"))); 576 assertEquals(View.LAYOUT_DIRECTION_RTL, 577 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-IR"))); 578 assertEquals(View.LAYOUT_DIRECTION_RTL, 579 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("fa-US"))); 580 assertEquals(View.LAYOUT_DIRECTION_RTL, 581 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("tr-Arab"))); 582 } 583} 584