/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.text; import com.google.android.collect.Lists; import android.test.MoreAsserts; import android.os.Parcel; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.SmallTest; import android.text.style.StyleSpan; import android.text.util.Rfc822Token; import android.text.util.Rfc822Tokenizer; import android.view.View; import java.util.ArrayList; import java.util.List; import java.util.Locale; import junit.framework.TestCase; /** * TextUtilsTest tests {@link TextUtils}. */ public class TextUtilsTest extends TestCase { @SmallTest public void testBasic() throws Exception { assertEquals("", TextUtils.concat()); assertEquals("foo", TextUtils.concat("foo")); assertEquals("foobar", TextUtils.concat("foo", "bar")); assertEquals("foobarbaz", TextUtils.concat("foo", "bar", "baz")); SpannableString foo = new SpannableString("foo"); foo.setSpan("foo", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); SpannableString bar = new SpannableString("bar"); bar.setSpan("bar", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); SpannableString baz = new SpannableString("baz"); baz.setSpan("baz", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); assertEquals("foo", TextUtils.concat(foo).toString()); assertEquals("foobar", TextUtils.concat(foo, bar).toString()); assertEquals("foobarbaz", TextUtils.concat(foo, bar, baz).toString()); assertEquals(1, ((Spanned) TextUtils.concat(foo)).getSpanStart("foo")); assertEquals(1, ((Spanned) TextUtils.concat(foo, bar)).getSpanStart("foo")); assertEquals(4, ((Spanned) TextUtils.concat(foo, bar)).getSpanStart("bar")); assertEquals(1, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("foo")); assertEquals(4, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("bar")); assertEquals(7, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("baz")); assertTrue(TextUtils.concat("foo", "bar") instanceof String); assertTrue(TextUtils.concat(foo, bar) instanceof SpannedString); } @SmallTest public void testTemplateString() throws Exception { CharSequence result; result = TextUtils.expandTemplate("This is a ^1 of the ^2 broadcast ^3.", "test", "emergency", "system"); assertEquals("This is a test of the emergency broadcast system.", result.toString()); result = TextUtils.expandTemplate("^^^1^^^2^3^a^1^^b^^^c", "one", "two", "three"); assertEquals("^one^twothree^aone^b^^c", result.toString()); result = TextUtils.expandTemplate("^"); assertEquals("^", result.toString()); result = TextUtils.expandTemplate("^^"); assertEquals("^", result.toString()); result = TextUtils.expandTemplate("^^^"); assertEquals("^^", result.toString()); result = TextUtils.expandTemplate("shorter ^1 values ^2.", "a", ""); assertEquals("shorter a values .", result.toString()); try { TextUtils.expandTemplate("Only ^1 value given, but ^2 used.", "foo"); fail(); } catch (IllegalArgumentException e) { } try { TextUtils.expandTemplate("^1 value given, and ^0 used.", "foo"); fail(); } catch (IllegalArgumentException e) { } result = TextUtils.expandTemplate("^1 value given, and ^9 used.", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"); assertEquals("one value given, and nine used.", result.toString()); try { TextUtils.expandTemplate("^1 value given, and ^10 used.", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"); fail(); } catch (IllegalArgumentException e) { } // putting carets in the values: expansion is not recursive. result = TextUtils.expandTemplate("^2", "foo", "^^"); assertEquals("^^", result.toString()); result = TextUtils.expandTemplate("^^2", "foo", "1"); assertEquals("^2", result.toString()); result = TextUtils.expandTemplate("^1", "value with ^2 in it", "foo"); assertEquals("value with ^2 in it", result.toString()); } /** Fail unless text+spans contains a span 'spanName' with the given start and end. */ private void checkContains(Spanned text, String[] spans, String spanName, int start, int end) throws Exception { for (String i: spans) { if (i.equals(spanName)) { assertEquals(start, text.getSpanStart(i)); assertEquals(end, text.getSpanEnd(i)); return; } } fail(); } @SmallTest public void testTemplateSpan() throws Exception { SpannableString template; Spanned result; String[] spans; // ordinary replacement template = new SpannableString("a^1b"); template.setSpan("before", 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); template.setSpan("during", 1, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); template.setSpan("after", 3, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); template.setSpan("during+after", 1, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); result = (Spanned) TextUtils.expandTemplate(template, "foo"); assertEquals(5, result.length()); spans = result.getSpans(0, result.length(), String.class); // value is one character longer, so span endpoints should change. assertEquals(4, spans.length); checkContains(result, spans, "before", 0, 1); checkContains(result, spans, "during", 1, 4); checkContains(result, spans, "after", 4, 5); checkContains(result, spans, "during+after", 1, 5); // replacement with empty string result = (Spanned) TextUtils.expandTemplate(template, ""); assertEquals(2, result.length()); spans = result.getSpans(0, result.length(), String.class); // the "during" span should disappear. assertEquals(3, spans.length); checkContains(result, spans, "before", 0, 1); checkContains(result, spans, "after", 1, 2); checkContains(result, spans, "during+after", 1, 2); } @SmallTest public void testStringSplitterSimple() { stringSplitterTestHelper("a,b,cde", new String[] {"a", "b", "cde"}); } @SmallTest public void testStringSplitterEmpty() { stringSplitterTestHelper("", new String[] {}); } @SmallTest public void testStringSplitterWithLeadingEmptyString() { stringSplitterTestHelper(",a,b,cde", new String[] {"", "a", "b", "cde"}); } @SmallTest public void testStringSplitterWithInternalEmptyString() { stringSplitterTestHelper("a,b,,cde", new String[] {"a", "b", "", "cde"}); } @SmallTest public void testStringSplitterWithTrailingEmptyString() { // A single trailing emtpy string should be ignored. stringSplitterTestHelper("a,b,cde,", new String[] {"a", "b", "cde"}); } private void stringSplitterTestHelper(String string, String[] expectedStrings) { TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(','); splitter.setString(string); List strings = Lists.newArrayList(); for (String s : splitter) { strings.add(s); } MoreAsserts.assertEquals(expectedStrings, strings.toArray(new String[]{})); } @SmallTest public void testTrim() { String[] strings = { "abc", " abc", " abc", "abc ", "abc ", " abc ", " abc ", "\nabc\n", "\nabc", "abc\n" }; for (String s : strings) { assertEquals(s.trim().length(), TextUtils.getTrimmedLength(s)); } } @SmallTest public void testRfc822TokenizerFullAddress() { Rfc822Token[] tokens = Rfc822Tokenizer.tokenize("Foo Bar (something) "); assertNotNull(tokens); assertEquals(1, tokens.length); assertEquals("foo@google.com", tokens[0].getAddress()); assertEquals("Foo Bar", tokens[0].getName()); assertEquals("something",tokens[0].getComment()); } @SmallTest public void testRfc822TokenizeItemWithError() { Rfc822Token[] tokens = Rfc822Tokenizer.tokenize("\"Foo Bar\\"); assertNotNull(tokens); assertEquals(1, tokens.length); assertEquals("Foo Bar", tokens[0].getAddress()); } @SmallTest public void testRfc822FindToken() { Rfc822Tokenizer tokenizer = new Rfc822Tokenizer(); // 0 1 2 3 4 // 0 1234 56789012345678901234 5678 90123456789012345 String address = "\"Foo\" , \"Bar\" "; assertEquals(0, tokenizer.findTokenStart(address, 21)); assertEquals(22, tokenizer.findTokenEnd(address, 21)); assertEquals(24, tokenizer.findTokenStart(address, 25)); assertEquals(46, tokenizer.findTokenEnd(address, 25)); } @SmallTest public void testRfc822FindTokenWithError() { assertEquals(9, new Rfc822Tokenizer().findTokenEnd("\"Foo Bar\\", 0)); } @LargeTest public void testEllipsize() { CharSequence s1 = "The quick brown fox jumps over \u00FEhe lazy dog."; CharSequence s2 = new Wrapper(s1); Spannable s3 = new SpannableString(s1); s3.setSpan(new StyleSpan(0), 5, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); TextPaint p = new TextPaint(); p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG); for (int i = 0; i < 100; i++) { for (int j = 0; j < 3; j++) { TextUtils.TruncateAt kind = null; switch (j) { case 0: kind = TextUtils.TruncateAt.START; break; case 1: kind = TextUtils.TruncateAt.END; break; case 2: kind = TextUtils.TruncateAt.MIDDLE; break; } String out1 = TextUtils.ellipsize(s1, p, i, kind).toString(); String out2 = TextUtils.ellipsize(s2, p, i, kind).toString(); String out3 = TextUtils.ellipsize(s3, p, i, kind).toString(); String keep1 = TextUtils.ellipsize(s1, p, i, kind, true, null).toString(); String keep2 = TextUtils.ellipsize(s2, p, i, kind, true, null).toString(); String keep3 = TextUtils.ellipsize(s3, p, i, kind, true, null).toString(); String trim1 = keep1.replace("\uFEFF", ""); // Are all normal output strings identical? assertEquals("wid " + i + " pass " + j, out1, out2); assertEquals("wid " + i + " pass " + j, out2, out3); // Are preserved output strings identical? assertEquals("wid " + i + " pass " + j, keep1, keep2); assertEquals("wid " + i + " pass " + j, keep2, keep3); // Does trimming padding from preserved yield normal? assertEquals("wid " + i + " pass " + j, out1, trim1); // Did preserved output strings preserve length? assertEquals("wid " + i + " pass " + j, keep1.length(), s1.length()); // Does the output string actually fit in the space? assertTrue("wid " + i + " pass " + j, p.measureText(out1) <= i); // Is the padded output the same width as trimmed output? assertTrue("wid " + i + " pass " + j, p.measureText(keep1) == p.measureText(out1)); } } } @SmallTest public void testDelimitedStringContains() { assertFalse(TextUtils.delimitedStringContains("", ',', null)); assertFalse(TextUtils.delimitedStringContains(null, ',', "")); // Whole match assertTrue(TextUtils.delimitedStringContains("gps", ',', "gps")); // At beginning. assertTrue(TextUtils.delimitedStringContains("gps,gpsx,network,mock", ',', "gps")); assertTrue(TextUtils.delimitedStringContains("gps,network,mock", ',', "gps")); // In middle, both without, before & after a false match. assertTrue(TextUtils.delimitedStringContains("network,gps,mock", ',', "gps")); assertTrue(TextUtils.delimitedStringContains("network,gps,gpsx,mock", ',', "gps")); assertTrue(TextUtils.delimitedStringContains("network,gpsx,gps,mock", ',', "gps")); // At the end. assertTrue(TextUtils.delimitedStringContains("network,mock,gps", ',', "gps")); assertTrue(TextUtils.delimitedStringContains("network,mock,gpsx,gps", ',', "gps")); // Not present (but with a false match) assertFalse(TextUtils.delimitedStringContains("network,mock,gpsx", ',', "gps")); } @SmallTest public void testCharSequenceCreator() { Parcel p = Parcel.obtain(); TextUtils.writeToParcel(null, p, 0); CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); assertNull("null CharSequence should generate null from parcel", text); p = Parcel.obtain(); TextUtils.writeToParcel("test", p, 0); p.setDataPosition(0); text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); assertEquals("conversion to/from parcel failed", "test", text); } @SmallTest public void testCharSequenceCreatorNull() { Parcel p; CharSequence text; p = Parcel.obtain(); TextUtils.writeToParcel(null, p, 0); p.setDataPosition(0); text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); assertNull("null CharSequence should generate null from parcel", text); } @SmallTest public void testCharSequenceCreatorSpannable() { Parcel p; CharSequence text; p = Parcel.obtain(); TextUtils.writeToParcel(new SpannableString("test"), p, 0); p.setDataPosition(0); text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); assertEquals("conversion to/from parcel failed", "test", text.toString()); } @SmallTest public void testCharSequenceCreatorString() { Parcel p; CharSequence text; p = Parcel.obtain(); TextUtils.writeToParcel("test", p, 0); p.setDataPosition(0); text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); assertEquals("conversion to/from parcel failed", "test", text.toString()); } /** * CharSequence wrapper for testing the cases where text is copied into * a char array instead of working from a String or a Spanned. */ private static class Wrapper implements CharSequence { private CharSequence mString; public Wrapper(CharSequence s) { mString = s; } public int length() { return mString.length(); } public char charAt(int off) { return mString.charAt(off); } @Override public String toString() { return mString.toString(); } public CharSequence subSequence(int start, int end) { return new Wrapper(mString.subSequence(start, end)); } } @LargeTest public void testRemoveEmptySpans() { MockSpanned spanned = new MockSpanned(); spanned.test(); spanned.addSpan().test(); spanned.addSpan().test(); spanned.addSpan().test(); spanned.addEmptySpan().test(); spanned.addSpan().test(); spanned.addEmptySpan().test(); spanned.addEmptySpan().test(); spanned.addSpan().test(); spanned.clear(); spanned.addEmptySpan().test(); spanned.addEmptySpan().test(); spanned.addEmptySpan().test(); spanned.addSpan().test(); spanned.addEmptySpan().test(); spanned.addSpan().test(); spanned.clear(); spanned.addSpan().test(); spanned.addEmptySpan().test(); spanned.addSpan().test(); spanned.addEmptySpan().test(); spanned.addSpan().test(); spanned.addSpan().test(); } protected static class MockSpanned implements Spanned { private List allSpans = new ArrayList(); private List nonEmptySpans = new ArrayList(); public void clear() { allSpans.clear(); nonEmptySpans.clear(); } public MockSpanned addSpan() { Object o = new Object(); allSpans.add(o); nonEmptySpans.add(o); return this; } public MockSpanned addEmptySpan() { Object o = new Object(); allSpans.add(o); return this; } public void test() { Object[] nonEmpty = TextUtils.removeEmptySpans(allSpans.toArray(), this, Object.class); assertEquals("Mismatched array size", nonEmptySpans.size(), nonEmpty.length); for (int i=0; i T[] getSpans(int start, int end, Class type) { return null; } @Override public int getSpanStart(Object tag) { return 0; } @Override public int getSpanEnd(Object tag) { return nonEmptySpans.contains(tag) ? 1 : 0; } @Override public int getSpanFlags(Object tag) { return 0; } @Override public int nextSpanTransition(int start, int limit, Class type) { return 0; } } @SmallTest public void testGetLayoutDirectionFromLocale() { assertEquals(View.LAYOUT_DIRECTION_LTR, TextUtils.getLayoutDirectionFromLocale(null)); assertEquals(View.LAYOUT_DIRECTION_LTR, TextUtils.getLayoutDirectionFromLocale(Locale.ROOT)); assertEquals(View.LAYOUT_DIRECTION_LTR, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("en"))); assertEquals(View.LAYOUT_DIRECTION_LTR, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("en-US"))); assertEquals(View.LAYOUT_DIRECTION_LTR, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az"))); assertEquals(View.LAYOUT_DIRECTION_LTR, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-AZ"))); assertEquals(View.LAYOUT_DIRECTION_LTR, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-Latn"))); assertEquals(View.LAYOUT_DIRECTION_LTR, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("en-EG"))); assertEquals(View.LAYOUT_DIRECTION_LTR, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("ar-Latn"))); assertEquals(View.LAYOUT_DIRECTION_RTL, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("ar"))); assertEquals(View.LAYOUT_DIRECTION_RTL, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("fa"))); assertEquals(View.LAYOUT_DIRECTION_RTL, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("he"))); assertEquals(View.LAYOUT_DIRECTION_RTL, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("iw"))); assertEquals(View.LAYOUT_DIRECTION_RTL, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("ur"))); assertEquals(View.LAYOUT_DIRECTION_RTL, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("dv"))); assertEquals(View.LAYOUT_DIRECTION_RTL, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-Arab"))); assertEquals(View.LAYOUT_DIRECTION_RTL, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-IR"))); assertEquals(View.LAYOUT_DIRECTION_RTL, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("fa-US"))); assertEquals(View.LAYOUT_DIRECTION_RTL, TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("tr-Arab"))); } }