/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ex.chips;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.Editable;
import android.text.SpannableStringBuilder;
import android.text.style.ImageSpan;
import android.text.util.Rfc822Tokenizer;
import android.widget.TextView;
import com.android.ex.chips.BaseRecipientAdapter;
import com.android.ex.chips.RecipientEditTextView;
import com.android.ex.chips.RecipientEntry;
import com.android.ex.chips.recipientchip.DrawableRecipientChip;
import com.android.ex.chips.recipientchip.VisibleRecipientChip;;
import java.util.regex.Pattern;
@SmallTest
public class ChipsTest extends AndroidTestCase {
private DrawableRecipientChip[] mMockRecips;
private RecipientEntry[] mMockEntries;
private Rfc822Tokenizer mTokenizer;
private Editable mEditable;
class BaseMockRecipientEditTextView extends RecipientEditTextView {
public BaseMockRecipientEditTextView(Context context) {
super(context, null);
mTokenizer = new Rfc822Tokenizer();
setTokenizer(mTokenizer);
}
@Override
public DrawableRecipientChip[] getSortedRecipients() {
return mMockRecips;
}
@Override
public int getLineHeight() {
return 48;
}
@Override
Drawable getChipBackground(RecipientEntry contact) {
return createChipBackground();
}
@Override
public int getViewWidth() {
return 100;
}
}
class MockRecipientEditTextView extends BaseMockRecipientEditTextView {
public MockRecipientEditTextView(Context context) {
super(context);
mTokenizer = new Rfc822Tokenizer();
setTokenizer(mTokenizer);
}
@Override
public DrawableRecipientChip[] getSortedRecipients() {
return mMockRecips;
}
@Override
public Editable getText() {
return mEditable;
}
@Override
public Editable getSpannable() {
return mEditable;
}
@Override
public int getLineHeight() {
return 48;
}
@Override
Drawable getChipBackground(RecipientEntry contact) {
return createChipBackground();
}
@Override
public int length() {
return mEditable != null ? mEditable.length() : 0;
}
@Override
public String toString() {
return mEditable != null ? mEditable.toString() : "";
}
@Override
public int getViewWidth() {
return 100;
}
}
private class TestBaseRecipientAdapter extends BaseRecipientAdapter {
public TestBaseRecipientAdapter(final Context context) {
super(context);
}
public TestBaseRecipientAdapter(final Context context, final int preferredMaxResultCount,
final int queryMode) {
super(context, preferredMaxResultCount, queryMode);
}
}
private MockRecipientEditTextView createViewForTesting() {
mEditable = new SpannableStringBuilder();
MockRecipientEditTextView view = new MockRecipientEditTextView(getContext());
view.setAdapter(new TestBaseRecipientAdapter(getContext()));
return view;
}
public void testCreateDisplayText() {
RecipientEditTextView view = createViewForTesting();
RecipientEntry entry = RecipientEntry.constructGeneratedEntry("User Name, Jr",
"user@username.com", true);
String testAddress = view.createAddressText(entry);
String testDisplay = view.createChipDisplayText(entry);
assertEquals("Expected a properly formatted RFC email address",
"\"User Name, Jr\" , ", testAddress);
assertEquals("Expected a displayable name", "User Name, Jr", testDisplay);
RecipientEntry alreadyFormatted =
RecipientEntry.constructFakeEntry("user@username.com, ", true);
testAddress = view.createAddressText(alreadyFormatted);
testDisplay = view.createChipDisplayText(alreadyFormatted);
assertEquals("Expected a properly formatted RFC email address", ", ",
testAddress);
assertEquals("Expected a displayable name", "user@username.com", testDisplay);
RecipientEntry alreadyFormattedNoSpace = RecipientEntry
.constructFakeEntry("user@username.com,", true);
testAddress = view.createAddressText(alreadyFormattedNoSpace);
assertEquals("Expected a properly formatted RFC email address", ", ",
testAddress);
RecipientEntry alreadyNamed = RecipientEntry.constructGeneratedEntry("User Name",
"\"User Name, Jr\" ", true);
testAddress = view.createAddressText(alreadyNamed);
testDisplay = view.createChipDisplayText(alreadyNamed);
assertEquals(
"Expected address that used the name not the excess address name",
"User Name , ", testAddress);
assertEquals("Expected a displayable name", "User Name", testDisplay);
}
public void testSanitizeBetween() {
// First, add 2 chips and then make sure we remove
// the extra content between them correctly.
populateMocks(2);
MockRecipientEditTextView view = createViewForTesting();
String first = (String) mTokenizer.terminateToken("FIRST");
String second = (String) mTokenizer.terminateToken("SECOND");
String extra = "EXTRA";
mEditable = new SpannableStringBuilder();
mEditable.append(first + extra + second);
int firstStart = mEditable.toString().indexOf(first);
int firstEnd = firstStart + first.trim().length();
int secondStart = mEditable.toString().indexOf(second);
int secondEnd = secondStart + second.trim().length();
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], secondStart, secondEnd, 0);
view.sanitizeBetween();
String editableString = mEditable.toString();
assertEquals(editableString.indexOf(extra), -1);
assertEquals(editableString.indexOf(first), firstStart);
assertEquals(editableString.indexOf(second), secondStart - extra.length());
assertEquals(editableString, (first + second));
// Add 1 chip and make sure that we remove the extra stuff before it correctly.
mEditable = new SpannableStringBuilder();
populateMocks(1);
mEditable.append(extra);
mEditable.append(first);
firstStart = mEditable.toString().indexOf(first);
firstEnd = firstStart + first.length();
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], firstStart, firstEnd, 0);
view.sanitizeBetween();
assertEquals(mEditable.toString(), first);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), firstStart
- extra.length());
}
public void testSanitizeEnd() {
// First, add 2 chips and then make sure we remove
// the extra content between them correctly.
populateMocks(2);
MockRecipientEditTextView view = createViewForTesting();
String first = (String) mTokenizer.terminateToken("FIRST");
String second = (String) mTokenizer.terminateToken("SECOND");
String extra = "EXTRA";
mEditable = new SpannableStringBuilder();
mEditable.append(first + second);
int firstStart = mEditable.toString().indexOf(first);
int firstEnd = firstStart + first.trim().length();
int secondStart = mEditable.toString().indexOf(second);
int secondEnd = secondStart + second.trim().length();
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], secondStart, secondEnd, 0);
view.sanitizeEnd();
String editableString = mEditable.toString();
assertEquals(editableString.indexOf(extra), -1);
assertEquals(editableString.indexOf(first), firstStart);
assertEquals(editableString.indexOf(second), secondStart);
assertEquals(editableString, (first + second));
mEditable.append(extra);
editableString = mEditable.toString();
assertEquals(mEditable.toString(), (first + second + extra));
view.sanitizeEnd();
assertEquals(mEditable.toString(), (first + second));
}
public void testMoreChipPlainText() {
MockRecipientEditTextView view = createViewForTesting();
view.setMoreItem(createTestMoreItem());
String first = (String) mTokenizer.terminateToken("FIRST");
String second = (String) mTokenizer.terminateToken("SECOND");
String third = (String) mTokenizer.terminateToken("THIRD");
mEditable = new SpannableStringBuilder();
mEditable.append(first+second+third);
int thirdStart = mEditable.toString().indexOf(third);
int thirdEnd = thirdStart + third.trim().length();
view.createMoreChipPlainText();
ImageSpan moreChip = view.getMoreChip();
assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
assertEquals(mEditable.getSpanEnd(moreChip), thirdEnd + 1);
}
public void testCountTokens() {
MockRecipientEditTextView view = createViewForTesting();
view.setMoreItem(createTestMoreItem());
String first = (String) mTokenizer.terminateToken("FIRST");
String second = (String) mTokenizer.terminateToken("SECOND");
String third = (String) mTokenizer.terminateToken("THIRD");
String fourth = "FOURTH,";
String fifth = "FIFTH,";
mEditable = new SpannableStringBuilder();
mEditable.append(first+second+third+fourth+fifth);
assertEquals(view.countTokens(mEditable), 5);
}
public void testTooManyRecips() {
BaseMockRecipientEditTextView view = new BaseMockRecipientEditTextView(getContext());
view.setMoreItem(createTestMoreItem());
for (int i = 0; i < 100; i++) {
view.append(mTokenizer.terminateToken(i + ""));
}
assertEquals(view.countTokens(view.getText()), 100);
view.handlePendingChips();
view.createMoreChip();
ImageSpan moreChip = view.getMoreChip();
// We show 2 chips then place a more chip.
int secondStart = view.getText().toString().indexOf(
(String) mTokenizer.terminateToken(RecipientEditTextView.CHIP_LIMIT + ""));
assertEquals(view.getText().getSpanStart(moreChip), secondStart);
assertEquals(view.getText().getSpanEnd(moreChip), view.length());
assertEquals(view.getSortedRecipients(), null);
}
public void testMoreChip() {
// Add 3 chips: this is the trigger point at which the more chip will be created.
// Test that adding the chips and then creating and removing the more chip, as if
// the user were focusing/ removing focus from the chips field.
populateMocks(3);
MockRecipientEditTextView view = createViewForTesting();
view.setMoreItem(createTestMoreItem());
String first = (String) mTokenizer.terminateToken("FIRST");
String second = (String) mTokenizer.terminateToken("SECOND");
String third = (String) mTokenizer.terminateToken("THIRD");
mEditable = new SpannableStringBuilder();
mEditable.append(first+second+third);
int firstStart = mEditable.toString().indexOf(first);
int firstEnd = firstStart + first.trim().length();
int secondStart = mEditable.toString().indexOf(second);
int secondEnd = secondStart + second.trim().length();
int thirdStart = mEditable.toString().indexOf(third);
int thirdEnd = thirdStart + third.trim().length();
mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
view.createMoreChip();
assertEquals(mEditable.toString(), first+second+third);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
// Find the more chip.
ImageSpan moreChip = view.getMoreChip();
assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
assertEquals(mEditable.getSpanEnd(moreChip), thirdEnd + 1);
view.removeMoreChip();
assertEquals(mEditable.toString(), first+second+third);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdEnd);
moreChip = view.getMoreChip();
assertEquals(mEditable.getSpanStart(moreChip), -1);
// Rinse and repeat, just in case!
view.createMoreChip();
assertEquals(mEditable.toString(), first+second+third);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
// Find the more chip.
moreChip = view.getMoreChip();
assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
assertEquals(mEditable.getSpanEnd(moreChip), thirdEnd + 1);
view.removeMoreChip();
assertEquals(mEditable.toString(), first+second+third);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdEnd);
moreChip = view.getMoreChip();
assertEquals(mEditable.getSpanStart(moreChip), -1);
}
public void testMoreChipLotsOfUsers() {
// Test adding and removing the more chip in the case where we have a lot of users.
populateMocks(10);
MockRecipientEditTextView view = createViewForTesting();
view.setMoreItem(createTestMoreItem());
String first = (String) mTokenizer.terminateToken("FIRST");
String second = (String) mTokenizer.terminateToken("SECOND");
String third = (String) mTokenizer.terminateToken("THIRD");
String fourth = (String) mTokenizer.terminateToken("FOURTH");
String fifth = (String) mTokenizer.terminateToken("FIFTH");
String sixth = (String) mTokenizer.terminateToken("SIXTH");
String seventh = (String) mTokenizer.terminateToken("SEVENTH");
String eigth = (String) mTokenizer.terminateToken("EIGHTH");
String ninth = (String) mTokenizer.terminateToken("NINTH");
String tenth = (String) mTokenizer.terminateToken("TENTH");
mEditable = new SpannableStringBuilder();
mEditable.append(first+second+third+fourth+fifth+sixth+seventh+eigth+ninth+tenth);
int firstStart = mEditable.toString().indexOf(first);
int firstEnd = firstStart + first.trim().length();
int secondStart = mEditable.toString().indexOf(second);
int secondEnd = secondStart + second.trim().length();
int thirdStart = mEditable.toString().indexOf(third);
int thirdEnd = thirdStart + third.trim().length();
int fourthStart = mEditable.toString().indexOf(fourth);
int fourthEnd = fourthStart + fourth.trim().length();
int fifthStart = mEditable.toString().indexOf(fifth);
int fifthEnd = fifthStart + fifth.trim().length();
int sixthStart = mEditable.toString().indexOf(sixth);
int sixthEnd = sixthStart + sixth.trim().length();
int seventhStart = mEditable.toString().indexOf(seventh);
int seventhEnd = seventhStart + seventh.trim().length();
int eighthStart = mEditable.toString().indexOf(eigth);
int eighthEnd = eighthStart + eigth.trim().length();
int ninthStart = mEditable.toString().indexOf(ninth);
int ninthEnd = ninthStart + ninth.trim().length();
int tenthStart = mEditable.toString().indexOf(tenth);
int tenthEnd = tenthStart + tenth.trim().length();
mEditable.setSpan(mMockRecips[mMockRecips.length - 10], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 9], secondStart, secondEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 8], thirdStart, thirdEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 7], fourthStart, fourthEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 6], fifthStart, fifthEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 5], sixthStart, sixthEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 4], seventhStart, seventhEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 3], eighthStart, eighthEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], ninthStart, ninthEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], tenthStart, tenthEnd, 0);
view.createMoreChip();
assertEquals(mEditable.toString(), first + second + third + fourth + fifth + sixth
+ seventh + eigth + ninth + tenth);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 10]), firstStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 9]), secondStart);
// Find the more chip.
ImageSpan moreChip = view.getMoreChip();
assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
assertEquals(mEditable.getSpanEnd(moreChip), tenthEnd + 1);
view.removeMoreChip();
assertEquals(mEditable.toString(), first + second + third + fourth + fifth + sixth
+ seventh + eigth + ninth + tenth);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 10]), firstStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 9]), secondStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 8]), thirdStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 7]), fourthStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 6]), fifthStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 5]), sixthStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 4]), seventhStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), eighthStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), ninthStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), tenthStart);
moreChip = view.getMoreChip();
assertEquals(mEditable.getSpanStart(moreChip), -1);
}
public void testMoreChipSpecialChars() {
// Make sure the more chip correctly handles extra tokenizer characters in the middle
// of chip text.
populateMocks(3);
MockRecipientEditTextView view = createViewForTesting();
view.setMoreItem(createTestMoreItem());
String first = (String) mTokenizer.terminateToken("FI,RST");
String second = (String) mTokenizer.terminateToken("SE,COND");
String third = (String) mTokenizer.terminateToken("THI,RD");
mEditable = new SpannableStringBuilder();
mEditable.append(first+second+third);
int firstStart = mEditable.toString().indexOf(first);
int firstEnd = firstStart + first.trim().length();
int secondStart = mEditable.toString().indexOf(second);
int secondEnd = secondStart + second.trim().length();
int thirdStart = mEditable.toString().indexOf(third);
int thirdEnd = thirdStart + third.trim().length();
mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
view.createMoreChip();
assertEquals(mEditable.toString(), first+second+third);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
// Find the more chip.
ImageSpan moreChip = view.getMoreChip();
assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
assertEquals(mEditable.getSpanEnd(moreChip), thirdEnd + 1);
view.removeMoreChip();
assertEquals(mEditable.toString(), first+second+third);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdEnd);
moreChip = view.getMoreChip();
assertEquals(mEditable.getSpanStart(moreChip), -1);
}
public void testMoreChipDupes() {
// Make sure the more chip is correctly added and removed when we have duplicate chips.
populateMocks(4);
MockRecipientEditTextView view = createViewForTesting();
view.setMoreItem(createTestMoreItem());
String first = (String) mTokenizer.terminateToken("FIRST");
String second = (String) mTokenizer.terminateToken("SECOND");
String third = (String) mTokenizer.terminateToken("THIRD");
mEditable = new SpannableStringBuilder();
mEditable.append(first+second+third+third);
int firstStart = mEditable.toString().indexOf(first);
int firstEnd = firstStart + first.trim().length();
int secondStart = mEditable.toString().indexOf(second);
int secondEnd = secondStart + second.trim().length();
int thirdStart = mEditable.toString().indexOf(third);
int thirdEnd = thirdStart + third.trim().length();
int thirdNextStart = mEditable.toString().indexOf(third, thirdEnd);
int thirdNextEnd = thirdNextStart + third.trim().length();
mEditable.setSpan(mMockRecips[mMockRecips.length - 4], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 3], secondStart, secondEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], thirdStart, thirdEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdNextStart, thirdNextEnd, 0);
view.createMoreChip();
assertEquals(mEditable.toString(), first+second+third+third);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 4]), firstStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), secondStart);
// Find the more chip.
ImageSpan moreChip = view.getMoreChip();
assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
assertEquals(mEditable.getSpanEnd(moreChip), thirdNextEnd + 1);
view.removeMoreChip();
assertEquals(mEditable.toString(), first+second+third+third);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 4]), firstStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 4]), firstEnd);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), secondStart);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), thirdStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), thirdEnd);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdNextStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdNextEnd);
moreChip = view.getMoreChip();
assertEquals(mEditable.getSpanStart(moreChip), -1);
}
public void testRemoveChip() {
// Create 3 chips to start and test removing chips in various postions.
populateMocks(3);
MockRecipientEditTextView view = createViewForTesting();
view.setMoreItem(createTestMoreItem());
String first = (String) mTokenizer.terminateToken("FIRST");
String second = (String) mTokenizer.terminateToken("SECOND");
String third = (String) mTokenizer.terminateToken("THIRD");
mEditable = new SpannableStringBuilder();
mEditable.append(first + second + third);
int firstStart = mEditable.toString().indexOf(first);
int firstEnd = firstStart + first.length();
int secondStart = mEditable.toString().indexOf(second);
int secondEnd = secondStart + second.length();
int thirdStart = mEditable.toString().indexOf(third);
int thirdEnd = thirdStart + third.length();
mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
assertEquals(mEditable.toString(), first + second + third);
// Test removing the middle chip.
view.removeChip(mMockRecips[mMockRecips.length - 2]);
assertEquals(mEditable.toString(), first + third);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), -1);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), -1);
int newThirdStart = mEditable.toString().indexOf(third);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), newThirdStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), newThirdStart
+ third.length());
// Test removing the first chip.
populateMocks(3);
view = createViewForTesting();
view.setMoreItem(createTestMoreItem());
mEditable = new SpannableStringBuilder();
mEditable.append(first + second + third);
firstStart = mEditable.toString().indexOf(first);
firstEnd = firstStart + first.length();
secondStart = mEditable.toString().indexOf(second);
secondEnd = secondStart + second.length();
thirdStart = mEditable.toString().indexOf(third);
thirdEnd = thirdStart + third.length();
mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
assertEquals(mEditable.toString(), first + second + third);
view.removeChip(mMockRecips[mMockRecips.length - 3]);
assertEquals(mEditable.toString(), second + third);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), -1);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), -1);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), 0);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), second.length());
newThirdStart = mEditable.toString().indexOf(third);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), newThirdStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), newThirdStart
+ third.length());
// Test removing the last chip.
populateMocks(3);
view = createViewForTesting();
view.setMoreItem(createTestMoreItem());
mEditable = new SpannableStringBuilder();
mEditable.append(first + second + third);
firstStart = mEditable.toString().indexOf(first);
firstEnd = firstStart + first.length();
secondStart = mEditable.toString().indexOf(second);
secondEnd = secondStart + second.length();
thirdStart = mEditable.toString().indexOf(third);
thirdEnd = thirdStart + third.length();
mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
assertEquals(mEditable.toString(), first + second + third);
view.removeChip(mMockRecips[mMockRecips.length - 1]);
assertEquals(mEditable.toString(), first + second);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), secondEnd);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), -1);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), -1);
}
public void testReplaceChip() {
populateMocks(3);
MockRecipientEditTextView view = createViewForTesting();
view.setMoreItem(createTestMoreItem());
view.setChipBackground(createChipBackground());
view.setChipHeight(48);
String first = (String) mTokenizer.terminateToken("FIRST");
String second = (String) mTokenizer.terminateToken("SECOND");
String third = (String) mTokenizer.terminateToken("THIRD");
mEditable = new SpannableStringBuilder();
mEditable.append(first + second + third);
// Test replacing the first chip with a new chip.
int firstStart = mEditable.toString().indexOf(first);
int firstEnd = firstStart + first.trim().length();
int secondStart = mEditable.toString().indexOf(second);
int secondEnd = secondStart + second.trim().length();
int thirdStart = mEditable.toString().indexOf(third);
int thirdEnd = thirdStart + third.trim().length();
mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
assertEquals(mEditable.toString(), first + second + third);
view.replaceChip(mMockRecips[mMockRecips.length - 3], RecipientEntry
.constructGeneratedEntry("replacement", "replacement@replacement.com", true));
assertEquals(mEditable.toString(), mTokenizer
.terminateToken("replacement ")
+ second + third);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), -1);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), -1);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), mEditable
.toString().indexOf(second));
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), mEditable
.toString().indexOf(second)
+ second.trim().length());
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), mEditable
.toString().indexOf(third));
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), mEditable
.toString().indexOf(third)
+ third.trim().length());
DrawableRecipientChip[] spans =
mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class);
assertEquals(spans.length, 3);
spans = mEditable
.getSpans(0, mEditable.toString().indexOf(second) - 1, DrawableRecipientChip.class);
assertEquals((String) spans[0].getDisplay(), "replacement");
// Test replacing the middle chip with a new chip.
mEditable = new SpannableStringBuilder();
mEditable.append(first + second + third);
firstStart = mEditable.toString().indexOf(first);
firstEnd = firstStart + first.trim().length();
secondStart = mEditable.toString().indexOf(second);
secondEnd = secondStart + second.trim().length();
thirdStart = mEditable.toString().indexOf(third);
thirdEnd = thirdStart + third.trim().length();
mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
assertEquals(mEditable.toString(), first + second + third);
view.replaceChip(mMockRecips[mMockRecips.length - 2], RecipientEntry
.constructGeneratedEntry("replacement", "replacement@replacement.com", true));
assertEquals(mEditable.toString(), first + mTokenizer
.terminateToken("replacement ") + third);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), -1);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), -1);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), mEditable
.toString().indexOf(third));
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), mEditable
.toString().indexOf(third)
+ third.trim().length());
spans = mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class);
assertEquals(spans.length, 3);
spans = mEditable.getSpans(firstEnd, mEditable.toString().indexOf(third) - 1,
DrawableRecipientChip.class);
assertEquals((String) spans[0].getDisplay(), "replacement");
// Test replacing the last chip with a new chip.
mEditable = new SpannableStringBuilder();
mEditable.append(first + second + third);
firstStart = mEditable.toString().indexOf(first);
firstEnd = firstStart + first.trim().length();
secondStart = mEditable.toString().indexOf(second);
secondEnd = secondStart + second.trim().length();
thirdStart = mEditable.toString().indexOf(third);
thirdEnd = thirdStart + third.trim().length();
mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
assertEquals(mEditable.toString(), first + second + third);
view.replaceChip(mMockRecips[mMockRecips.length - 1], RecipientEntry
.constructGeneratedEntry("replacement", "replacement@replacement.com", true));
assertEquals(mEditable.toString(), first + second + mTokenizer
.terminateToken("replacement "));
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), secondEnd);
assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), -1);
assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), -1);
spans = mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class);
assertEquals(spans.length, 3);
spans = mEditable
.getSpans(secondEnd, mEditable.length(), DrawableRecipientChip.class);
assertEquals((String) spans[0].getDisplay(), "replacement");
}
public void testHandlePaste() {
// Start with an empty edit field.
// Add an address; the text should be left as is.
MockRecipientEditTextView view = createViewForTesting();
view.setMoreItem(createTestMoreItem());
view.setChipBackground(createChipBackground());
view.setChipHeight(48);
mEditable = new SpannableStringBuilder();
mEditable.append("user@user.com");
view.setSelection(mEditable.length());
view.handlePaste();
assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 0);
assertEquals(mEditable.toString(), "user@user.com");
// Test adding a single address to an empty chips field with a space at
// the end of it. The address should stay as text.
mEditable = new SpannableStringBuilder();
String tokenizedUser = "user@user.com" + " ";
mEditable.append(tokenizedUser);
view.setSelection(mEditable.length());
view.handlePaste();
assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 0);
assertEquals(mEditable.toString(), tokenizedUser);
// Test adding a single address to an empty chips field with a semicolon at
// the end of it. The address should become a chip
mEditable = new SpannableStringBuilder();
tokenizedUser = "user@user.com;";
mEditable.append(tokenizedUser);
view.setSelection(mEditable.length());
view.handlePaste();
assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 1);
// Test adding 2 address to an empty chips field. The second to last
// address should become a chip and the last address should stay as
// text.
mEditable = new SpannableStringBuilder();
mEditable.append("user1,user2@user.com");
view.setSelection(mEditable.length());
view.handlePaste();
assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 1);
assertEquals(mEditable.getSpans(0, mEditable.toString().indexOf("user2@user.com"),
DrawableRecipientChip.class).length, 1);
assertEquals(mEditable.toString(), ", user2@user.com");
// Test adding a single address to the end of existing chips. The existing
// chips should remain, and the last address should stay as text.
populateMocks(3);
String first = (String) mTokenizer.terminateToken("FIRST");
String second = (String) mTokenizer.terminateToken("SECOND");
String third = (String) mTokenizer.terminateToken("THIRD");
mEditable = new SpannableStringBuilder();
mEditable.append(first + second + third);
view.setSelection(mEditable.length());
int firstStart = mEditable.toString().indexOf(first);
int firstEnd = firstStart + first.trim().length();
int secondStart = mEditable.toString().indexOf(second);
int secondEnd = secondStart + second.trim().length();
int thirdStart = mEditable.toString().indexOf(third);
int thirdEnd = thirdStart + third.trim().length();
mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
mEditable.append("user@user.com");
view.setSelection(mEditable.length());
view.handlePaste();
assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length,
mMockRecips.length);
assertEquals(mEditable.toString(), first + second + third + "user@user.com");
// Paste 2 addresses after existing chips. We expect the first address to be turned into
// a chip and the second to be left as text.
populateMocks(3);
mEditable = new SpannableStringBuilder();
mEditable.append(first + second + third);
mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
mEditable.append("user1, user2@user.com");
view.setSelection(mEditable.length());
view.handlePaste();
assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length,
mMockRecips.length + 1);
assertEquals(mEditable.getSpans(mEditable.toString().indexOf(""), mEditable
.toString().indexOf("user2@user.com") - 1, DrawableRecipientChip.class).length, 1);
assertEquals(mEditable.getSpans(mEditable.toString().indexOf("user2@user.com"), mEditable
.length(), DrawableRecipientChip.class).length, 0);
assertEquals(mEditable.toString(), first + second + third + ", user2@user.com");
// Paste 2 addresses after existing chips. We expect the first address to be turned into
// a chip and the second to be left as text. This removes the space seperator char between
// addresses.
populateMocks(3);
mEditable = new SpannableStringBuilder();
mEditable.append(first + second + third);
mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
mEditable.append("user1,user2@user.com");
view.setSelection(mEditable.length());
view.handlePaste();
assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length,
mMockRecips.length + 1);
assertEquals(mEditable.getSpans(mEditable.toString().indexOf(""), mEditable
.toString().indexOf("user2@user.com") - 1, DrawableRecipientChip.class).length, 1);
assertEquals(mEditable.getSpans(mEditable.toString().indexOf("user2@user.com"), mEditable
.length(), DrawableRecipientChip.class).length, 0);
assertEquals(mEditable.toString(), first + second + third + ", user2@user.com");
// Test a complete token pasted in at the end. It should be turned into a chip.
mEditable = new SpannableStringBuilder();
mEditable.append("user1, user2@user.com,");
view.setSelection(mEditable.length());
view.handlePaste();
assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 2);
assertEquals(mEditable.getSpans(mEditable.toString().indexOf(""), mEditable
.toString().indexOf("user2@user.com") - 1, DrawableRecipientChip.class).length, 1);
assertEquals(mEditable.getSpans(mEditable.toString().indexOf("user2@user.com"), mEditable
.length(), DrawableRecipientChip.class).length, 1);
assertEquals(mEditable.toString(), ", , ");
}
public void testGetPastTerminators() {
MockRecipientEditTextView view = createViewForTesting();
view.setMoreItem(createTestMoreItem());
view.setChipBackground(createChipBackground());
view.setChipHeight(48);
String test = "test";
mEditable = new SpannableStringBuilder();
mEditable.append(test);
assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
test.length());
test = "test,";
mEditable = new SpannableStringBuilder();
mEditable.append(test);
assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
test.length());
test = "test, ";
mEditable = new SpannableStringBuilder();
mEditable.append(test);
assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
test.length());
test = "test;";
mEditable = new SpannableStringBuilder();
mEditable.append(test);
assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
test.length());
test = "test; ";
mEditable = new SpannableStringBuilder();
mEditable.append(test);
assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
test.length());
}
public void testIsCompletedToken() {
MockRecipientEditTextView view = createViewForTesting();
view.setMoreItem(createTestMoreItem());
view.setChipBackground(createChipBackground());
view.setChipHeight(48);
assertTrue(view.isCompletedToken("test;"));
assertTrue(view.isCompletedToken("test,"));
assertFalse(view.isCompletedToken("test"));
assertFalse(view.isCompletedToken("test "));
}
public void testGetLastChip() {
populateMocks(3);
MockRecipientEditTextView view = createViewForTesting();
view.setMoreItem(createTestMoreItem());
view.setChipBackground(createChipBackground());
view.setChipHeight(48);
String first = (String) mTokenizer.terminateToken("FIRST");
String second = (String) mTokenizer.terminateToken("SECOND");
String third = (String) mTokenizer.terminateToken("THIRD");
mEditable = new SpannableStringBuilder();
mEditable.append(first + second + third);
// Test replacing the first chip with a new chip.
int firstStart = mEditable.toString().indexOf(first);
int firstEnd = firstStart + first.trim().length();
int secondStart = mEditable.toString().indexOf(second);
int secondEnd = secondStart + second.trim().length();
int thirdStart = mEditable.toString().indexOf(third);
int thirdEnd = thirdStart + third.trim().length();
mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
assertEquals(view.getLastChip(), mMockRecips[mMockRecips.length - 1]);
mEditable.append("extra");
assertEquals(view.getLastChip(), mMockRecips[mMockRecips.length - 1]);
}
private Drawable createChipBackground() {
Bitmap drawable = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
return new BitmapDrawable(getContext().getResources(), drawable);
}
private TextView createTestMoreItem() {
TextView view = new TextView(getContext());
view.setText("%1$s more...");
return view;
}
private void populateMocks(int size) {
mMockEntries = new RecipientEntry[size];
for (int i = 0; i < size; i++) {
mMockEntries[i] = RecipientEntry.constructGeneratedEntry("user",
"user@username.com", true);
}
mMockRecips = new DrawableRecipientChip[size];
for (int i = 0; i < size; i++) {
mMockRecips[i] = new VisibleRecipientChip(null, mMockEntries[i]);
}
}
/**
*
* Ensure the original text is always accurate, regardless of the type of email. The original
* text is used to determine where to display the chip span. If this test fails, it means some
* text that should be turned into one whole chip may behave unexpectedly.
*
*
* For example, a bug was seen where
*
*
* "Android User"
*
*
* was converted to
*
*
* Android User [android@example.com]
*
*
* where text inside [] is a chip.
*
*/
public void testCreateReplacementChipOriginalText() {
// Name in quotes + email address
testCreateReplacementChipOriginalText("\"Android User\" ,");
// Name in quotes + email address without brackets
testCreateReplacementChipOriginalText("\"Android User\" android@example.com,");
// Name in quotes
testCreateReplacementChipOriginalText("\"Android User\",");
// Name without quotes + email address
testCreateReplacementChipOriginalText("Android User ,");
// Name without quotes
testCreateReplacementChipOriginalText("Android User,");
// Email address
testCreateReplacementChipOriginalText(",");
// Email address without brackets
testCreateReplacementChipOriginalText("android@example.com,");
}
private void testCreateReplacementChipOriginalText(final String email) {
// No trailing space
attemptCreateReplacementChipOriginalText(email.trim());
// Trailing space
attemptCreateReplacementChipOriginalText(email.trim() + " ");
}
private void attemptCreateReplacementChipOriginalText(final String email) {
final RecipientEditTextView view = new RecipientEditTextView(getContext(), null);
view.setText(email);
view.mPendingChips.add(email);
view.createReplacementChip(0, email.length(), view.getText(), true);
// The "original text" should be the email without the comma or space(s)
assertEquals(email.replaceAll(",\\s*$", ""),
view.mTemporaryRecipients.get(0).getOriginalText().toString().trim());
}
public void testCreateTokenizedEntryForPhone() {
final String phonePattern = "[^\\d]*888[^\\d]*555[^\\d]*1234[^\\d]*";
final String phone1 = "8885551234";
final String phone2 = "888-555-1234";
final String phone3 = "(888) 555-1234";
final RecipientEditTextView view = new RecipientEditTextView(getContext(), null);
final BaseRecipientAdapter adapter = new TestBaseRecipientAdapter(getContext(), 10,
BaseRecipientAdapter.QUERY_TYPE_PHONE);
view.setAdapter(adapter);
final RecipientEntry entry1 = view.createTokenizedEntry(phone1);
final String destination1 = entry1.getDestination();
assertTrue(phone1 + " failed with " + destination1,
Pattern.matches(phonePattern, destination1));
final RecipientEntry entry2 = view.createTokenizedEntry(phone2);
final String destination2 = entry2.getDestination();
assertTrue(phone2 + " failed with " + destination2,
Pattern.matches(phonePattern, destination2));
final RecipientEntry entry3 = view.createTokenizedEntry(phone3);
final String destination3 = entry3.getDestination();
assertTrue(phone3 + " failed with " + destination3,
Pattern.matches(phonePattern, destination3));
}
}