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