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