ChipsTest.java revision 00be4ce28569ae7d1871a2f95453b0e6a6a47852
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.content.Context;
20import android.graphics.Bitmap;
21import android.graphics.drawable.BitmapDrawable;
22import android.graphics.drawable.Drawable;
23import android.test.AndroidTestCase;
24import android.test.suitebuilder.annotation.SmallTest;
25import android.text.Editable;
26import android.text.SpannableStringBuilder;
27import android.text.style.ImageSpan;
28import android.text.util.Rfc822Tokenizer;
29import android.widget.TextView;
30
31import com.android.ex.chips.RecipientEditTextView;
32import com.android.ex.chips.RecipientEntry;
33
34@SmallTest
35public class ChipsTest extends AndroidTestCase {
36    private RecipientChip[] mMockRecips;
37
38    private RecipientEntry[] mMockEntries;
39
40    private Rfc822Tokenizer mTokenizer;
41
42    private Editable mEditable;
43
44    class MockRecipientEditTextView extends RecipientEditTextView {
45
46        public MockRecipientEditTextView(Context context) {
47            super(context, null);
48            mTokenizer = new Rfc822Tokenizer();
49            setTokenizer(mTokenizer);
50        }
51
52        @Override
53        public RecipientChip[] getSortedRecipients() {
54            return mMockRecips;
55        }
56
57        @Override
58        public Editable getText() {
59            return mEditable;
60        }
61
62        @Override
63        public Editable getSpannable() {
64            return mEditable;
65        }
66
67        @Override
68        public int getLineHeight() {
69            return 48;
70        }
71
72        @Override
73        Drawable getChipBackground(RecipientEntry contact) {
74            return createChipBackground();
75        }
76
77        @Override
78        public int length() {
79            return mEditable != null ? mEditable.length() : 0;
80        }
81
82        @Override
83        public String toString() {
84            return mEditable != null ? mEditable.toString() : "";
85        }
86    }
87
88    private MockRecipientEditTextView createViewForTesting() {
89        mEditable = new SpannableStringBuilder();
90        MockRecipientEditTextView view = new MockRecipientEditTextView(getContext());
91        return view;
92    }
93
94    public void testCreateDisplayText() {
95        RecipientEditTextView view = createViewForTesting();
96        RecipientEntry entry = RecipientEntry.constructGeneratedEntry("User Name, Jr",
97                "user@username.com");
98        String testAddress = view.createAddressText(entry);
99        String testDisplay = view.createChipDisplayText(entry);
100        assertEquals("Expected a properly formatted RFC email address",
101                "\"User Name, Jr\" <user@username.com>, ", testAddress);
102        assertEquals("Expected a displayable name", "User Name, Jr", testDisplay);
103
104
105        RecipientEntry alreadyFormatted = RecipientEntry.constructFakeEntry("user@username.com, ");
106        testAddress = view.createAddressText(alreadyFormatted);
107        testDisplay = view.createChipDisplayText(alreadyFormatted);
108        assertEquals("Expected a properly formatted RFC email address", "<user@username.com>, ",
109                testAddress);
110        assertEquals("Expected a displayable name", "user@username.com", testDisplay);
111
112        RecipientEntry alreadyFormattedNoSpace = RecipientEntry
113                .constructFakeEntry("user@username.com,");
114        testAddress = view.createAddressText(alreadyFormattedNoSpace);
115        assertEquals("Expected a properly formatted RFC email address", "<user@username.com>, ",
116                testAddress);
117
118        RecipientEntry alreadyNamed = RecipientEntry.constructGeneratedEntry("User Name",
119                "\"User Name, Jr\" <user@username.com>");
120        testAddress = view.createAddressText(alreadyNamed);
121        testDisplay = view.createChipDisplayText(alreadyNamed);
122        assertEquals(
123                "Expected address that used the name not the excess address name",
124                "User Name <user@username.com>, ", testAddress);
125        assertEquals("Expected a displayable name", "User Name", testDisplay);
126    }
127
128    public void testSanitizeBetween() {
129        // First, add 2 chips and then make sure we remove
130        // the extra content between them correctly.
131        populateMocks(2);
132        MockRecipientEditTextView view = createViewForTesting();
133        String first = (String) mTokenizer.terminateToken("FIRST");
134        String second = (String) mTokenizer.terminateToken("SECOND");
135        String extra = "EXTRA";
136        mEditable = new SpannableStringBuilder();
137        mEditable.append(first + extra + second);
138        int firstStart = mEditable.toString().indexOf(first);
139        int firstEnd = firstStart + first.trim().length();
140        int secondStart = mEditable.toString().indexOf(second);
141        int secondEnd = secondStart + second.trim().length();
142        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], firstStart, firstEnd, 0);
143        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], secondStart, secondEnd, 0);
144        view.sanitizeBetween();
145        String editableString = mEditable.toString();
146        assertEquals(editableString.indexOf(extra), -1);
147        assertEquals(editableString.indexOf(first), firstStart);
148        assertEquals(editableString.indexOf(second), secondStart - extra.length());
149        assertEquals(editableString, (first + second));
150
151        // Add 1 chip and make sure that we remove the extra stuff before it correctly.
152        mEditable = new SpannableStringBuilder();
153        populateMocks(1);
154        mEditable.append(extra);
155        mEditable.append(first);
156        firstStart = mEditable.toString().indexOf(first);
157        firstEnd = firstStart + first.length();
158        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], firstStart, firstEnd, 0);
159        view.sanitizeBetween();
160        assertEquals(mEditable.toString(), first);
161        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), firstStart
162                - extra.length());
163    }
164
165    public void testSanitizeEnd() {
166        // First, add 2 chips and then make sure we remove
167        // the extra content between them correctly.
168        populateMocks(2);
169        MockRecipientEditTextView view = createViewForTesting();
170        String first = (String) mTokenizer.terminateToken("FIRST");
171        String second = (String) mTokenizer.terminateToken("SECOND");
172        String extra = "EXTRA";
173        mEditable = new SpannableStringBuilder();
174        mEditable.append(first + second);
175        int firstStart = mEditable.toString().indexOf(first);
176        int firstEnd = firstStart + first.trim().length();
177        int secondStart = mEditable.toString().indexOf(second);
178        int secondEnd = secondStart + second.trim().length();
179        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], firstStart, firstEnd, 0);
180        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], secondStart, secondEnd, 0);
181        view.sanitizeEnd();
182        String editableString = mEditable.toString();
183        assertEquals(editableString.indexOf(extra), -1);
184        assertEquals(editableString.indexOf(first), firstStart);
185        assertEquals(editableString.indexOf(second), secondStart);
186        assertEquals(editableString, (first + second));
187        mEditable.append(extra);
188        editableString = mEditable.toString();
189        assertEquals(mEditable.toString(), (first + second + extra));
190        view.sanitizeEnd();
191        assertEquals(mEditable.toString(), (first + second));
192    }
193
194    public void testMoreChip() {
195        // Add 3 chips: this is the trigger point at which the more chip will be created.
196        // Test that adding the chips and then creating and removing the more chip, as if
197        // the user were focusing/ removing focus from the chips field.
198        populateMocks(3);
199        MockRecipientEditTextView view = createViewForTesting();
200        view.setMoreItem(createTestMoreItem());
201        String first = (String) mTokenizer.terminateToken("FIRST");
202        String second = (String) mTokenizer.terminateToken("SECOND");
203        String third = (String) mTokenizer.terminateToken("THIRD");
204        mEditable = new SpannableStringBuilder();
205        mEditable.append(first+second+third);
206
207        int firstStart = mEditable.toString().indexOf(first);
208        int firstEnd = firstStart + first.trim().length();
209        int secondStart = mEditable.toString().indexOf(second);
210        int secondEnd = secondStart + second.trim().length();
211        int thirdStart = mEditable.toString().indexOf(third);
212        int thirdEnd = thirdStart + third.trim().length();
213        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
214        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
215        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
216
217        view.createMoreChip();
218        assertEquals(mEditable.toString(), first+second+third);
219        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
220        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
221        // Find the more chip.
222        ImageSpan moreChip = view.getMoreChip();
223        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
224        assertEquals(mEditable.getSpanEnd(moreChip), thirdEnd);
225
226        view.removeMoreChip();
227        assertEquals(mEditable.toString(), first+second+third);
228        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
229        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
230        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
231        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdStart);
232        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdEnd);
233        moreChip = view.getMoreChip();
234        assertEquals(mEditable.getSpanStart(moreChip), -1);
235
236        // Rinse and repeat, just in case!
237        view.createMoreChip();
238        assertEquals(mEditable.toString(), first+second+third);
239        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
240        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
241        // Find the more chip.
242        moreChip = view.getMoreChip();
243        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
244        assertEquals(mEditable.getSpanEnd(moreChip), thirdEnd);
245
246        view.removeMoreChip();
247        assertEquals(mEditable.toString(), first+second+third);
248        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
249        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
250        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
251        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdStart);
252        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdEnd);
253        moreChip = view.getMoreChip();
254        assertEquals(mEditable.getSpanStart(moreChip), -1);
255    }
256
257    public void testMoreChipLotsOfUsers() {
258        // Test adding and removing the more chip in the case where we have a lot of users.
259        populateMocks(10);
260        MockRecipientEditTextView view = createViewForTesting();
261        view.setMoreItem(createTestMoreItem());
262        String first = (String) mTokenizer.terminateToken("FIRST");
263        String second = (String) mTokenizer.terminateToken("SECOND");
264        String third = (String) mTokenizer.terminateToken("THIRD");
265        String fourth = (String) mTokenizer.terminateToken("FOURTH");
266        String fifth = (String) mTokenizer.terminateToken("FIFTH");
267        String sixth = (String) mTokenizer.terminateToken("SIXTH");
268        String seventh = (String) mTokenizer.terminateToken("SEVENTH");
269        String eigth = (String) mTokenizer.terminateToken("EIGHTH");
270        String ninth = (String) mTokenizer.terminateToken("NINTH");
271        String tenth = (String) mTokenizer.terminateToken("TENTH");
272        mEditable = new SpannableStringBuilder();
273        mEditable.append(first+second+third+fourth+fifth+sixth+seventh+eigth+ninth+tenth);
274
275        int firstStart = mEditable.toString().indexOf(first);
276        int firstEnd = firstStart + first.trim().length();
277        int secondStart = mEditable.toString().indexOf(second);
278        int secondEnd = secondStart + second.trim().length();
279        int thirdStart = mEditable.toString().indexOf(third);
280        int thirdEnd = thirdStart + third.trim().length();
281        int fourthStart = mEditable.toString().indexOf(fourth);
282        int fourthEnd = fourthStart + fourth.trim().length();
283        int fifthStart = mEditable.toString().indexOf(fifth);
284        int fifthEnd = fifthStart + fifth.trim().length();
285        int sixthStart = mEditable.toString().indexOf(sixth);
286        int sixthEnd = sixthStart + sixth.trim().length();
287        int seventhStart = mEditable.toString().indexOf(seventh);
288        int seventhEnd = seventhStart + seventh.trim().length();
289        int eighthStart = mEditable.toString().indexOf(eigth);
290        int eighthEnd = eighthStart + eigth.trim().length();
291        int ninthStart = mEditable.toString().indexOf(ninth);
292        int ninthEnd = ninthStart + ninth.trim().length();
293        int tenthStart = mEditable.toString().indexOf(tenth);
294        int tenthEnd = tenthStart + tenth.trim().length();
295        mEditable.setSpan(mMockRecips[mMockRecips.length - 10], firstStart, firstEnd, 0);
296        mEditable.setSpan(mMockRecips[mMockRecips.length - 9], secondStart, secondEnd, 0);
297        mEditable.setSpan(mMockRecips[mMockRecips.length - 8], thirdStart, thirdEnd, 0);
298        mEditable.setSpan(mMockRecips[mMockRecips.length - 7], fourthStart, fourthEnd, 0);
299        mEditable.setSpan(mMockRecips[mMockRecips.length - 6], fifthStart, fifthEnd, 0);
300        mEditable.setSpan(mMockRecips[mMockRecips.length - 5], sixthStart, sixthEnd, 0);
301        mEditable.setSpan(mMockRecips[mMockRecips.length - 4], seventhStart, seventhEnd, 0);
302        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], eighthStart, eighthEnd, 0);
303        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], ninthStart, ninthEnd, 0);
304        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], tenthStart, tenthEnd, 0);
305
306        view.createMoreChip();
307        assertEquals(mEditable.toString(), first + second + third + fourth + fifth + sixth
308                + seventh + eigth + ninth + tenth);
309        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 10]), firstStart);
310        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 9]), secondStart);
311        // Find the more chip.
312        ImageSpan moreChip = view.getMoreChip();
313        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
314        assertEquals(mEditable.getSpanEnd(moreChip), tenthEnd);
315
316        view.removeMoreChip();
317        assertEquals(mEditable.toString(), first + second + third + fourth + fifth + sixth
318                + seventh + eigth + ninth + tenth);
319        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 10]), firstStart);
320        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 9]), secondStart);
321
322        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 8]), thirdStart);
323        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 7]), fourthStart);
324        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 6]), fifthStart);
325        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 5]), sixthStart);
326        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 4]), seventhStart);
327        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), eighthStart);
328        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), ninthStart);
329        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), tenthStart);
330        moreChip = view.getMoreChip();
331        assertEquals(mEditable.getSpanStart(moreChip), -1);
332
333    }
334
335    public void testMoreChipSpecialChars() {
336        // Make sure the more chip correctly handles extra tokenizer characters in the middle
337        // of chip text.
338        populateMocks(3);
339        MockRecipientEditTextView view = createViewForTesting();
340        view.setMoreItem(createTestMoreItem());
341        String first = (String) mTokenizer.terminateToken("FI,RST");
342        String second = (String) mTokenizer.terminateToken("SE,COND");
343        String third = (String) mTokenizer.terminateToken("THI,RD");
344        mEditable = new SpannableStringBuilder();
345        mEditable.append(first+second+third);
346
347        int firstStart = mEditable.toString().indexOf(first);
348        int firstEnd = firstStart + first.trim().length();
349        int secondStart = mEditable.toString().indexOf(second);
350        int secondEnd = secondStart + second.trim().length();
351        int thirdStart = mEditable.toString().indexOf(third);
352        int thirdEnd = thirdStart + third.trim().length();
353        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
354        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
355        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
356
357        view.createMoreChip();
358        assertEquals(mEditable.toString(), first+second+third);
359        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
360        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
361        // Find the more chip.
362        ImageSpan moreChip = view.getMoreChip();
363        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
364        assertEquals(mEditable.getSpanEnd(moreChip), thirdEnd);
365
366        view.removeMoreChip();
367        assertEquals(mEditable.toString(), first+second+third);
368        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
369        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
370        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
371        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdStart);
372        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdEnd);
373        moreChip = view.getMoreChip();
374        assertEquals(mEditable.getSpanStart(moreChip), -1);
375    }
376
377    public void testMoreChipDupes() {
378        // Make sure the more chip is correctly added and removed when we have duplicate chips.
379        populateMocks(4);
380        MockRecipientEditTextView view = createViewForTesting();
381        view.setMoreItem(createTestMoreItem());
382        String first = (String) mTokenizer.terminateToken("FIRST");
383        String second = (String) mTokenizer.terminateToken("SECOND");
384        String third = (String) mTokenizer.terminateToken("THIRD");
385        mEditable = new SpannableStringBuilder();
386        mEditable.append(first+second+third+third);
387
388        int firstStart = mEditable.toString().indexOf(first);
389        int firstEnd = firstStart + first.trim().length();
390        int secondStart = mEditable.toString().indexOf(second);
391        int secondEnd = secondStart + second.trim().length();
392        int thirdStart = mEditable.toString().indexOf(third);
393        int thirdEnd = thirdStart + third.trim().length();
394        int thirdNextStart = mEditable.toString().indexOf(third, thirdEnd);
395        int thirdNextEnd = thirdNextStart + third.trim().length();
396        mEditable.setSpan(mMockRecips[mMockRecips.length - 4], firstStart, firstEnd, 0);
397        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], secondStart, secondEnd, 0);
398        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], thirdStart, thirdEnd, 0);
399        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdNextStart, thirdNextEnd, 0);
400
401        view.createMoreChip();
402        assertEquals(mEditable.toString(), first+second+third+third);
403        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 4]), firstStart);
404        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), secondStart);
405        // Find the more chip.
406        ImageSpan moreChip = view.getMoreChip();
407        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
408        assertEquals(mEditable.getSpanEnd(moreChip), thirdNextEnd);
409
410        view.removeMoreChip();
411        assertEquals(mEditable.toString(), first+second+third+third);
412        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 4]), firstStart);
413        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 4]), firstEnd);
414        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), secondStart);
415        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), thirdStart);
416        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), thirdEnd);
417        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdNextStart);
418        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdNextEnd);
419        moreChip = view.getMoreChip();
420        assertEquals(mEditable.getSpanStart(moreChip), -1);
421    }
422
423    public void testRemoveChip() {
424        // Create 3 chips to start and test removing chips in various postions.
425        populateMocks(3);
426        MockRecipientEditTextView view = createViewForTesting();
427        view.setMoreItem(createTestMoreItem());
428        String first = (String) mTokenizer.terminateToken("FIRST");
429        String second = (String) mTokenizer.terminateToken("SECOND");
430        String third = (String) mTokenizer.terminateToken("THIRD");
431        mEditable = new SpannableStringBuilder();
432        mEditable.append(first + second + third);
433
434        int firstStart = mEditable.toString().indexOf(first);
435        int firstEnd = firstStart + first.length();
436        int secondStart = mEditable.toString().indexOf(second);
437        int secondEnd = secondStart + second.length();
438        int thirdStart = mEditable.toString().indexOf(third);
439        int thirdEnd = thirdStart + third.length();
440        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
441        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
442        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
443        assertEquals(mEditable.toString(), first + second + third);
444        // Test removing the middle chip.
445        view.removeChip(mMockRecips[mMockRecips.length - 2]);
446        assertEquals(mEditable.toString(), first + third);
447        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
448        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
449        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), -1);
450        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), -1);
451        int newThirdStart = mEditable.toString().indexOf(third);
452        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), newThirdStart);
453        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), newThirdStart
454                + third.length());
455
456        // Test removing the first chip.
457        populateMocks(3);
458        view = createViewForTesting();
459        view.setMoreItem(createTestMoreItem());
460        mEditable = new SpannableStringBuilder();
461        mEditable.append(first + second + third);
462
463        firstStart = mEditable.toString().indexOf(first);
464        firstEnd = firstStart + first.length();
465        secondStart = mEditable.toString().indexOf(second);
466        secondEnd = secondStart + second.length();
467        thirdStart = mEditable.toString().indexOf(third);
468        thirdEnd = thirdStart + third.length();
469        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
470        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
471        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
472        assertEquals(mEditable.toString(), first + second + third);
473        view.removeChip(mMockRecips[mMockRecips.length - 3]);
474        assertEquals(mEditable.toString(), second + third);
475        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), -1);
476        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), -1);
477        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), 0);
478        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), second.length());
479        newThirdStart = mEditable.toString().indexOf(third);
480        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), newThirdStart);
481        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), newThirdStart
482                + third.length());
483
484        // Test removing the last chip.
485        populateMocks(3);
486        view = createViewForTesting();
487        view.setMoreItem(createTestMoreItem());
488        mEditable = new SpannableStringBuilder();
489        mEditable.append(first + second + third);
490
491        firstStart = mEditable.toString().indexOf(first);
492        firstEnd = firstStart + first.length();
493        secondStart = mEditable.toString().indexOf(second);
494        secondEnd = secondStart + second.length();
495        thirdStart = mEditable.toString().indexOf(third);
496        thirdEnd = thirdStart + third.length();
497        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
498        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
499        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
500        assertEquals(mEditable.toString(), first + second + third);
501        view.removeChip(mMockRecips[mMockRecips.length - 1]);
502        assertEquals(mEditable.toString(), first + second);
503        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
504        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
505        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
506        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), secondEnd);
507        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), -1);
508        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), -1);
509    }
510
511    public void testReplaceChip() {
512        populateMocks(3);
513        MockRecipientEditTextView view = createViewForTesting();
514        view.setMoreItem(createTestMoreItem());
515        view.setChipBackground(createChipBackground());
516        view.setChipHeight(48);
517        String first = (String) mTokenizer.terminateToken("FIRST");
518        String second = (String) mTokenizer.terminateToken("SECOND");
519        String third = (String) mTokenizer.terminateToken("THIRD");
520        mEditable = new SpannableStringBuilder();
521        mEditable.append(first + second + third);
522
523        // Test replacing the first chip with a new chip.
524        int firstStart = mEditable.toString().indexOf(first);
525        int firstEnd = firstStart + first.trim().length();
526        int secondStart = mEditable.toString().indexOf(second);
527        int secondEnd = secondStart + second.trim().length();
528        int thirdStart = mEditable.toString().indexOf(third);
529        int thirdEnd = thirdStart + third.trim().length();
530        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
531        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
532        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
533        assertEquals(mEditable.toString(), first + second + third);
534        view.replaceChip(mMockRecips[mMockRecips.length - 3], RecipientEntry
535                .constructGeneratedEntry("replacement", "replacement@replacement.com"));
536        assertEquals(mEditable.toString(), mTokenizer
537                .terminateToken("replacement <replacement@replacement.com>")
538                + second + third);
539        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), -1);
540        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), -1);
541        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), mEditable
542                .toString().indexOf(second));
543        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), mEditable
544                .toString().indexOf(second)
545                + second.trim().length());
546        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), mEditable
547                .toString().indexOf(third));
548        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), mEditable
549                .toString().indexOf(third)
550                + third.trim().length());
551        RecipientChip[] spans = mEditable.getSpans(0, mEditable.length(), RecipientChip.class);
552        assertEquals(spans.length, 3);
553        spans = mEditable
554                .getSpans(0, mEditable.toString().indexOf(second) - 1, RecipientChip.class);
555        assertEquals((String) spans[0].getDisplay(), "replacement");
556
557
558        // Test replacing the middle chip with a new chip.
559        mEditable = new SpannableStringBuilder();
560        mEditable.append(first + second + third);
561        firstStart = mEditable.toString().indexOf(first);
562        firstEnd = firstStart + first.trim().length();
563        secondStart = mEditable.toString().indexOf(second);
564        secondEnd = secondStart + second.trim().length();
565        thirdStart = mEditable.toString().indexOf(third);
566        thirdEnd = thirdStart + third.trim().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.replaceChip(mMockRecips[mMockRecips.length - 2], RecipientEntry
572                .constructGeneratedEntry("replacement", "replacement@replacement.com"));
573        assertEquals(mEditable.toString(), first + mTokenizer
574                .terminateToken("replacement <replacement@replacement.com>") + third);
575        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
576        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
577        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), -1);
578        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), -1);
579        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), mEditable
580                .toString().indexOf(third));
581        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), mEditable
582                .toString().indexOf(third)
583                + third.trim().length());
584        spans = mEditable.getSpans(0, mEditable.length(), RecipientChip.class);
585        assertEquals(spans.length, 3);
586        spans = mEditable
587                .getSpans(firstEnd, mEditable.toString().indexOf(third) - 1, RecipientChip.class);
588        assertEquals((String) spans[0].getDisplay(), "replacement");
589
590
591        // Test replacing the last chip with a new chip.
592        mEditable = new SpannableStringBuilder();
593        mEditable.append(first + second + third);
594        firstStart = mEditable.toString().indexOf(first);
595        firstEnd = firstStart + first.trim().length();
596        secondStart = mEditable.toString().indexOf(second);
597        secondEnd = secondStart + second.trim().length();
598        thirdStart = mEditable.toString().indexOf(third);
599        thirdEnd = thirdStart + third.trim().length();
600        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
601        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
602        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
603        assertEquals(mEditable.toString(), first + second + third);
604        view.replaceChip(mMockRecips[mMockRecips.length - 1], RecipientEntry
605                .constructGeneratedEntry("replacement", "replacement@replacement.com"));
606        assertEquals(mEditable.toString(), first + second + mTokenizer
607                .terminateToken("replacement <replacement@replacement.com>"));
608        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
609        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
610        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
611        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), secondEnd);
612        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), -1);
613        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), -1);
614        spans = mEditable.getSpans(0, mEditable.length(), RecipientChip.class);
615        assertEquals(spans.length, 3);
616        spans = mEditable
617                .getSpans(secondEnd, mEditable.length(), RecipientChip.class);
618        assertEquals((String) spans[0].getDisplay(), "replacement");
619    }
620
621    public void testHandlePaste() {
622        // Start with an empty edit field.
623        // Add an address; the text should be left as is.
624        MockRecipientEditTextView view = createViewForTesting();
625        view.setMoreItem(createTestMoreItem());
626        view.setChipBackground(createChipBackground());
627        view.setChipHeight(48);
628        mEditable = new SpannableStringBuilder();
629        mEditable.append("user@user.com");
630        view.setSelection(mEditable.length());
631        view.handlePaste();
632        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length, 0);
633        assertEquals(mEditable.toString(), "user@user.com");
634
635        // Test adding a single address to an empty chips field with a space at
636        // the end of it. The address should stay as text.
637        mEditable = new SpannableStringBuilder();
638        String tokenizedUser = "user@user.com" + " ";
639        mEditable.append(tokenizedUser);
640        view.setSelection(mEditable.length());
641        view.handlePaste();
642        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length, 0);
643        assertEquals(mEditable.toString(), tokenizedUser);
644
645        // Test adding a single address to an empty chips field with a semicolon at
646        // the end of it. The address should become a chip
647        mEditable = new SpannableStringBuilder();
648        tokenizedUser = "user@user.com;";
649        mEditable.append(tokenizedUser);
650        view.setSelection(mEditable.length());
651        view.handlePaste();
652        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length, 1);
653
654        // Test adding 2 address to an empty chips field. The second to last
655        // address should become a chip and the last address should stay as
656        // text.
657        mEditable = new SpannableStringBuilder();
658        mEditable.append("user1,user2@user.com");
659        view.setSelection(mEditable.length());
660        view.handlePaste();
661        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length, 1);
662        assertEquals(mEditable.getSpans(0, mEditable.toString().indexOf("user2@user.com"),
663                RecipientChip.class).length, 1);
664        assertEquals(mEditable.toString(), "<user1>, user2@user.com");
665
666        // Test adding a single address to the end of existing chips. The existing
667        // chips should remain, and the last address should stay as text.
668        populateMocks(3);
669        String first = (String) mTokenizer.terminateToken("FIRST");
670        String second = (String) mTokenizer.terminateToken("SECOND");
671        String third = (String) mTokenizer.terminateToken("THIRD");
672        mEditable = new SpannableStringBuilder();
673        mEditable.append(first + second + third);
674        view.setSelection(mEditable.length());
675        int firstStart = mEditable.toString().indexOf(first);
676        int firstEnd = firstStart + first.trim().length();
677        int secondStart = mEditable.toString().indexOf(second);
678        int secondEnd = secondStart + second.trim().length();
679        int thirdStart = mEditable.toString().indexOf(third);
680        int thirdEnd = thirdStart + third.trim().length();
681        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
682        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
683        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
684
685        mEditable.append("user@user.com");
686        view.setSelection(mEditable.length());
687        view.handlePaste();
688        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length,
689                mMockRecips.length);
690        assertEquals(mEditable.toString(), first + second + third + "user@user.com");
691
692        // Paste 2 addresses after existing chips. We expect the first address to be turned into
693        // a chip and the second to be left as text.
694        populateMocks(3);
695        mEditable = new SpannableStringBuilder();
696        mEditable.append(first + second + third);
697
698        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
699        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
700        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
701
702        mEditable.append("user1, user2@user.com");
703        view.setSelection(mEditable.length());
704        view.handlePaste();
705        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length,
706                mMockRecips.length + 1);
707        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("<user1>"), mEditable
708                .toString().indexOf("user2@user.com") - 1, RecipientChip.class).length, 1);
709        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("user2@user.com"), mEditable
710                .length(), RecipientChip.class).length, 0);
711        assertEquals(mEditable.toString(), first + second + third + "<user1>, user2@user.com");
712
713        // Paste 2 addresses after existing chips. We expect the first address to be turned into
714        // a chip and the second to be left as text. This removes the space seperator char between
715        // addresses.
716        populateMocks(3);
717        mEditable = new SpannableStringBuilder();
718        mEditable.append(first + second + third);
719
720        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
721        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
722        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
723
724        mEditable.append("user1,user2@user.com");
725        view.setSelection(mEditable.length());
726        view.handlePaste();
727        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length,
728                mMockRecips.length + 1);
729        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("<user1>"), mEditable
730                .toString().indexOf("user2@user.com") - 1, RecipientChip.class).length, 1);
731        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("user2@user.com"), mEditable
732                .length(), RecipientChip.class).length, 0);
733        assertEquals(mEditable.toString(), first + second + third + "<user1>, user2@user.com");
734
735        // Test a complete token pasted in at the end. It should be turned into a chip.
736        mEditable = new SpannableStringBuilder();
737        mEditable.append("user1, user2@user.com,");
738        view.setSelection(mEditable.length());
739        view.handlePaste();
740        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length, 2);
741        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("<user1>"), mEditable
742                .toString().indexOf("user2@user.com") - 1, RecipientChip.class).length, 1);
743        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("user2@user.com"), mEditable
744                .length(), RecipientChip.class).length, 1);
745        assertEquals(mEditable.toString(), "<user1>, <user2@user.com>, ");
746    }
747
748    public void testGetPastTerminators() {
749        MockRecipientEditTextView view = createViewForTesting();
750        view.setMoreItem(createTestMoreItem());
751        view.setChipBackground(createChipBackground());
752        view.setChipHeight(48);
753        String test = "test";
754        mEditable = new SpannableStringBuilder();
755        mEditable.append(test);
756        assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
757                test.length());
758
759        test = "test,";
760        mEditable = new SpannableStringBuilder();
761        mEditable.append(test);
762        assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
763                test.length());
764
765        test = "test, ";
766        mEditable = new SpannableStringBuilder();
767        mEditable.append(test);
768        assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
769                test.length());
770
771        test = "test;";
772        mEditable = new SpannableStringBuilder();
773        mEditable.append(test);
774        assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
775                test.length());
776
777        test = "test; ";
778        mEditable = new SpannableStringBuilder();
779        mEditable.append(test);
780        assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
781                test.length());
782    }
783
784    public void testIsCompletedToken() {
785        MockRecipientEditTextView view = createViewForTesting();
786        view.setMoreItem(createTestMoreItem());
787        view.setChipBackground(createChipBackground());
788        view.setChipHeight(48);
789        assertTrue(view.isCompletedToken("test;"));
790        assertTrue(view.isCompletedToken("test,"));
791        assertFalse(view.isCompletedToken("test"));
792        assertFalse(view.isCompletedToken("test "));
793    }
794
795    public void testGetLastChip() {
796        populateMocks(3);
797        MockRecipientEditTextView view = createViewForTesting();
798        view.setMoreItem(createTestMoreItem());
799        view.setChipBackground(createChipBackground());
800        view.setChipHeight(48);
801        String first = (String) mTokenizer.terminateToken("FIRST");
802        String second = (String) mTokenizer.terminateToken("SECOND");
803        String third = (String) mTokenizer.terminateToken("THIRD");
804        mEditable = new SpannableStringBuilder();
805        mEditable.append(first + second + third);
806
807        // Test replacing the first chip with a new chip.
808        int firstStart = mEditable.toString().indexOf(first);
809        int firstEnd = firstStart + first.trim().length();
810        int secondStart = mEditable.toString().indexOf(second);
811        int secondEnd = secondStart + second.trim().length();
812        int thirdStart = mEditable.toString().indexOf(third);
813        int thirdEnd = thirdStart + third.trim().length();
814        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
815        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
816        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
817        assertEquals(view.getLastChip(), mMockRecips[mMockRecips.length - 1]);
818        mEditable.append("extra");
819        assertEquals(view.getLastChip(), mMockRecips[mMockRecips.length - 1]);
820    }
821
822    private Drawable createChipBackground() {
823        Bitmap drawable = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
824        return new BitmapDrawable(getContext().getResources(), drawable);
825    }
826
827    private TextView createTestMoreItem() {
828        TextView view = new TextView(getContext());
829        view.setText("<xliff:g id='count'>%1$s</xliff:g> more...");
830        return view;
831    }
832
833    private void populateMocks(int size) {
834        mMockEntries = new RecipientEntry[size];
835        for (int i = 0; i < size; i++) {
836            mMockEntries[i] = RecipientEntry.constructGeneratedEntry("user",
837                    "user@username.com");
838        }
839        mMockRecips = new RecipientChip[size];
840        for (int i = 0; i < size; i++) {
841            mMockRecips[i] = new RecipientChip(null, mMockEntries[i], i);
842        }
843    }
844}
845