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