blob: f9f44cf9b6076193138322f130534a8205a453a3 [file] [log] [blame]
/*
* Copyright (C) 2017 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 androidx.emoji.util;
import static org.mockito.Matchers.argThat;
import android.text.Spanned;
import android.text.TextUtils;
import androidx.emoji.text.EmojiSpan;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.mockito.ArgumentMatcher;
/**
* Utility class that includes matchers specific to emojis and EmojiSpans.
*/
public class EmojiMatcher {
public static Matcher<CharSequence> hasEmojiAt(final int id, final int start,
final int end) {
return new EmojiResourceMatcher(id, start, end);
}
public static Matcher<CharSequence> hasEmojiAt(final Emoji.EmojiMapping emojiMapping,
final int start, final int end) {
return new EmojiResourceMatcher(emojiMapping.id(), start, end);
}
public static Matcher<CharSequence> hasEmojiAt(final int start, final int end) {
return new EmojiResourceMatcher(-1, start, end);
}
public static Matcher<CharSequence> hasEmoji(final int id) {
return new EmojiResourceMatcher(id, -1, -1);
}
public static Matcher<CharSequence> hasEmoji(final Emoji.EmojiMapping emojiMapping) {
return new EmojiResourceMatcher(emojiMapping.id(), -1, -1);
}
public static Matcher<CharSequence> hasEmoji() {
return new EmojiSpanMatcher();
}
public static Matcher<CharSequence> hasEmojiCount(final int count) {
return new EmojiCountMatcher(count);
}
public static <T extends CharSequence> T sameCharSequence(final T expected) {
return argThat(new ArgumentMatcher<T>() {
@Override
public boolean matches(T o) {
if (o instanceof CharSequence) {
return TextUtils.equals(expected, o);
}
return false;
}
@Override
public String toString() {
return "doesn't match " + expected;
}
});
}
private static class EmojiSpanMatcher extends TypeSafeMatcher<CharSequence> {
private EmojiSpan[] mSpans;
EmojiSpanMatcher() {
}
@Override
public void describeTo(Description description) {
description.appendText("should have EmojiSpans");
}
@Override
protected void describeMismatchSafely(final CharSequence charSequence,
Description mismatchDescription) {
mismatchDescription.appendText(" has no EmojiSpans");
}
@Override
protected boolean matchesSafely(final CharSequence charSequence) {
if (charSequence == null) return false;
if (!(charSequence instanceof Spanned)) return false;
mSpans = ((Spanned) charSequence).getSpans(0, charSequence.length(), EmojiSpan.class);
return mSpans.length != 0;
}
}
private static class EmojiCountMatcher extends TypeSafeMatcher<CharSequence> {
private final int mCount;
private EmojiSpan[] mSpans;
EmojiCountMatcher(final int count) {
mCount = count;
}
@Override
public void describeTo(Description description) {
description.appendText("should have ").appendValue(mCount).appendText(" EmojiSpans");
}
@Override
protected void describeMismatchSafely(final CharSequence charSequence,
Description mismatchDescription) {
mismatchDescription.appendText(" has ");
if (mSpans == null) {
mismatchDescription.appendValue("no");
} else {
mismatchDescription.appendValue(mSpans.length);
}
mismatchDescription.appendText(" EmojiSpans");
}
@Override
protected boolean matchesSafely(final CharSequence charSequence) {
if (charSequence == null) return false;
if (!(charSequence instanceof Spanned)) return false;
mSpans = ((Spanned) charSequence).getSpans(0, charSequence.length(), EmojiSpan.class);
return mSpans.length == mCount;
}
}
private static class EmojiResourceMatcher extends TypeSafeMatcher<CharSequence> {
private static final int ERR_NONE = 0;
private static final int ERR_SPANNABLE_NULL = 1;
private static final int ERR_NO_SPANS = 2;
private static final int ERR_WRONG_INDEX = 3;
private final int mResId;
private final int mStart;
private final int mEnd;
private int mError = ERR_NONE;
private int mActualStart = -1;
private int mActualEnd = -1;
EmojiResourceMatcher(int resId, int start, int end) {
mResId = resId;
mStart = start;
mEnd = end;
}
@Override
public void describeTo(final Description description) {
if (mResId == -1) {
description.appendText("should have EmojiSpan at ")
.appendValue("[" + mStart + "," + mEnd + "]");
} else if (mStart == -1 && mEnd == -1) {
description.appendText("should have EmojiSpan with resource id ")
.appendValue(Integer.toHexString(mResId));
} else {
description.appendText("should have EmojiSpan with resource id ")
.appendValue(Integer.toHexString(mResId))
.appendText(" at ")
.appendValue("[" + mStart + "," + mEnd + "]");
}
}
@Override
protected void describeMismatchSafely(final CharSequence charSequence,
Description mismatchDescription) {
int offset = 0;
mismatchDescription.appendText("[");
while (offset < charSequence.length()) {
int codepoint = Character.codePointAt(charSequence, offset);
mismatchDescription.appendText(Integer.toHexString(codepoint));
offset += Character.charCount(codepoint);
if (offset < charSequence.length()) {
mismatchDescription.appendText(",");
}
}
mismatchDescription.appendText("]");
switch (mError) {
case ERR_NO_SPANS:
mismatchDescription.appendText(" had no spans");
break;
case ERR_SPANNABLE_NULL:
mismatchDescription.appendText(" was null");
break;
case ERR_WRONG_INDEX:
mismatchDescription.appendText(" had Emoji at ")
.appendValue("[" + mActualStart + "," + mActualEnd + "]");
break;
default:
mismatchDescription.appendText(" does not have an EmojiSpan with given "
+ "resource id ");
}
}
@Override
protected boolean matchesSafely(final CharSequence charSequence) {
if (charSequence == null) {
mError = ERR_SPANNABLE_NULL;
return false;
}
if (!(charSequence instanceof Spanned)) {
mError = ERR_NO_SPANS;
return false;
}
Spanned spanned = (Spanned) charSequence;
final EmojiSpan[] spans = spanned.getSpans(0, charSequence.length(), EmojiSpan.class);
if (spans.length == 0) {
mError = ERR_NO_SPANS;
return false;
}
if (mStart == -1 && mEnd == -1) {
for (int index = 0; index < spans.length; index++) {
if (mResId == spans[index].getId()) {
return true;
}
}
return false;
} else {
for (int index = 0; index < spans.length; index++) {
if (mResId == -1 || mResId == spans[index].getId()) {
mActualStart = spanned.getSpanStart(spans[index]);
mActualEnd = spanned.getSpanEnd(spans[index]);
if (mActualStart == mStart && mActualEnd == mEnd) {
return true;
}
}
}
if (mActualStart != -1 && mActualEnd != -1) {
mError = ERR_WRONG_INDEX;
}
return false;
}
}
}
}