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 17 package android.text; 18 19 import android.graphics.Paint; 20 import android.test.suitebuilder.annotation.LargeTest; 21 import android.test.suitebuilder.annotation.SmallTest; 22 import android.text.Spannable; 23 import android.text.SpannableString; 24 import android.text.Spanned; 25 import android.text.SpannedString; 26 import android.text.TextPaint; 27 import android.text.TextUtils; 28 import android.text.style.StyleSpan; 29 import android.text.util.Rfc822Token; 30 import android.text.util.Rfc822Tokenizer; 31 import android.test.MoreAsserts; 32 33 import com.google.android.collect.Lists; 34 import com.google.android.collect.Maps; 35 36 import junit.framework.TestCase; 37 38 import java.util.List; 39 import java.util.Map; 40 41 /** 42 * TextUtilsTest tests {@link TextUtils}. 43 */ 44 public class TextUtilsTest extends TestCase { 45 46 @SmallTest 47 public void testBasic() throws Exception { 48 assertEquals("", TextUtils.concat()); 49 assertEquals("foo", TextUtils.concat("foo")); 50 assertEquals("foobar", TextUtils.concat("foo", "bar")); 51 assertEquals("foobarbaz", TextUtils.concat("foo", "bar", "baz")); 52 53 SpannableString foo = new SpannableString("foo"); 54 foo.setSpan("foo", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); 55 56 SpannableString bar = new SpannableString("bar"); 57 bar.setSpan("bar", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); 58 59 SpannableString baz = new SpannableString("baz"); 60 baz.setSpan("baz", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); 61 62 assertEquals("foo", TextUtils.concat(foo).toString()); 63 assertEquals("foobar", TextUtils.concat(foo, bar).toString()); 64 assertEquals("foobarbaz", TextUtils.concat(foo, bar, baz).toString()); 65 66 assertEquals(1, ((Spanned) TextUtils.concat(foo)).getSpanStart("foo")); 67 68 assertEquals(1, ((Spanned) TextUtils.concat(foo, bar)).getSpanStart("foo")); 69 assertEquals(4, ((Spanned) TextUtils.concat(foo, bar)).getSpanStart("bar")); 70 71 assertEquals(1, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("foo")); 72 assertEquals(4, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("bar")); 73 assertEquals(7, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("baz")); 74 75 assertTrue(TextUtils.concat("foo", "bar") instanceof String); 76 assertTrue(TextUtils.concat(foo, bar) instanceof SpannedString); 77 } 78 79 @SmallTest 80 public void testTemplateString() throws Exception { 81 CharSequence result; 82 83 result = TextUtils.expandTemplate("This is a ^1 of the ^2 broadcast ^3.", 84 "test", "emergency", "system"); 85 assertEquals("This is a test of the emergency broadcast system.", 86 result.toString()); 87 88 result = TextUtils.expandTemplate("^^^1^^^2^3^a^1^^b^^^c", 89 "one", "two", "three"); 90 assertEquals("^one^twothree^aone^b^^c", 91 result.toString()); 92 93 result = TextUtils.expandTemplate("^"); 94 assertEquals("^", result.toString()); 95 96 result = TextUtils.expandTemplate("^^"); 97 assertEquals("^", result.toString()); 98 99 result = TextUtils.expandTemplate("^^^"); 100 assertEquals("^^", result.toString()); 101 102 result = TextUtils.expandTemplate("shorter ^1 values ^2.", "a", ""); 103 assertEquals("shorter a values .", result.toString()); 104 105 try { 106 TextUtils.expandTemplate("Only ^1 value given, but ^2 used.", "foo"); 107 fail(); 108 } catch (IllegalArgumentException e) { 109 } 110 111 try { 112 TextUtils.expandTemplate("^1 value given, and ^0 used.", "foo"); 113 fail(); 114 } catch (IllegalArgumentException e) { 115 } 116 117 result = TextUtils.expandTemplate("^1 value given, and ^9 used.", 118 "one", "two", "three", "four", "five", 119 "six", "seven", "eight", "nine"); 120 assertEquals("one value given, and nine used.", result.toString()); 121 122 try { 123 TextUtils.expandTemplate("^1 value given, and ^10 used.", 124 "one", "two", "three", "four", "five", 125 "six", "seven", "eight", "nine", "ten"); 126 fail(); 127 } catch (IllegalArgumentException e) { 128 } 129 130 // putting carets in the values: expansion is not recursive. 131 132 result = TextUtils.expandTemplate("^2", "foo", "^^"); 133 assertEquals("^^", result.toString()); 134 135 result = TextUtils.expandTemplate("^^2", "foo", "1"); 136 assertEquals("^2", result.toString()); 137 138 result = TextUtils.expandTemplate("^1", "value with ^2 in it", "foo"); 139 assertEquals("value with ^2 in it", result.toString()); 140 } 141 142 /** Fail unless text+spans contains a span 'spanName' with the given start and end. */ 143 private void checkContains(Spanned text, String[] spans, String spanName, 144 int start, int end) throws Exception { 145 for (String i: spans) { 146 if (i.equals(spanName)) { 147 assertEquals(start, text.getSpanStart(i)); 148 assertEquals(end, text.getSpanEnd(i)); 149 return; 150 } 151 } 152 fail(); 153 } 154 155 @SmallTest 156 public void testTemplateSpan() throws Exception { 157 SpannableString template; 158 Spanned result; 159 String[] spans; 160 161 // ordinary replacement 162 163 template = new SpannableString("a^1b"); 164 template.setSpan("before", 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 165 template.setSpan("during", 1, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 166 template.setSpan("after", 3, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 167 template.setSpan("during+after", 1, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 168 169 result = (Spanned) TextUtils.expandTemplate(template, "foo"); 170 assertEquals(5, result.length()); 171 spans = result.getSpans(0, result.length(), String.class); 172 173 // value is one character longer, so span endpoints should change. 174 assertEquals(4, spans.length); 175 checkContains(result, spans, "before", 0, 1); 176 checkContains(result, spans, "during", 1, 4); 177 checkContains(result, spans, "after", 4, 5); 178 checkContains(result, spans, "during+after", 1, 5); 179 180 181 // replacement with empty string 182 183 result = (Spanned) TextUtils.expandTemplate(template, ""); 184 assertEquals(2, result.length()); 185 spans = result.getSpans(0, result.length(), String.class); 186 187 // the "during" span should disappear. 188 assertEquals(3, spans.length); 189 checkContains(result, spans, "before", 0, 1); 190 checkContains(result, spans, "after", 1, 2); 191 checkContains(result, spans, "during+after", 1, 2); 192 } 193 194 @SmallTest 195 public void testStringSplitterSimple() { 196 stringSplitterTestHelper("a,b,cde", new String[] {"a", "b", "cde"}); 197 } 198 199 @SmallTest 200 public void testStringSplitterEmpty() { 201 stringSplitterTestHelper("", new String[] {}); 202 } 203 204 @SmallTest 205 public void testStringSplitterWithLeadingEmptyString() { 206 stringSplitterTestHelper(",a,b,cde", new String[] {"", "a", "b", "cde"}); 207 } 208 209 @SmallTest 210 public void testStringSplitterWithInternalEmptyString() { 211 stringSplitterTestHelper("a,b,,cde", new String[] {"a", "b", "", "cde"}); 212 } 213 214 @SmallTest 215 public void testStringSplitterWithTrailingEmptyString() { 216 // A single trailing emtpy string should be ignored. 217 stringSplitterTestHelper("a,b,cde,", new String[] {"a", "b", "cde"}); 218 } 219 220 private void stringSplitterTestHelper(String string, String[] expectedStrings) { 221 TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(','); 222 splitter.setString(string); 223 List<String> strings = Lists.newArrayList(); 224 for (String s : splitter) { 225 strings.add(s); 226 } 227 MoreAsserts.assertEquals(expectedStrings, strings.toArray(new String[]{})); 228 } 229 230 @SmallTest 231 public void testTrim() { 232 String[] strings = { "abc", " abc", " abc", "abc ", "abc ", 233 " abc ", " abc ", "\nabc\n", "\nabc", "abc\n" }; 234 235 for (String s : strings) { 236 assertEquals(s.trim().length(), TextUtils.getTrimmedLength(s)); 237 } 238 } 239 240 @SmallTest 241 public void testRfc822TokenizerFullAddress() { 242 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize("Foo Bar (something) <foo (at) google.com>"); 243 assertNotNull(tokens); 244 assertEquals(1, tokens.length); 245 assertEquals("foo (at) google.com", tokens[0].getAddress()); 246 assertEquals("Foo Bar", tokens[0].getName()); 247 assertEquals("something",tokens[0].getComment()); 248 } 249 250 @SmallTest 251 public void testRfc822TokenizeItemWithError() { 252 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize("\"Foo Bar\\"); 253 assertNotNull(tokens); 254 assertEquals(1, tokens.length); 255 assertEquals("Foo Bar", tokens[0].getAddress()); 256 } 257 258 @LargeTest 259 public void testEllipsize() { 260 CharSequence s1 = "The quick brown fox jumps over \u00FEhe lazy dog."; 261 CharSequence s2 = new Wrapper(s1); 262 Spannable s3 = new SpannableString(s1); 263 s3.setSpan(new StyleSpan(0), 5, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 264 TextPaint p = new TextPaint(); 265 p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG); 266 267 for (int i = 0; i < 100; i++) { 268 for (int j = 0; j < 3; j++) { 269 TextUtils.TruncateAt kind = null; 270 271 switch (j) { 272 case 0: 273 kind = TextUtils.TruncateAt.START; 274 break; 275 276 case 1: 277 kind = TextUtils.TruncateAt.END; 278 break; 279 280 case 2: 281 kind = TextUtils.TruncateAt.MIDDLE; 282 break; 283 } 284 285 String out1 = TextUtils.ellipsize(s1, p, i, kind).toString(); 286 String out2 = TextUtils.ellipsize(s2, p, i, kind).toString(); 287 String out3 = TextUtils.ellipsize(s3, p, i, kind).toString(); 288 289 String keep1 = TextUtils.ellipsize(s1, p, i, kind, true, null).toString(); 290 String keep2 = TextUtils.ellipsize(s2, p, i, kind, true, null).toString(); 291 String keep3 = TextUtils.ellipsize(s3, p, i, kind, true, null).toString(); 292 293 String trim1 = keep1.replace("\uFEFF", ""); 294 295 // Are all normal output strings identical? 296 assertEquals("wid " + i + " pass " + j, out1, out2); 297 assertEquals("wid " + i + " pass " + j, out2, out3); 298 299 // Are preserved output strings identical? 300 assertEquals("wid " + i + " pass " + j, keep1, keep2); 301 assertEquals("wid " + i + " pass " + j, keep2, keep3); 302 303 // Does trimming padding from preserved yield normal? 304 assertEquals("wid " + i + " pass " + j, out1, trim1); 305 306 // Did preserved output strings preserve length? 307 assertEquals("wid " + i + " pass " + j, keep1.length(), s1.length()); 308 309 // Does the output string actually fit in the space? 310 assertTrue("wid " + i + " pass " + j, p.measureText(out1) <= i); 311 312 // Is the padded output the same width as trimmed output? 313 assertTrue("wid " + i + " pass " + j, p.measureText(keep1) == p.measureText(out1)); 314 } 315 } 316 } 317 318 @SmallTest 319 public void testDelimitedStringContains() { 320 assertFalse(TextUtils.delimitedStringContains("", ',', null)); 321 assertFalse(TextUtils.delimitedStringContains(null, ',', "")); 322 // Whole match 323 assertTrue(TextUtils.delimitedStringContains("gps", ',', "gps")); 324 // At beginning. 325 assertTrue(TextUtils.delimitedStringContains("gps,gpsx,network,mock", ',', "gps")); 326 assertTrue(TextUtils.delimitedStringContains("gps,network,mock", ',', "gps")); 327 // In middle, both without, before & after a false match. 328 assertTrue(TextUtils.delimitedStringContains("network,gps,mock", ',', "gps")); 329 assertTrue(TextUtils.delimitedStringContains("network,gps,gpsx,mock", ',', "gps")); 330 assertTrue(TextUtils.delimitedStringContains("network,gpsx,gps,mock", ',', "gps")); 331 // At the end. 332 assertTrue(TextUtils.delimitedStringContains("network,mock,gps", ',', "gps")); 333 assertTrue(TextUtils.delimitedStringContains("network,mock,gpsx,gps", ',', "gps")); 334 // Not present (but with a false match) 335 assertFalse(TextUtils.delimitedStringContains("network,mock,gpsx", ',', "gps")); 336 } 337 338 /** 339 * CharSequence wrapper for testing the cases where text is copied into 340 * a char array instead of working from a String or a Spanned. 341 */ 342 private static class Wrapper implements CharSequence { 343 private CharSequence mString; 344 345 public Wrapper(CharSequence s) { 346 mString = s; 347 } 348 349 public int length() { 350 return mString.length(); 351 } 352 353 public char charAt(int off) { 354 return mString.charAt(off); 355 } 356 357 public String toString() { 358 return mString.toString(); 359 } 360 361 public CharSequence subSequence(int start, int end) { 362 return new Wrapper(mString.subSequence(start, end)); 363 } 364 } 365 } 366