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