LinkifyCompatTest.java revision e2104f4b5c8e3ad63570306a25e61502dfe4c418
1/*
2 * Copyright (C) 2016 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.support.v4.text.util;
18
19import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertFalse;
21import static org.junit.Assert.assertNotEquals;
22import static org.junit.Assert.assertTrue;
23import static org.junit.Assert.fail;
24
25import android.support.test.filters.SmallTest;
26import android.support.test.runner.AndroidJUnit4;
27import android.support.v4.util.PatternsCompat;
28import android.text.Spannable;
29import android.text.SpannableString;
30import android.text.style.URLSpan;
31import android.text.util.Linkify;
32import android.text.util.Linkify.MatchFilter;
33import android.text.util.Linkify.TransformFilter;
34
35import org.junit.Test;
36import org.junit.runner.RunWith;
37
38import java.util.Locale;
39import java.util.regex.Matcher;
40import java.util.regex.Pattern;
41
42/**
43 * Test {@link LinkifyCompat}.
44 */
45@SmallTest
46@RunWith(AndroidJUnit4.class)
47public class LinkifyCompatTest {
48    private static final Pattern LINKIFY_TEST_PATTERN = Pattern.compile(
49            "(test:)?[a-zA-Z0-9]+(\\.pattern)?");
50
51    private MatchFilter mMatchFilterStartWithDot = new MatchFilter() {
52        @Override
53        public final boolean acceptMatch(final CharSequence s, final int start, final int end) {
54            if (start == 0) {
55                return true;
56            }
57
58            if (s.charAt(start - 1) == '.') {
59                return false;
60            }
61
62            return true;
63        }
64    };
65
66    private TransformFilter mTransformFilterUpperChar = new TransformFilter() {
67        @Override
68        public final String transformUrl(final Matcher match, String url) {
69            StringBuilder buffer = new StringBuilder();
70            String matchingRegion = match.group();
71
72            for (int i = 0, size = matchingRegion.length(); i < size; i++) {
73                char character = matchingRegion.charAt(i);
74
75                if (character == '.' || Character.isLowerCase(character)
76                        || Character.isDigit(character)) {
77                    buffer.append(character);
78                }
79            }
80            return buffer.toString();
81        }
82    };
83
84    @Test
85    public void testAddLinksToSpannable() {
86        // Verify URLs including the ones that have new gTLDs, and the
87        // ones that look like gTLDs (and so are accepted by linkify)
88        // and the ones that should not be linkified due to non-compliant
89        // gTLDs
90        final String longGTLD =
91                "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabc";
92        SpannableString spannable = new SpannableString("name@gmail.com, "
93                + "www.google.com, http://www.google.com/language_tools?hl=en, "
94                + "a.bd, "   // a URL with accepted TLD so should be linkified
95                + "d.e, f.1, g.12, "  // not valid, so should not be linkified
96                + "http://h." + longGTLD + " "  // valid, should be linkified
97                + "j." + longGTLD + "a"); // not a valid URL (gtld too long), no linkify
98
99        assertTrue(LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS));
100        URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
101        assertEquals(4, spans.length);
102        assertEquals("http://www.google.com", spans[0].getURL());
103        assertEquals("http://www.google.com/language_tools?hl=en", spans[1].getURL());
104        assertEquals("http://a.bd", spans[2].getURL());
105        assertEquals("http://h." + longGTLD, spans[3].getURL());
106
107        assertTrue(LinkifyCompat.addLinks(spannable, Linkify.EMAIL_ADDRESSES));
108        spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
109        assertEquals(1, spans.length);
110        assertEquals("mailto:name@gmail.com", spans[0].getURL());
111
112        try {
113            LinkifyCompat.addLinks((Spannable) null, Linkify.WEB_URLS);
114            fail("Should throw NullPointerException!");
115        } catch (NullPointerException e) {
116            // expect
117        }
118
119        assertFalse(LinkifyCompat.addLinks((Spannable) null, 0));
120    }
121
122    @Test
123    public void testAddLinksToSpannableWithScheme() {
124        String text = "google.pattern, test:AZ0101.pattern";
125
126        SpannableString spannable = new SpannableString(text);
127        LinkifyCompat.addLinks(spannable, LINKIFY_TEST_PATTERN, "Test:");
128        URLSpan[] spans = (spannable.getSpans(0, spannable.length(), URLSpan.class));
129        assertEquals(2, spans.length);
130        assertEquals("test:google.pattern", spans[0].getURL());
131        assertEquals("test:AZ0101.pattern", spans[1].getURL());
132
133        try {
134            LinkifyCompat.addLinks((Spannable) null, LINKIFY_TEST_PATTERN, "Test:");
135            fail("Should throw NullPointerException!");
136        } catch (NullPointerException e) {
137        }
138
139        try {
140            LinkifyCompat.addLinks(spannable, null, "Test:");
141            fail("Should throw NullPointerException!");
142        } catch (NullPointerException e) {
143        }
144
145        spannable = new SpannableString(text);
146        LinkifyCompat.addLinks(spannable, LINKIFY_TEST_PATTERN, null);
147        spans = (spannable.getSpans(0, spannable.length(), URLSpan.class));
148        assertEquals(2, spans.length);
149        assertEquals("google.pattern", spans[0].getURL());
150        assertEquals("test:AZ0101.pattern", spans[1].getURL());
151    }
152
153    @Test
154    public void testAddLinks3() {
155        String text = "FilterUpperCase.pattern, 12.345.pattern";
156
157        SpannableString spannable = new SpannableString(text);
158        LinkifyCompat.addLinks(spannable, LINKIFY_TEST_PATTERN, "Test:",
159                mMatchFilterStartWithDot, mTransformFilterUpperChar);
160        URLSpan[] spans = (spannable.getSpans(0, spannable.length(), URLSpan.class));
161        assertEquals(2, spans.length);
162        assertEquals("test:ilterpperase.pattern", spans[0].getURL());
163        assertEquals("test:12", spans[1].getURL());
164
165        try {
166            LinkifyCompat.addLinks((Spannable)null, LINKIFY_TEST_PATTERN, "Test:",
167                    mMatchFilterStartWithDot, mTransformFilterUpperChar);
168            fail("Should throw NullPointerException!");
169        } catch (NullPointerException e) {
170            // expect
171        }
172
173        try {
174            LinkifyCompat.addLinks(spannable, null, "Test:", mMatchFilterStartWithDot,
175                    mTransformFilterUpperChar);
176            fail("Should throw NullPointerException!");
177        } catch (NullPointerException e) {
178            // expect
179        }
180
181        spannable = new SpannableString(text);
182        LinkifyCompat.addLinks(spannable, LINKIFY_TEST_PATTERN, null, mMatchFilterStartWithDot,
183                mTransformFilterUpperChar);
184        spans = (spannable.getSpans(0, spannable.length(), URLSpan.class));
185        assertEquals(2, spans.length);
186        assertEquals("ilterpperase.pattern", spans[0].getURL());
187        assertEquals("12", spans[1].getURL());
188
189        spannable = new SpannableString(text);
190        LinkifyCompat.addLinks(spannable, LINKIFY_TEST_PATTERN, "Test:", null, mTransformFilterUpperChar);
191        spans = (spannable.getSpans(0, spannable.length(), URLSpan.class));
192        assertEquals(3, spans.length);
193        assertEquals("test:ilterpperase.pattern", spans[0].getURL());
194        assertEquals("test:12", spans[1].getURL());
195        assertEquals("test:345.pattern", spans[2].getURL());
196
197        spannable = new SpannableString(text);
198        LinkifyCompat.addLinks(spannable, LINKIFY_TEST_PATTERN, "Test:", mMatchFilterStartWithDot, null);
199        spans = (spannable.getSpans(0, spannable.length(), URLSpan.class));
200        assertEquals(2, spans.length);
201        assertEquals("test:FilterUpperCase.pattern", spans[0].getURL());
202        assertEquals("test:12", spans[1].getURL());
203    }
204
205    @Test
206    public void testAddLinksPhoneNumbers() {
207        String numbersInvalid = "123456789 not a phone number";
208        String numbersUKLocal = "tel:(0812)1234560 (0812)1234561";
209        String numbersUSLocal = "tel:(812)1234562 (812)123.4563 "
210                + " tel:(800)5551210 (800)555-1211 555-1212";
211        String numbersIntl = "tel:+4408121234564 +44-0812-123-4565"
212                + " tel:+18005551213 +1-800-555-1214";
213        SpannableString spannable = new SpannableString(
214                numbersInvalid
215                        + " " + numbersUKLocal
216                        + " " + numbersUSLocal
217                        + " " + numbersIntl);
218
219        // phonenumber linkify is locale-dependent
220        if (Locale.US.equals(Locale.getDefault())) {
221            assertTrue(LinkifyCompat.addLinks(spannable, Linkify.PHONE_NUMBERS));
222            URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
223            // We cannot assert the contents of the spans as support library falls back to the
224            // framework libphonenumber which behaves differently for different API levels.
225            assertNotEquals("There should be more than zero phone number spans.", 0, spans.length);
226        }
227
228        try {
229            LinkifyCompat.addLinks((Spannable) null, Linkify.WEB_URLS);
230            fail("Should throw NullPointerException!");
231        } catch (NullPointerException e) {
232            // expect
233        }
234
235        assertFalse(LinkifyCompat.addLinks((Spannable) null, 0));
236    }
237
238    @Test
239    public void testAddLinks_spanOverlapPruning() {
240        SpannableString spannable = new SpannableString("800-555-1211@gmail.com 800-555-1222.com"
241                + " phone800-555-1233");
242
243        // phonenumber linkify is locale-dependent
244        if (Locale.US.equals(Locale.getDefault())) {
245            assertTrue(LinkifyCompat.addLinks(spannable, Linkify.ALL));
246            URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
247            assertEquals(3, spans.length);
248            assertEquals("tel:8005551233", spans[0].getURL());
249            assertEquals("mailto:800-555-1211@gmail.com", spans[1].getURL());
250            assertEquals("http://800-555-1222.com", spans[2].getURL());
251        }
252    }
253
254    @Test
255    public void testAddLinks_addsLinksWhenDefaultSchemeIsNull() {
256        Spannable spannable = new SpannableString("any https://android.com any android.com any");
257        LinkifyCompat.addLinks(spannable, PatternsCompat.AUTOLINK_WEB_URL, null, null, null);
258
259        URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
260        assertEquals("android.com and https://android.com should be linkified", 2, spans.length);
261        assertEquals("https://android.com", spans[0].getURL());
262        assertEquals("android.com", spans[1].getURL());
263    }
264
265    @Test
266    public void testAddLinks_addsLinksWhenSchemesArrayIsNull() {
267        Spannable spannable = new SpannableString("any https://android.com any android.com any");
268        LinkifyCompat.addLinks(spannable, PatternsCompat.AUTOLINK_WEB_URL, "http://", null, null);
269
270        URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
271        assertEquals("android.com and https://android.com should be linkified", 2, spans.length);
272        // expected behavior, passing null schemes array means: prepend defaultScheme to all links.
273        assertEquals("http://https://android.com", spans[0].getURL());
274        assertEquals("http://android.com", spans[1].getURL());
275    }
276
277    @Test
278    public void testAddLinks_prependsDefaultSchemeToBeginingOfLink() {
279        Spannable spannable = new SpannableString("any android.com any");
280        LinkifyCompat.addLinks(spannable, PatternsCompat.AUTOLINK_WEB_URL, "http://",
281                new String[] { "http://", "https://"}, null, null);
282
283        URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
284        assertEquals("android.com should be linkified", 1, spans.length);
285        assertEquals("http://android.com", spans[0].getURL());
286    }
287
288    @Test
289    public void testAddLinks_doesNotPrependSchemeIfSchemeExists() {
290        Spannable spannable = new SpannableString("any https://android.com any");
291        LinkifyCompat.addLinks(spannable, PatternsCompat.AUTOLINK_WEB_URL, "http://",
292                new String[] { "http://", "https://"}, null, null);
293
294        URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
295        assertEquals("android.com should be linkified", 1, spans.length);
296        assertEquals("https://android.com", spans[0].getURL());
297    }
298
299    // WEB_URLS Related Tests
300
301    @Test
302    public void testAddLinks_doesNotAddLinksForUrlWithoutProtocolAndWithoutKnownTld() {
303        Spannable spannable = new SpannableString("hey man.its me");
304        boolean linksAdded = LinkifyCompat.addLinks(spannable, Linkify.ALL);
305        assertFalse("Should not add link with unknown TLD", linksAdded);
306    }
307
308    @Test
309    public void testAddLinks_shouldNotAddEmailAddressAsUrl() {
310        String url = "name@gmail.com";
311        verifyAddLinksWithWebUrlFails("Should not recognize email address as URL", url);
312    }
313
314    @Test
315    public void testAddLinks_acceptsUrlsWithCommasInRequestParameterValues() {
316        String url = "https://android.com/path?ll=37.4221,-122.0836&z=17&pll=37.4221,-122.0836";
317        verifyAddLinksWithWebUrlSucceeds("Should accept commas", url);
318    }
319
320    @Test
321    public void testAddLinks_addsLinksForUrlWithProtocolWithoutTld() {
322        String url = "http://android/#notld///a/n/d/r/o/i/d&p1=1&p2=2";
323        verifyAddLinksWithWebUrlSucceeds("Should accept URL starting with protocol but does not"
324                + " have TLD", url);
325    }
326
327    @Test
328    public void testAddLinks_matchesProtocolCaseInsensitive() {
329        String url = "hTtP://android.com";
330        verifyAddLinksWithWebUrlSucceeds("Protocol matching should be case insensitive", url);
331    }
332
333    @Test
334    public void testAddLinks_matchesValidUrlWithSchemeAndHostname() {
335        String url = "http://www.android.com";
336        verifyAddLinksWithWebUrlSucceeds("Should match valid URL with scheme and hostname", url);
337    }
338
339    @Test
340    public void testAddLinks_matchesValidUrlWithSchemeHostnameAndNewTld() {
341        String url = "http://www.android.me";
342        verifyAddLinksWithWebUrlSucceeds("Should match valid URL with scheme hostname and new TLD",
343                url);
344    }
345
346    @Test
347    public void testAddLinks_matchesValidUrlWithHostnameAndNewTld() {
348        String url = "android.camera";
349        verifyAddLinksWithWebUrlSucceeds("Should match valid URL with hostname and new TLD", url);
350    }
351
352    @Test
353    public void testAddLinks_matchesPunycodeUrl() {
354        String url = "http://xn--fsqu00a.xn--unup4y";
355        verifyAddLinksWithWebUrlSucceeds("Should match Punycode URL", url);
356    }
357
358    @Test
359    public void testAddLinks_matchesPunycodeUrlWithoutProtocol() {
360        String url = "xn--fsqu00a.xn--unup4y";
361        verifyAddLinksWithWebUrlSucceeds("Should match Punycode URL without protocol", url);
362    }
363
364    @Test
365    public void testAddLinks_doesNotMatchPunycodeTldThatStartsWithDash() {
366        String url = "xn--fsqu00a.-xn--unup4y";
367        verifyAddLinksWithWebUrlFails("Should not match Punycode TLD that starts with dash", url);
368    }
369
370    @Test
371    public void testAddLinks_partiallyMatchesPunycodeTldThatEndsWithDash() {
372        String url = "http://xn--fsqu00a.xn--unup4y-";
373        verifyAddLinksWithWebUrlPartiallyMatches("Should partially match Punycode TLD that ends "
374                + "with dash", "http://xn--fsqu00a.xn--unup4y", url);
375    }
376
377    @Test
378    public void testAddLinks_matchesUrlWithUnicodeDomainName() {
379        String url = "http://\uD604\uAE08\uC601\uC218\uC99D.kr";
380        verifyAddLinksWithWebUrlSucceeds("Should match URL with Unicode domain name", url);
381    }
382
383    @Test
384    public void testAddLinks_matchesUrlWithUnicodeDomainNameWithoutProtocol() {
385        String url = "\uD604\uAE08\uC601\uC218\uC99D.kr";
386        verifyAddLinksWithWebUrlSucceeds("Should match URL without protocol and with Unicode "
387                + "domain name", url);
388    }
389
390    @Test
391    public void testAddLinks_matchesUrlWithUnicodeDomainNameAndTld() {
392        String url = "\uB3C4\uBA54\uC778.\uD55C\uAD6D";
393        verifyAddLinksWithWebUrlSucceeds("Should match URL with Unicode domain name and TLD", url);
394    }
395
396    @Test
397    public void testAddLinks_matchesUrlWithUnicodePath() {
398        String url = "http://android.com/\u2019/a";
399        verifyAddLinksWithWebUrlSucceeds("Should match URL with Unicode path", url);
400    }
401
402    @Test
403    public void testAddLinks_matchesValidUrlWithPort() {
404        String url = "http://www.example.com:8080";
405        verifyAddLinksWithWebUrlSucceeds("Should match URL with port", url);
406    }
407
408    @Test
409    public void testAddLinks_matchesUrlWithPortAndQuery() {
410        String url = "http://www.example.com:8080/?foo=bar";
411        verifyAddLinksWithWebUrlSucceeds("Should match URL with port and query", url);
412    }
413
414    @Test
415    public void testAddLinks_matchesUrlWithTilde() {
416        String url = "http://www.example.com:8080/~user/?foo=bar";
417        verifyAddLinksWithWebUrlSucceeds("Should match URL with tilde", url);
418    }
419
420    @Test
421    public void testAddLinks_matchesUrlStartingWithHttpAndDoesNotHaveTld() {
422        String url = "http://android/#notld///a/n/d/r/o/i/d&p1=1&p2=2";
423        verifyAddLinksWithWebUrlSucceeds("Should match URL without a TLD and starting with http",
424                url);
425    }
426
427    @Test
428    public void testAddLinks_doesNotMatchUrlsWithoutProtocolAndWithUnknownTld() {
429        String url = "thank.you";
430        verifyAddLinksWithWebUrlFails("Should not match URL that does not start with a protocol "
431                + "and does not contain a known TLD", url);
432    }
433
434    @Test
435    public void testAddLinks_matchesValidUrlWithEmoji() {
436        String url = "Thank\u263A.com";
437        verifyAddLinksWithWebUrlSucceeds("Should match URL with emoji", url);
438    }
439
440    @Test
441    public void testAddLinks_doesNotMatchUrlsWithEmojiWithoutProtocolAndWithoutKnownTld() {
442        String url = "Thank\u263A.you";
443        verifyAddLinksWithWebUrlFails("Should not match URLs containing emoji and with unknown "
444                + "TLD", url);
445    }
446
447    @Test
448    public void testAddLinks_matchesDomainNameWithSurrogatePairs() {
449        String url = "android\uD83C\uDF38.com";
450        verifyAddLinksWithWebUrlSucceeds("Should match domain name with Unicode surrogate pairs",
451                url);
452    }
453
454    @Test
455    public void testAddLinks_matchesTldWithSurrogatePairs() {
456        String url = "http://android.\uD83C\uDF38com";
457        verifyAddLinksWithWebUrlSucceeds("Should match TLD with Unicode surrogate pairs", url);
458    }
459
460    @Test
461    public void testAddLinks_doesNotMatchUrlWithExcludedSurrogate() {
462        String url = "android\uD83F\uDFFE.com";
463        verifyAddLinksWithWebUrlFails("Should not match URL with excluded Unicode surrogate"
464                + " pair",  url);
465    }
466
467    @Test
468    public void testAddLinks_matchesPathWithSurrogatePairs() {
469        String url = "http://android.com/path-with-\uD83C\uDF38?v=\uD83C\uDF38f";
470        verifyAddLinksWithWebUrlSucceeds("Should match path and query with Unicode surrogate pairs",
471                url);
472    }
473
474    @Test
475    public void testAddLinks__doesNotMatchUnicodeSpaces() {
476        String part1 = "http://and";
477        String part2 = "roid.com";
478        String[] emptySpaces = new String[]{
479                "\u00A0", // no-break space
480                "\u2000", // en quad
481                "\u2001", // em quad
482                "\u2002", // en space
483                "\u2003", // em space
484                "\u2004", // three-per-em space
485                "\u2005", // four-per-em space
486                "\u2006", // six-per-em space
487                "\u2007", // figure space
488                "\u2008", // punctuation space
489                "\u2009", // thin space
490                "\u200A", // hair space
491                "\u2028", // line separator
492                "\u2029", // paragraph separator
493                "\u202F", // narrow no-break space
494                "\u3000"  // ideographic space
495        };
496
497        for (String emptySpace : emptySpaces) {
498            String url = part1 + emptySpace + part2;
499            verifyAddLinksWithWebUrlPartiallyMatches("Should not include empty space with code: "
500                    + emptySpace.codePointAt(0), part1, url);
501        }
502    }
503
504    @Test
505    public void testAddLinks_matchesDomainNameWithDash() {
506        String url = "http://a-nd.r-oid.com";
507        verifyAddLinksWithWebUrlSucceeds("Should match domain name with '-'", url);
508
509        url = "a-nd.r-oid.com";
510        verifyAddLinksWithWebUrlSucceeds("Should match domain name with '-'", url);
511    }
512
513    @Test
514    public void testAddLinks_matchesDomainNameWithUnderscore() {
515        String url = "http://a_nd.r_oid.com";
516        verifyAddLinksWithWebUrlSucceeds("Should match domain name with '_'", url);
517
518        url = "a_nd.r_oid.com";
519        verifyAddLinksWithWebUrlSucceeds("Should match domain name with '_'", url);
520    }
521
522    @Test
523    public void testAddLinks_matchesPathAndQueryWithDollarSign() {
524        String url = "http://android.com/path$?v=$val";
525        verifyAddLinksWithWebUrlSucceeds("Should match path and query with '$'", url);
526
527        url = "android.com/path$?v=$val";
528        verifyAddLinksWithWebUrlSucceeds("Should match path and query with '$'", url);
529    }
530
531    @Test
532    public void testAddLinks_matchesEmptyPathWithQueryParams() {
533        String url = "http://android.com?q=v";
534        verifyAddLinksWithWebUrlSucceeds("Should match empty path with query params", url);
535
536        url = "android.com?q=v";
537        verifyAddLinksWithWebUrlSucceeds("Should match empty path with query params", url);
538
539        url = "http://android.com/?q=v";
540        verifyAddLinksWithWebUrlSucceeds("Should match empty path with query params", url);
541
542        url = "android.com/?q=v";
543        verifyAddLinksWithWebUrlSucceeds("Should match empty path with query params", url);
544    }
545
546    // EMAIL_ADDRESSES Related Tests
547
548    @Test
549    public void testAddLinks_email_matchesShortValidEmail() {
550        String email = "a@a.co";
551        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
552    }
553
554    @Test
555    public void testAddLinks_email_matchesRegularEmail() {
556        String email = "email@android.com";
557        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
558    }
559
560    @Test
561    public void testAddLinks_email_matchesEmailWithMultipleSubdomains() {
562        String email = "email@e.somelongdomainnameforandroid.abc.uk";
563        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
564    }
565
566    @Test
567    public void testAddLinks_email_matchesLocalPartWithDot() {
568        String email = "e.mail@android.com";
569        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
570    }
571
572    @Test
573    public void testAddLinks_email_matchesLocalPartWithPlus() {
574        String email = "e+mail@android.com";
575        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
576    }
577
578    @Test
579    public void testAddLinks_email_matchesLocalPartWithUnderscore() {
580        String email = "e_mail@android.com";
581        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
582    }
583
584    @Test
585    public void testAddLinks_email_matchesLocalPartWithDash() {
586        String email = "e-mail@android.com";
587        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
588    }
589
590    @Test
591    public void testAddLinks_email_matchesLocalPartWithApostrophe() {
592        String email = "e'mail@android.com";
593        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
594    }
595
596    @Test
597    public void testAddLinks_email_matchesLocalPartWithDigits() {
598        String email = "123@android.com";
599        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
600    }
601
602    @Test
603    public void testAddLinks_email_matchesUnicodeLocalPart() {
604        String email = "\uD604\uAE08\uC601\uC218\uC99D@android.kr";
605        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
606    }
607
608    @Test
609    public void testAddLinks_email_matchesLocalPartWithEmoji() {
610        String email = "smiley\u263A@android.com";
611        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
612    }
613
614    @Test
615    public void testAddLinks_email_matchesLocalPartWithSurrogatePairs() {
616        String email = "a\uD83C\uDF38a@android.com";
617        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
618    }
619
620    @Test
621    public void testAddLinks_email_matchesDomainWithDash() {
622        String email = "email@an-droid.com";
623        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
624    }
625
626    @Test
627    public void testAddLinks_email_matchesUnicodeDomain() {
628        String email = "email@\uD604\uAE08\uC601\uC218\uC99D.kr";
629        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
630    }
631
632    @Test
633    public void testAddLinks_email_matchesUnicodeLocalPartAndDomain() {
634        String email = "\uD604\uAE08\uC601\uC218\uC99D@\uD604\uAE08\uC601\uC218\uC99D.kr";
635        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
636    }
637
638    @Test
639    public void testAddLinks_email_matchesDomainWithEmoji() {
640        String email = "smiley@\u263Aandroid.com";
641        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
642    }
643
644    @Test
645    public void testAddLinks_email_matchesDomainWithSurrogatePairs() {
646        String email = "email@\uD83C\uDF38android.com";
647        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
648    }
649
650    @Test
651    public void testAddLinks_email_matchesLocalPartAndDomainWithSurrogatePairs() {
652        String email = "a\uD83C\uDF38a@\uD83C\uDF38android.com";
653        verifyAddLinksWithEmailSucceeds("Should match email: " + email, email);
654    }
655
656    @Test
657    public void testAddLinks_partiallyMatchesEmailEndingWithDot() {
658        String email = "email@android.co.uk.";
659        verifyAddLinksWithEmailPartiallyMatches("Should partially match email ending with dot",
660                "mailto:email@android.co.uk", email);
661    }
662
663    @Test
664    public void testAddLinks_email_partiallyMatchesLocalPartStartingWithDot() {
665        String email = ".email@android.com";
666        verifyAddLinksWithEmailPartiallyMatches("Should partially match email starting "
667                + "with dot", "mailto:email@android.com", email);
668    }
669
670    @Test
671    public void testAddLinks_email_doesNotMatchStringWithoutAtSign() {
672        String email = "android.com";
673        verifyAddLinksWithEmailFails("Should not match email: " + email, email);
674    }
675
676    @Test
677    public void testAddLinks_email_doesNotMatchPlainString() {
678        String email = "email";
679        verifyAddLinksWithEmailFails("Should not match email: " + email, email);
680    }
681
682    @Test
683    public void testAddLinks_email_doesNotMatchEmailWithoutTld() {
684        String email = "email@android";
685        verifyAddLinksWithEmailFails("Should not match email: " + email, email);
686    }
687
688    @Test
689    public void testAddLinks_email_doesNotMatchLocalPartEndingWithDot() {
690        String email = "email.@android.com";
691        verifyAddLinksWithEmailFails("Should not match email: " + email, email);
692    }
693
694    @Test
695    public void testAddLinks_email_doesNotMatchDomainStartingWithDash() {
696        String email = "email@-android.com";
697        verifyAddLinksWithEmailFails("Should not match email: " + email, email);
698    }
699
700    @Test
701    public void testAddLinks_email_doesNotMatchDomainWithConsecutiveDots() {
702        String email = "email@android..com";
703        verifyAddLinksWithEmailFails("Should not match email: " + email, email);
704    }
705
706    @Test
707    public void testAddLinks_email_doesNotMatchEmailWithIp() {
708        String email = "email@127.0.0.1";
709        verifyAddLinksWithEmailFails("Should not match email: " + email, email);
710    }
711
712    @Test
713    public void testAddLinks_email_doesNotMatchEmailWithInvalidTld() {
714        String email = "email@android.c";
715        verifyAddLinksWithEmailFails("Should not match email: " + email, email);
716    }
717
718    @Test
719    public void testAddLinks_email_matchesLocalPartUpTo64Chars() {
720        String localPart = "";
721        for (int i = 0; i < 64; i++) {
722            localPart += "a";
723        }
724        String email = localPart + "@android.com";
725        verifyAddLinksWithEmailSucceeds("Should match email local part of length: "
726                + localPart.length(), email);
727
728        email = localPart + "a@android.com";
729        verifyAddLinksWithEmailFails("Should not match email local part of length:"
730                + localPart.length(), email);
731    }
732
733    @Test
734    public void testAddLinks_email_matchesSubdomainUpTo63Chars() {
735        String subdomain = "";
736        for (int i = 0; i < 63; i++) {
737            subdomain += "a";
738        }
739        String email = "email@" + subdomain + ".com";
740
741        verifyAddLinksWithEmailSucceeds("Should match email subdomain of length: "
742                + subdomain.length(), email);
743
744        subdomain += "a";
745        email = "email@" + subdomain + ".com";
746
747        verifyAddLinksWithEmailFails("Should not match email subdomain of length:"
748                + subdomain.length(), email);
749    }
750
751    @Test
752    public void testAddLinks_email_matchesDomainUpTo255Chars() {
753        String domain = "";
754        while (domain.length() <= 250) {
755            domain += "d.";
756        }
757        domain += "com";
758        assertEquals(255, domain.length());
759        String email = "a@" + domain;
760        verifyAddLinksWithEmailSucceeds("Should match email domain of length: "
761                + domain.length(), email);
762
763        email = email + "m";
764        verifyAddLinksWithEmailFails("Should not match email domain of length:"
765                + domain.length(), email);
766    }
767
768    // Utility functions
769    private static void verifyAddLinksWithWebUrlSucceeds(String msg, String url) {
770        verifyAddLinksSucceeds(msg, url, Linkify.WEB_URLS);
771    }
772
773    private static void verifyAddLinksWithWebUrlFails(String msg, String url) {
774        verifyAddLinksFails(msg, url, Linkify.WEB_URLS);
775    }
776
777    private static void verifyAddLinksWithWebUrlPartiallyMatches(String msg, String expected,
778            String url) {
779        verifyAddLinksPartiallyMatches(msg, expected, url, Linkify.WEB_URLS);
780    }
781
782    private static void verifyAddLinksWithEmailSucceeds(String msg, String url) {
783        verifyAddLinksSucceeds(msg, url, Linkify.EMAIL_ADDRESSES);
784    }
785
786    private static void verifyAddLinksWithEmailFails(String msg, String url) {
787        verifyAddLinksFails(msg, url, Linkify.EMAIL_ADDRESSES);
788    }
789
790    private static void verifyAddLinksWithEmailPartiallyMatches(String msg, String expected,
791            String url) {
792        verifyAddLinksPartiallyMatches(msg, expected, url, Linkify.EMAIL_ADDRESSES);
793    }
794
795    private static void verifyAddLinksSucceeds(String msg, String string, int type) {
796        String str = "start " + string + " end";
797        Spannable spannable = new SpannableString(str);
798
799        boolean linksAdded = LinkifyCompat.addLinks(spannable, type);
800        URLSpan[] spans = spannable.getSpans(0, str.length(), URLSpan.class);
801
802        assertTrue(msg, linksAdded);
803        assertEquals("Span should start from the beginning of: " + string,
804                "start ".length(), spannable.getSpanStart(spans[0]));
805        assertEquals("Span should end at the end of: " + string,
806                str.length() - " end".length(), spannable.getSpanEnd(spans[0]));
807    }
808
809    private static void verifyAddLinksFails(String msg, String string, int type) {
810        Spannable spannable = new SpannableString("start " + string + " end");
811        boolean linksAdded = LinkifyCompat.addLinks(spannable, type);
812        assertFalse(msg, linksAdded);
813    }
814
815    private static void verifyAddLinksPartiallyMatches(String msg, String expected,
816            String string, int type) {
817        Spannable spannable = new SpannableString("start " + string + " end");
818        boolean linksAdded = LinkifyCompat.addLinks(spannable, type);
819        URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
820        assertTrue(msg, linksAdded);
821        assertEquals(msg, expected, spans[0].getURL().toString());
822    }
823}
824