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