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