1/*
2 * Copyright (C) 2011 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 com.android.ex.chips;
18
19import android.annotation.TargetApi;
20import android.content.ClipData;
21import android.content.Context;
22import android.graphics.Bitmap;
23import android.graphics.drawable.BitmapDrawable;
24import android.graphics.drawable.Drawable;
25import android.net.Uri;
26import android.test.AndroidTestCase;
27import android.test.suitebuilder.annotation.SmallTest;
28import android.text.Editable;
29import android.text.SpannableStringBuilder;
30import android.text.util.Rfc822Tokenizer;
31import android.widget.TextView;
32
33import com.android.ex.chips.recipientchip.DrawableRecipientChip;
34import com.android.ex.chips.recipientchip.ReplacementDrawableSpan;
35import com.android.ex.chips.recipientchip.VisibleRecipientChip;
36
37import java.util.regex.Pattern;
38
39@SmallTest
40public class ChipsTest extends AndroidTestCase {
41    private DrawableRecipientChip[] mMockRecips;
42
43    private RecipientEntry[] mMockEntries;
44
45    private Rfc822Tokenizer mTokenizer;
46
47    private Editable mEditable;
48
49    class BaseMockRecipientEditTextView extends RecipientEditTextView {
50
51        public BaseMockRecipientEditTextView(Context context) {
52            super(context, null);
53            mTokenizer = new Rfc822Tokenizer();
54            setTokenizer(mTokenizer);
55        }
56
57        @Override
58        public DrawableRecipientChip[] getSortedRecipients() {
59            return mMockRecips;
60        }
61
62        @Override
63        public int getLineHeight() {
64            return 48;
65        }
66
67        @Override
68        Drawable getChipBackground(RecipientEntry contact) {
69            return createChipBackground();
70        }
71
72        @Override
73        public int getViewWidth() {
74            return 100;
75        }
76    }
77
78    class MockRecipientEditTextView extends BaseMockRecipientEditTextView {
79
80        public MockRecipientEditTextView(Context context) {
81            super(context);
82            mTokenizer = new Rfc822Tokenizer();
83            setTokenizer(mTokenizer);
84        }
85
86        @Override
87        public DrawableRecipientChip[] getSortedRecipients() {
88            return mMockRecips;
89        }
90
91        @Override
92        public Editable getText() {
93            return mEditable;
94        }
95
96        @Override
97        public Editable getSpannable() {
98            return mEditable;
99        }
100
101        @Override
102        public int getLineHeight() {
103            return 48;
104        }
105
106        @Override
107        Drawable getChipBackground(RecipientEntry contact) {
108            return createChipBackground();
109        }
110
111        @Override
112        public int length() {
113            return mEditable != null ? mEditable.length() : 0;
114        }
115
116        @Override
117        public String toString() {
118            return mEditable != null ? mEditable.toString() : "";
119        }
120
121        @Override
122        public int getViewWidth() {
123            return 100;
124        }
125    }
126
127    private class TestBaseRecipientAdapter extends BaseRecipientAdapter {
128        public TestBaseRecipientAdapter(final Context context) {
129            super(context);
130        }
131
132        public TestBaseRecipientAdapter(final Context context, final int preferredMaxResultCount,
133                final int queryMode) {
134            super(context, preferredMaxResultCount, queryMode);
135        }
136    }
137
138    private MockRecipientEditTextView createViewForTesting() {
139        mEditable = new SpannableStringBuilder();
140        MockRecipientEditTextView view = new MockRecipientEditTextView(getContext());
141        view.setAdapter(new TestBaseRecipientAdapter(getContext()));
142        return view;
143    }
144
145    public void testCreateDisplayText() {
146        RecipientEditTextView view = createViewForTesting();
147        RecipientEntry entry = RecipientEntry.constructGeneratedEntry("User Name, Jr",
148                "user@username.com", true);
149        String testAddress = view.createAddressText(entry);
150        String testDisplay = view.createChipDisplayText(entry);
151        assertEquals("Expected a properly formatted RFC email address",
152                "\"User Name, Jr\" <user@username.com>, ", testAddress);
153        assertEquals("Expected a displayable name", "User Name, Jr", testDisplay);
154
155        RecipientEntry alreadyFormatted =
156                RecipientEntry.constructFakeEntry("user@username.com, ", true);
157        testAddress = view.createAddressText(alreadyFormatted);
158        testDisplay = view.createChipDisplayText(alreadyFormatted);
159        assertEquals("Expected a properly formatted RFC email address", "<user@username.com>, ",
160                testAddress);
161        assertEquals("Expected a displayable name", "user@username.com", testDisplay);
162
163        RecipientEntry alreadyFormattedNoSpace = RecipientEntry
164                .constructFakeEntry("user@username.com,", true);
165        testAddress = view.createAddressText(alreadyFormattedNoSpace);
166        assertEquals("Expected a properly formatted RFC email address", "<user@username.com>, ",
167                testAddress);
168
169        RecipientEntry alreadyNamed = RecipientEntry.constructGeneratedEntry("User Name",
170                "\"User Name, Jr\" <user@username.com>", true);
171        testAddress = view.createAddressText(alreadyNamed);
172        testDisplay = view.createChipDisplayText(alreadyNamed);
173        assertEquals(
174                "Expected address that used the name not the excess address name",
175                "User Name <user@username.com>, ", testAddress);
176        assertEquals("Expected a displayable name", "User Name", testDisplay);
177    }
178
179    public void testSanitizeBetween() {
180        // First, add 2 chips and then make sure we remove
181        // the extra content between them correctly.
182        populateMocks(2);
183        MockRecipientEditTextView view = createViewForTesting();
184        String first = (String) mTokenizer.terminateToken("FIRST");
185        String second = (String) mTokenizer.terminateToken("SECOND");
186        String extra = "EXTRA";
187        mEditable = new SpannableStringBuilder();
188        mEditable.append(first + extra + second);
189        int firstStart = mEditable.toString().indexOf(first);
190        int firstEnd = firstStart + first.trim().length();
191        int secondStart = mEditable.toString().indexOf(second);
192        int secondEnd = secondStart + second.trim().length();
193        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], firstStart, firstEnd, 0);
194        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], secondStart, secondEnd, 0);
195        view.sanitizeBetween();
196        String editableString = mEditable.toString();
197        assertEquals(editableString.indexOf(extra), -1);
198        assertEquals(editableString.indexOf(first), firstStart);
199        assertEquals(editableString.indexOf(second), secondStart - extra.length());
200        assertEquals(editableString, (first + second));
201
202        // Add 1 chip and make sure that we remove the extra stuff before it correctly.
203        mEditable = new SpannableStringBuilder();
204        populateMocks(1);
205        mEditable.append(extra);
206        mEditable.append(first);
207        firstStart = mEditable.toString().indexOf(first);
208        firstEnd = firstStart + first.length();
209        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], firstStart, firstEnd, 0);
210        view.sanitizeBetween();
211        assertEquals(mEditable.toString(), first);
212        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), firstStart
213                - extra.length());
214    }
215
216    public void testSanitizeEnd() {
217        // First, add 2 chips and then make sure we remove
218        // the extra content between them correctly.
219        populateMocks(2);
220        MockRecipientEditTextView view = createViewForTesting();
221        String first = (String) mTokenizer.terminateToken("FIRST");
222        String second = (String) mTokenizer.terminateToken("SECOND");
223        String extra = "EXTRA";
224        mEditable = new SpannableStringBuilder();
225        mEditable.append(first + second);
226        int firstStart = mEditable.toString().indexOf(first);
227        int firstEnd = firstStart + first.trim().length();
228        int secondStart = mEditable.toString().indexOf(second);
229        int secondEnd = secondStart + second.trim().length();
230        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], firstStart, firstEnd, 0);
231        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], secondStart, secondEnd, 0);
232        view.sanitizeEnd();
233        String editableString = mEditable.toString();
234        assertEquals(editableString.indexOf(extra), -1);
235        assertEquals(editableString.indexOf(first), firstStart);
236        assertEquals(editableString.indexOf(second), secondStart);
237        assertEquals(editableString, (first + second));
238        mEditable.append(extra);
239        editableString = mEditable.toString();
240        assertEquals(mEditable.toString(), (first + second + extra));
241        view.sanitizeEnd();
242        assertEquals(mEditable.toString(), (first + second));
243    }
244
245    public void testMoreChipPlainText() {
246        MockRecipientEditTextView view = createViewForTesting();
247        view.setMoreItem(createTestMoreItem());
248        String first = (String) mTokenizer.terminateToken("FIRST");
249        String second = (String) mTokenizer.terminateToken("SECOND");
250        String third = (String) mTokenizer.terminateToken("THIRD");
251        mEditable = new SpannableStringBuilder();
252        mEditable.append(first+second+third);
253        int thirdStart = mEditable.toString().indexOf(third);
254        int thirdEnd = thirdStart + third.trim().length();
255        view.createMoreChipPlainText();
256        ReplacementDrawableSpan moreChip = view.getMoreChip();
257        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
258        assertEquals(mEditable.getSpanEnd(moreChip), thirdEnd + 1);
259    }
260
261    public void testCountTokens() {
262        MockRecipientEditTextView view = createViewForTesting();
263        view.setMoreItem(createTestMoreItem());
264        String first = (String) mTokenizer.terminateToken("FIRST");
265        String second = (String) mTokenizer.terminateToken("SECOND");
266        String third = (String) mTokenizer.terminateToken("THIRD");
267        String fourth = "FOURTH,";
268        String fifth = "FIFTH,";
269        mEditable = new SpannableStringBuilder();
270        mEditable.append(first+second+third+fourth+fifth);
271        assertEquals(view.countTokens(mEditable), 5);
272    }
273
274    public void testTooManyRecips() {
275        BaseMockRecipientEditTextView view = new BaseMockRecipientEditTextView(getContext());
276        view.setMoreItem(createTestMoreItem());
277        for (int i = 0; i < 100; i++) {
278            view.append(mTokenizer.terminateToken(i + ""));
279        }
280        assertEquals(view.countTokens(view.getText()), 100);
281        view.handlePendingChips();
282        view.createMoreChip();
283        ReplacementDrawableSpan moreChip = view.getMoreChip();
284        // We show 2 chips then place a more chip.
285        int secondStart = view.getText().toString().indexOf(
286                (String) mTokenizer.terminateToken(RecipientEditTextView.CHIP_LIMIT + ""));
287        assertEquals(view.getText().getSpanStart(moreChip), secondStart);
288        assertEquals(view.getText().getSpanEnd(moreChip), view.length());
289        assertEquals(view.getSortedRecipients(), null);
290    }
291
292    public void testMoreChip() {
293        // Add 3 chips: this is the trigger point at which the more chip will be created.
294        // Test that adding the chips and then creating and removing the more chip, as if
295        // the user were focusing/ removing focus from the chips field.
296        populateMocks(3);
297        MockRecipientEditTextView view = createViewForTesting();
298        view.setMoreItem(createTestMoreItem());
299        String first = (String) mTokenizer.terminateToken("FIRST");
300        String second = (String) mTokenizer.terminateToken("SECOND");
301        String third = (String) mTokenizer.terminateToken("THIRD");
302        mEditable = new SpannableStringBuilder();
303        mEditable.append(first+second+third);
304
305        int firstStart = mEditable.toString().indexOf(first);
306        int firstEnd = firstStart + first.trim().length();
307        int secondStart = mEditable.toString().indexOf(second);
308        int secondEnd = secondStart + second.trim().length();
309        int thirdStart = mEditable.toString().indexOf(third);
310        int thirdEnd = thirdStart + third.trim().length();
311        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
312        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
313        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
314
315        view.createMoreChip();
316        assertEquals(mEditable.toString(), first+second+third);
317        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
318        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
319        // Find the more chip.
320        ReplacementDrawableSpan moreChip = view.getMoreChip();
321        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
322        assertEquals(mEditable.getSpanEnd(moreChip), thirdEnd + 1);
323
324        view.removeMoreChip();
325        assertEquals(mEditable.toString(), first+second+third);
326        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
327        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
328        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
329        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdStart);
330        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdEnd);
331        moreChip = view.getMoreChip();
332        assertEquals(mEditable.getSpanStart(moreChip), -1);
333
334        // Rinse and repeat, just in case!
335        view.createMoreChip();
336        assertEquals(mEditable.toString(), first+second+third);
337        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
338        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
339        // Find the more chip.
340        moreChip = view.getMoreChip();
341        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
342        assertEquals(mEditable.getSpanEnd(moreChip), thirdEnd + 1);
343
344        view.removeMoreChip();
345        assertEquals(mEditable.toString(), first+second+third);
346        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
347        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
348        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
349        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdStart);
350        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdEnd);
351        moreChip = view.getMoreChip();
352        assertEquals(mEditable.getSpanStart(moreChip), -1);
353    }
354
355    public void testMoreChipLotsOfUsers() {
356        // Test adding and removing the more chip in the case where we have a lot of users.
357        populateMocks(10);
358        MockRecipientEditTextView view = createViewForTesting();
359        view.setMoreItem(createTestMoreItem());
360        String first = (String) mTokenizer.terminateToken("FIRST");
361        String second = (String) mTokenizer.terminateToken("SECOND");
362        String third = (String) mTokenizer.terminateToken("THIRD");
363        String fourth = (String) mTokenizer.terminateToken("FOURTH");
364        String fifth = (String) mTokenizer.terminateToken("FIFTH");
365        String sixth = (String) mTokenizer.terminateToken("SIXTH");
366        String seventh = (String) mTokenizer.terminateToken("SEVENTH");
367        String eigth = (String) mTokenizer.terminateToken("EIGHTH");
368        String ninth = (String) mTokenizer.terminateToken("NINTH");
369        String tenth = (String) mTokenizer.terminateToken("TENTH");
370        mEditable = new SpannableStringBuilder();
371        mEditable.append(first+second+third+fourth+fifth+sixth+seventh+eigth+ninth+tenth);
372
373        int firstStart = mEditable.toString().indexOf(first);
374        int firstEnd = firstStart + first.trim().length();
375        int secondStart = mEditable.toString().indexOf(second);
376        int secondEnd = secondStart + second.trim().length();
377        int thirdStart = mEditable.toString().indexOf(third);
378        int thirdEnd = thirdStart + third.trim().length();
379        int fourthStart = mEditable.toString().indexOf(fourth);
380        int fourthEnd = fourthStart + fourth.trim().length();
381        int fifthStart = mEditable.toString().indexOf(fifth);
382        int fifthEnd = fifthStart + fifth.trim().length();
383        int sixthStart = mEditable.toString().indexOf(sixth);
384        int sixthEnd = sixthStart + sixth.trim().length();
385        int seventhStart = mEditable.toString().indexOf(seventh);
386        int seventhEnd = seventhStart + seventh.trim().length();
387        int eighthStart = mEditable.toString().indexOf(eigth);
388        int eighthEnd = eighthStart + eigth.trim().length();
389        int ninthStart = mEditable.toString().indexOf(ninth);
390        int ninthEnd = ninthStart + ninth.trim().length();
391        int tenthStart = mEditable.toString().indexOf(tenth);
392        int tenthEnd = tenthStart + tenth.trim().length();
393        mEditable.setSpan(mMockRecips[mMockRecips.length - 10], firstStart, firstEnd, 0);
394        mEditable.setSpan(mMockRecips[mMockRecips.length - 9], secondStart, secondEnd, 0);
395        mEditable.setSpan(mMockRecips[mMockRecips.length - 8], thirdStart, thirdEnd, 0);
396        mEditable.setSpan(mMockRecips[mMockRecips.length - 7], fourthStart, fourthEnd, 0);
397        mEditable.setSpan(mMockRecips[mMockRecips.length - 6], fifthStart, fifthEnd, 0);
398        mEditable.setSpan(mMockRecips[mMockRecips.length - 5], sixthStart, sixthEnd, 0);
399        mEditable.setSpan(mMockRecips[mMockRecips.length - 4], seventhStart, seventhEnd, 0);
400        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], eighthStart, eighthEnd, 0);
401        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], ninthStart, ninthEnd, 0);
402        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], tenthStart, tenthEnd, 0);
403
404        view.createMoreChip();
405        assertEquals(mEditable.toString(), first + second + third + fourth + fifth + sixth
406                + seventh + eigth + ninth + tenth);
407        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 10]), firstStart);
408        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 9]), secondStart);
409        // Find the more chip.
410        ReplacementDrawableSpan moreChip = view.getMoreChip();
411        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
412        assertEquals(mEditable.getSpanEnd(moreChip), tenthEnd + 1);
413
414        view.removeMoreChip();
415        assertEquals(mEditable.toString(), first + second + third + fourth + fifth + sixth
416                + seventh + eigth + ninth + tenth);
417        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 10]), firstStart);
418        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 9]), secondStart);
419
420        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 8]), thirdStart);
421        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 7]), fourthStart);
422        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 6]), fifthStart);
423        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 5]), sixthStart);
424        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 4]), seventhStart);
425        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), eighthStart);
426        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), ninthStart);
427        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), tenthStart);
428        moreChip = view.getMoreChip();
429        assertEquals(mEditable.getSpanStart(moreChip), -1);
430
431    }
432
433    public void testMoreChipSpecialChars() {
434        // Make sure the more chip correctly handles extra tokenizer characters in the middle
435        // of chip text.
436        populateMocks(3);
437        MockRecipientEditTextView view = createViewForTesting();
438        view.setMoreItem(createTestMoreItem());
439        String first = (String) mTokenizer.terminateToken("FI,RST");
440        String second = (String) mTokenizer.terminateToken("SE,COND");
441        String third = (String) mTokenizer.terminateToken("THI,RD");
442        mEditable = new SpannableStringBuilder();
443        mEditable.append(first+second+third);
444
445        int firstStart = mEditable.toString().indexOf(first);
446        int firstEnd = firstStart + first.trim().length();
447        int secondStart = mEditable.toString().indexOf(second);
448        int secondEnd = secondStart + second.trim().length();
449        int thirdStart = mEditable.toString().indexOf(third);
450        int thirdEnd = thirdStart + third.trim().length();
451        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
452        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
453        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
454
455        view.createMoreChip();
456        assertEquals(mEditable.toString(), first+second+third);
457        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
458        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
459        // Find the more chip.
460        ReplacementDrawableSpan moreChip = view.getMoreChip();
461        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
462        assertEquals(mEditable.getSpanEnd(moreChip), thirdEnd + 1);
463
464        view.removeMoreChip();
465        assertEquals(mEditable.toString(), first+second+third);
466        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
467        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
468        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
469        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdStart);
470        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdEnd);
471        moreChip = view.getMoreChip();
472        assertEquals(mEditable.getSpanStart(moreChip), -1);
473    }
474
475    public void testMoreChipDupes() {
476        // Make sure the more chip is correctly added and removed when we have duplicate chips.
477        populateMocks(4);
478        MockRecipientEditTextView view = createViewForTesting();
479        view.setMoreItem(createTestMoreItem());
480        String first = (String) mTokenizer.terminateToken("FIRST");
481        String second = (String) mTokenizer.terminateToken("SECOND");
482        String third = (String) mTokenizer.terminateToken("THIRD");
483        mEditable = new SpannableStringBuilder();
484        mEditable.append(first+second+third+third);
485
486        int firstStart = mEditable.toString().indexOf(first);
487        int firstEnd = firstStart + first.trim().length();
488        int secondStart = mEditable.toString().indexOf(second);
489        int secondEnd = secondStart + second.trim().length();
490        int thirdStart = mEditable.toString().indexOf(third);
491        int thirdEnd = thirdStart + third.trim().length();
492        int thirdNextStart = mEditable.toString().indexOf(third, thirdEnd);
493        int thirdNextEnd = thirdNextStart + third.trim().length();
494        mEditable.setSpan(mMockRecips[mMockRecips.length - 4], firstStart, firstEnd, 0);
495        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], secondStart, secondEnd, 0);
496        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], thirdStart, thirdEnd, 0);
497        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdNextStart, thirdNextEnd, 0);
498
499        view.createMoreChip();
500        assertEquals(mEditable.toString(), first+second+third+third);
501        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 4]), firstStart);
502        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), secondStart);
503        // Find the more chip.
504        ReplacementDrawableSpan moreChip = view.getMoreChip();
505        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
506        assertEquals(mEditable.getSpanEnd(moreChip), thirdNextEnd + 1);
507
508        view.removeMoreChip();
509        assertEquals(mEditable.toString(), first+second+third+third);
510        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 4]), firstStart);
511        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 4]), firstEnd);
512        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), secondStart);
513        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), thirdStart);
514        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), thirdEnd);
515        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdNextStart);
516        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdNextEnd);
517        moreChip = view.getMoreChip();
518        assertEquals(mEditable.getSpanStart(moreChip), -1);
519    }
520
521    public void testRemoveChip() {
522        // Create 3 chips to start and test removing chips in various postions.
523        populateMocks(3);
524        MockRecipientEditTextView view = createViewForTesting();
525        view.setMoreItem(createTestMoreItem());
526        String first = (String) mTokenizer.terminateToken("FIRST");
527        String second = (String) mTokenizer.terminateToken("SECOND");
528        String third = (String) mTokenizer.terminateToken("THIRD");
529        mEditable = new SpannableStringBuilder();
530        mEditable.append(first + second + third);
531
532        int firstStart = mEditable.toString().indexOf(first);
533        int firstEnd = firstStart + first.length();
534        int secondStart = mEditable.toString().indexOf(second);
535        int secondEnd = secondStart + second.length();
536        int thirdStart = mEditable.toString().indexOf(third);
537        int thirdEnd = thirdStart + third.length();
538        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
539        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
540        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
541        assertEquals(mEditable.toString(), first + second + third);
542        // Test removing the middle chip.
543        view.removeChip(mMockRecips[mMockRecips.length - 2]);
544        assertEquals(mEditable.toString(), first + third);
545        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
546        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
547        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), -1);
548        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), -1);
549        int newThirdStart = mEditable.toString().indexOf(third);
550        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), newThirdStart);
551        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), newThirdStart
552                + third.length());
553
554        // Test removing the first chip.
555        populateMocks(3);
556        view = createViewForTesting();
557        view.setMoreItem(createTestMoreItem());
558        mEditable = new SpannableStringBuilder();
559        mEditable.append(first + second + third);
560
561        firstStart = mEditable.toString().indexOf(first);
562        firstEnd = firstStart + first.length();
563        secondStart = mEditable.toString().indexOf(second);
564        secondEnd = secondStart + second.length();
565        thirdStart = mEditable.toString().indexOf(third);
566        thirdEnd = thirdStart + third.length();
567        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
568        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
569        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
570        assertEquals(mEditable.toString(), first + second + third);
571        view.removeChip(mMockRecips[mMockRecips.length - 3]);
572        assertEquals(mEditable.toString(), second + third);
573        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), -1);
574        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), -1);
575        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), 0);
576        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), second.length());
577        newThirdStart = mEditable.toString().indexOf(third);
578        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), newThirdStart);
579        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), newThirdStart
580                + third.length());
581
582        // Test removing the last chip.
583        populateMocks(3);
584        view = createViewForTesting();
585        view.setMoreItem(createTestMoreItem());
586        mEditable = new SpannableStringBuilder();
587        mEditable.append(first + second + third);
588
589        firstStart = mEditable.toString().indexOf(first);
590        firstEnd = firstStart + first.length();
591        secondStart = mEditable.toString().indexOf(second);
592        secondEnd = secondStart + second.length();
593        thirdStart = mEditable.toString().indexOf(third);
594        thirdEnd = thirdStart + third.length();
595        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
596        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
597        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
598        assertEquals(mEditable.toString(), first + second + third);
599        view.removeChip(mMockRecips[mMockRecips.length - 1]);
600        assertEquals(mEditable.toString(), first + second);
601        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
602        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
603        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
604        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), secondEnd);
605        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), -1);
606        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), -1);
607    }
608
609    public void testReplaceChip() {
610        populateMocks(3);
611        MockRecipientEditTextView view = createViewForTesting();
612        view.setMoreItem(createTestMoreItem());
613        view.setChipBackground(createChipBackground());
614        view.setChipHeight(48);
615        String first = (String) mTokenizer.terminateToken("FIRST");
616        String second = (String) mTokenizer.terminateToken("SECOND");
617        String third = (String) mTokenizer.terminateToken("THIRD");
618        mEditable = new SpannableStringBuilder();
619        mEditable.append(first + second + third);
620
621        // Test replacing the first chip with a new chip.
622        int firstStart = mEditable.toString().indexOf(first);
623        int firstEnd = firstStart + first.trim().length();
624        int secondStart = mEditable.toString().indexOf(second);
625        int secondEnd = secondStart + second.trim().length();
626        int thirdStart = mEditable.toString().indexOf(third);
627        int thirdEnd = thirdStart + third.trim().length();
628        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
629        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
630        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
631        assertEquals(mEditable.toString(), first + second + third);
632        view.replaceChip(mMockRecips[mMockRecips.length - 3], RecipientEntry
633                .constructGeneratedEntry("replacement", "replacement@replacement.com", true));
634        assertEquals(mEditable.toString(), mTokenizer
635                .terminateToken("replacement <replacement@replacement.com>")
636                + second + third);
637        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), -1);
638        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), -1);
639        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), mEditable
640                .toString().indexOf(second));
641        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), mEditable
642                .toString().indexOf(second)
643                + second.trim().length());
644        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), mEditable
645                .toString().indexOf(third));
646        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), mEditable
647                .toString().indexOf(third)
648                + third.trim().length());
649        DrawableRecipientChip[] spans =
650                mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class);
651        assertEquals(spans.length, 3);
652        spans = mEditable
653                .getSpans(0, mEditable.toString().indexOf(second) - 1, DrawableRecipientChip.class);
654        assertEquals((String) spans[0].getDisplay(), "replacement");
655
656
657        // Test replacing the middle chip with a new chip.
658        mEditable = new SpannableStringBuilder();
659        mEditable.append(first + second + third);
660        firstStart = mEditable.toString().indexOf(first);
661        firstEnd = firstStart + first.trim().length();
662        secondStart = mEditable.toString().indexOf(second);
663        secondEnd = secondStart + second.trim().length();
664        thirdStart = mEditable.toString().indexOf(third);
665        thirdEnd = thirdStart + third.trim().length();
666        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
667        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
668        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
669        assertEquals(mEditable.toString(), first + second + third);
670        view.replaceChip(mMockRecips[mMockRecips.length - 2], RecipientEntry
671                .constructGeneratedEntry("replacement", "replacement@replacement.com", true));
672        assertEquals(mEditable.toString(), first + mTokenizer
673                .terminateToken("replacement <replacement@replacement.com>") + third);
674        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
675        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
676        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), -1);
677        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), -1);
678        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), mEditable
679                .toString().indexOf(third));
680        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), mEditable
681                .toString().indexOf(third)
682                + third.trim().length());
683        spans = mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class);
684        assertEquals(spans.length, 3);
685        spans = mEditable.getSpans(firstEnd, mEditable.toString().indexOf(third) - 1,
686                DrawableRecipientChip.class);
687        assertEquals((String) spans[0].getDisplay(), "replacement");
688
689
690        // Test replacing the last chip with a new chip.
691        mEditable = new SpannableStringBuilder();
692        mEditable.append(first + second + third);
693        firstStart = mEditable.toString().indexOf(first);
694        firstEnd = firstStart + first.trim().length();
695        secondStart = mEditable.toString().indexOf(second);
696        secondEnd = secondStart + second.trim().length();
697        thirdStart = mEditable.toString().indexOf(third);
698        thirdEnd = thirdStart + third.trim().length();
699        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
700        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
701        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
702        assertEquals(mEditable.toString(), first + second + third);
703        view.replaceChip(mMockRecips[mMockRecips.length - 1], RecipientEntry
704                .constructGeneratedEntry("replacement", "replacement@replacement.com", true));
705        assertEquals(mEditable.toString(), first + second + mTokenizer
706                .terminateToken("replacement <replacement@replacement.com>"));
707        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
708        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
709        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
710        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), secondEnd);
711        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), -1);
712        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), -1);
713        spans = mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class);
714        assertEquals(spans.length, 3);
715        spans = mEditable
716                .getSpans(secondEnd, mEditable.length(), DrawableRecipientChip.class);
717        assertEquals((String) spans[0].getDisplay(), "replacement");
718    }
719
720    public void testHandlePaste() {
721        // Start with an empty edit field.
722        // Add an address; the text should be left as is.
723        MockRecipientEditTextView view = createViewForTesting();
724        view.setMoreItem(createTestMoreItem());
725        view.setChipBackground(createChipBackground());
726        view.setChipHeight(48);
727        mEditable = new SpannableStringBuilder();
728        mEditable.append("user@user.com");
729        view.setSelection(mEditable.length());
730        view.handlePaste();
731        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 0);
732        assertEquals(mEditable.toString(), "user@user.com");
733
734        // Test adding a single address to an empty chips field with a space at
735        // the end of it. The address should stay as text.
736        mEditable = new SpannableStringBuilder();
737        String tokenizedUser = "user@user.com" + " ";
738        mEditable.append(tokenizedUser);
739        view.setSelection(mEditable.length());
740        view.handlePaste();
741        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 0);
742        assertEquals(mEditable.toString(), tokenizedUser);
743
744        // Test adding a single address to an empty chips field with a semicolon at
745        // the end of it. The address should become a chip
746        mEditable = new SpannableStringBuilder();
747        tokenizedUser = "user@user.com;";
748        mEditable.append(tokenizedUser);
749        view.setSelection(mEditable.length());
750        view.handlePaste();
751        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 1);
752
753        // Test adding 2 address to an empty chips field. The second to last
754        // address should become a chip and the last address should stay as
755        // text.
756        mEditable = new SpannableStringBuilder();
757        mEditable.append("user1,user2@user.com");
758        view.setSelection(mEditable.length());
759        view.handlePaste();
760        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 1);
761        assertEquals(mEditable.getSpans(0, mEditable.toString().indexOf("user2@user.com"),
762                DrawableRecipientChip.class).length, 1);
763        assertEquals(mEditable.toString(), "<user1>, user2@user.com");
764
765        // Test adding a single address to the end of existing chips. The existing
766        // chips should remain, and the last address should stay as text.
767        populateMocks(3);
768        String first = (String) mTokenizer.terminateToken("FIRST");
769        String second = (String) mTokenizer.terminateToken("SECOND");
770        String third = (String) mTokenizer.terminateToken("THIRD");
771        mEditable = new SpannableStringBuilder();
772        mEditable.append(first + second + third);
773        view.setSelection(mEditable.length());
774        int firstStart = mEditable.toString().indexOf(first);
775        int firstEnd = firstStart + first.trim().length();
776        int secondStart = mEditable.toString().indexOf(second);
777        int secondEnd = secondStart + second.trim().length();
778        int thirdStart = mEditable.toString().indexOf(third);
779        int thirdEnd = thirdStart + third.trim().length();
780        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
781        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
782        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
783
784        mEditable.append("user@user.com");
785        view.setSelection(mEditable.length());
786        view.handlePaste();
787        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length,
788                mMockRecips.length);
789        assertEquals(mEditable.toString(), first + second + third + "user@user.com");
790
791        // Paste 2 addresses after existing chips. We expect the first address to be turned into
792        // a chip and the second to be left as text.
793        populateMocks(3);
794        mEditable = new SpannableStringBuilder();
795        mEditable.append(first + second + third);
796
797        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
798        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
799        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
800
801        mEditable.append("user1, user2@user.com");
802        view.setSelection(mEditable.length());
803        view.handlePaste();
804        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length,
805                mMockRecips.length + 1);
806        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("<user1>"), mEditable
807                .toString().indexOf("user2@user.com") - 1, DrawableRecipientChip.class).length, 1);
808        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("user2@user.com"), mEditable
809                .length(), DrawableRecipientChip.class).length, 0);
810        assertEquals(mEditable.toString(), first + second + third + "<user1>, user2@user.com");
811
812        // Paste 2 addresses after existing chips. We expect the first address to be turned into
813        // a chip and the second to be left as text. This removes the space seperator char between
814        // addresses.
815        populateMocks(3);
816        mEditable = new SpannableStringBuilder();
817        mEditable.append(first + second + third);
818
819        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
820        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
821        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
822
823        mEditable.append("user1,user2@user.com");
824        view.setSelection(mEditable.length());
825        view.handlePaste();
826        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length,
827                mMockRecips.length + 1);
828        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("<user1>"), mEditable
829                .toString().indexOf("user2@user.com") - 1, DrawableRecipientChip.class).length, 1);
830        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("user2@user.com"), mEditable
831                .length(), DrawableRecipientChip.class).length, 0);
832        assertEquals(mEditable.toString(), first + second + third + "<user1>, user2@user.com");
833
834        // Test a complete token pasted in at the end. It should be turned into a chip.
835        mEditable = new SpannableStringBuilder();
836        mEditable.append("user1, user2@user.com,");
837        view.setSelection(mEditable.length());
838        view.handlePaste();
839        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 2);
840        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("<user1>"), mEditable
841                .toString().indexOf("user2@user.com") - 1, DrawableRecipientChip.class).length, 1);
842        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("user2@user.com"), mEditable
843                .length(), DrawableRecipientChip.class).length, 1);
844        assertEquals(mEditable.toString(), "<user1>, <user2@user.com>, ");
845    }
846
847    @TargetApi(16)
848    public void testHandlePasteClip() {
849        MockRecipientEditTextView view = createViewForTesting();
850
851        ClipData clipData = null;
852        mEditable = new SpannableStringBuilder();
853        view.handlePasteClip(clipData);
854        assertEquals("", view.getText().toString());
855
856        clipData = ClipData.newPlainText("user label", "<foo@example.com>");
857        mEditable = new SpannableStringBuilder();
858        view.handlePasteClip(clipData);
859        assertEquals("<foo@example.com>", view.getText().toString());
860
861        clipData = ClipData.newHtmlText("user label",
862                "<bar@example.com>", "<a href=\"mailto:bar@example.com\">email</a>");
863        mEditable = new SpannableStringBuilder();
864        view.handlePasteClip(clipData);
865        assertEquals("<bar@example.com>", view.getText().toString());
866
867        ClipData.Item clipImageData = new ClipData.Item(Uri.parse("content://my/image"));
868        clipData = new ClipData("user label", new String[]{"image/jpeg"}, clipImageData);
869        mEditable = new SpannableStringBuilder();
870        view.handlePasteClip(clipData);
871        assertEquals("", view.getText().toString()
872        );
873    }
874
875    public void testGetPastTerminators() {
876        MockRecipientEditTextView view = createViewForTesting();
877        view.setMoreItem(createTestMoreItem());
878        view.setChipBackground(createChipBackground());
879        view.setChipHeight(48);
880        String test = "test";
881        mEditable = new SpannableStringBuilder();
882        mEditable.append(test);
883        assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
884                test.length());
885
886        test = "test,";
887        mEditable = new SpannableStringBuilder();
888        mEditable.append(test);
889        assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
890                test.length());
891
892        test = "test, ";
893        mEditable = new SpannableStringBuilder();
894        mEditable.append(test);
895        assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
896                test.length());
897
898        test = "test;";
899        mEditable = new SpannableStringBuilder();
900        mEditable.append(test);
901        assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
902                test.length());
903
904        test = "test; ";
905        mEditable = new SpannableStringBuilder();
906        mEditable.append(test);
907        assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
908                test.length());
909    }
910
911    public void testIsCompletedToken() {
912        MockRecipientEditTextView view = createViewForTesting();
913        view.setMoreItem(createTestMoreItem());
914        view.setChipBackground(createChipBackground());
915        view.setChipHeight(48);
916        assertTrue(view.isCompletedToken("test;"));
917        assertTrue(view.isCompletedToken("test,"));
918        assertFalse(view.isCompletedToken("test"));
919        assertFalse(view.isCompletedToken("test "));
920    }
921
922    public void testGetLastChip() {
923        populateMocks(3);
924        MockRecipientEditTextView view = createViewForTesting();
925        view.setMoreItem(createTestMoreItem());
926        view.setChipBackground(createChipBackground());
927        view.setChipHeight(48);
928        String first = (String) mTokenizer.terminateToken("FIRST");
929        String second = (String) mTokenizer.terminateToken("SECOND");
930        String third = (String) mTokenizer.terminateToken("THIRD");
931        mEditable = new SpannableStringBuilder();
932        mEditable.append(first + second + third);
933
934        // Test replacing the first chip with a new chip.
935        int firstStart = mEditable.toString().indexOf(first);
936        int firstEnd = firstStart + first.trim().length();
937        int secondStart = mEditable.toString().indexOf(second);
938        int secondEnd = secondStart + second.trim().length();
939        int thirdStart = mEditable.toString().indexOf(third);
940        int thirdEnd = thirdStart + third.trim().length();
941        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
942        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
943        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
944        assertEquals(view.getLastChip(), mMockRecips[mMockRecips.length - 1]);
945        mEditable.append("extra");
946        assertEquals(view.getLastChip(), mMockRecips[mMockRecips.length - 1]);
947    }
948
949    private Drawable createChipBackground() {
950        Bitmap drawable = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
951        return new BitmapDrawable(getContext().getResources(), drawable);
952    }
953
954    private TextView createTestMoreItem() {
955        TextView view = new TextView(getContext());
956        view.setText("<xliff:g id='count'>%1$s</xliff:g> more...");
957        return view;
958    }
959
960    private void populateMocks(int size) {
961        mMockEntries = new RecipientEntry[size];
962        for (int i = 0; i < size; i++) {
963            mMockEntries[i] = RecipientEntry.constructGeneratedEntry("user",
964                    "user@username.com", true);
965        }
966        mMockRecips = new DrawableRecipientChip[size];
967        for (int i = 0; i < size; i++) {
968            mMockRecips[i] = new VisibleRecipientChip(null, mMockEntries[i]);
969        }
970    }
971
972    /**
973     * <p>
974     * Ensure the original text is always accurate, regardless of the type of email. The original
975     * text is used to determine where to display the chip span. If this test fails, it means some
976     * text that should be turned into one whole chip may behave unexpectedly.
977     * </p>
978     * <p>
979     * For example, a bug was seen where
980     *
981     * <pre>
982     * "Android User" <android@example.com>
983     * </pre>
984     *
985     * was converted to
986     *
987     * <pre>
988     * Android User [android@example.com]
989     * </pre>
990     *
991     * where text inside [] is a chip.
992     * </p>
993     */
994    public void testCreateReplacementChipOriginalText() {
995        // Name in quotes + email address
996        testCreateReplacementChipOriginalText("\"Android User\" <android@example.com>,");
997        // Name in quotes + email address without brackets
998        testCreateReplacementChipOriginalText("\"Android User\" android@example.com,");
999        // Name in quotes
1000        testCreateReplacementChipOriginalText("\"Android User\",");
1001        // Name without quotes + email address
1002        testCreateReplacementChipOriginalText("Android User <android@example.com>,");
1003        // Name without quotes
1004        testCreateReplacementChipOriginalText("Android User,");
1005        // Email address
1006        testCreateReplacementChipOriginalText("<android@example.com>,");
1007        // Email address without brackets
1008        testCreateReplacementChipOriginalText("android@example.com,");
1009    }
1010
1011    private void testCreateReplacementChipOriginalText(final String email) {
1012        // No trailing space
1013        attemptCreateReplacementChipOriginalText(email.trim());
1014        // Trailing space
1015        attemptCreateReplacementChipOriginalText(email.trim() + " ");
1016    }
1017
1018    private void attemptCreateReplacementChipOriginalText(final String email) {
1019        final RecipientEditTextView view = new RecipientEditTextView(getContext(), null);
1020
1021        view.setText(email);
1022        view.mPendingChips.add(email);
1023
1024        view.createReplacementChip(0, email.length(), view.getText(), true);
1025        // The "original text" should be the email without the comma or space(s)
1026        assertEquals(email.replaceAll(",\\s*$", ""),
1027                view.mTemporaryRecipients.get(0).getOriginalText().toString().trim());
1028    }
1029
1030    public void testCreateTokenizedEntryForPhone() {
1031        final String phonePattern = "[^\\d]*888[^\\d]*555[^\\d]*1234[^\\d]*";
1032        final String phone1 = "8885551234";
1033        final String phone2 = "888-555-1234";
1034        final String phone3 = "(888) 555-1234";
1035
1036        final RecipientEditTextView view = new RecipientEditTextView(getContext(), null);
1037        final BaseRecipientAdapter adapter = new TestBaseRecipientAdapter(getContext(), 10,
1038                BaseRecipientAdapter.QUERY_TYPE_PHONE);
1039        view.setAdapter(adapter);
1040
1041        final RecipientEntry entry1 = view.createTokenizedEntry(phone1);
1042        final String destination1 = entry1.getDestination();
1043        assertTrue(phone1 + " failed with " + destination1,
1044                Pattern.matches(phonePattern, destination1));
1045
1046        final RecipientEntry entry2 = view.createTokenizedEntry(phone2);
1047        final String destination2 = entry2.getDestination();
1048        assertTrue(phone2 + " failed with " + destination2,
1049                Pattern.matches(phonePattern, destination2));
1050
1051        final RecipientEntry entry3 = view.createTokenizedEntry(phone3);
1052        final String destination3 = entry3.getDestination();
1053        assertTrue(phone3 + " failed with " + destination3,
1054                Pattern.matches(phonePattern, destination3));
1055    }
1056}
1057