From 0587180f147e842dc3e589826f4606cb3ef2ccd3 Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 3 Jan 2020 15:32:06 +0000 Subject: [PATCH] Make SpannedSubject more fluent I decided the flags bit was a bit unclear so I played around with this It's also needed for more 'complex' assertions like colors - I didn't want to just chuck in a fourth int parameter to create: hasForegroundColorSpan(int start, int end, int flags, int color) PiperOrigin-RevId: 287989424 --- .../text/webvtt/WebvttCueParserTest.java | 16 +- .../text/webvtt/WebvttDecoderTest.java | 31 +- .../testutil/truth/SpannedSubject.java | 320 ++++++++++++++++-- .../testutil/truth/SpannedSubjectTest.java | 165 ++++++++- 4 files changed, 478 insertions(+), 54 deletions(-) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java index d23ed00e95..c9e8488c60 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java @@ -34,8 +34,7 @@ public final class WebvttCueParserTest { + "This is text with html tags"); assertThat(text.toString()).isEqualTo("This is text with html tags"); - assertThat(text) - .hasUnderlineSpan("This ".length(), "This is".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + assertThat(text).hasUnderlineSpanBetween("This ".length(), "This is".length()); assertThat(text) .hasBoldItalicSpan( "This is text with ".length(), @@ -59,10 +58,7 @@ public final class WebvttCueParserTest { assertThat(text.toString()).isEqualTo("An unclosed u tag with italic inside"); assertThat(text) - .hasUnderlineSpan( - "An ".length(), - "An unclosed u tag with italic inside".length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + .hasUnderlineSpanBetween("An ".length(), "An unclosed u tag with italic inside".length()); assertThat(text) .hasItalicSpan( "An unclosed u tag with ".length(), @@ -81,10 +77,9 @@ public final class WebvttCueParserTest { "An italic tag with unclosed underline".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); assertThat(text) - .hasUnderlineSpan( + .hasUnderlineSpanBetween( "An italic tag with unclosed ".length(), - "An italic tag with unclosed underline".length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + "An italic tag with unclosed underline".length()); } @Test @@ -95,8 +90,7 @@ public final class WebvttCueParserTest { assertThat(text.toString()).isEqualTo(expectedText); assertThat(text).hasBoldSpan(0, expectedText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // Text between the tags is underlined. - assertThat(text) - .hasUnderlineSpan(0, "Overlapping u and".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + assertThat(text).hasUnderlineSpanBetween(0, "Overlapping u and".length()); // Only text from to <\\u> is italic (unexpected - but simplifies the parsing). assertThat(text) .hasItalicSpan( diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java index 5c044c029b..a3ab3e8b1a 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java @@ -15,23 +15,23 @@ */ package com.google.android.exoplayer2.text.webvtt; +import static com.google.android.exoplayer2.testutil.truth.SpannedSubject.assertThat; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import android.graphics.Typeface; import android.text.Layout.Alignment; import android.text.Spanned; -import android.text.style.BackgroundColorSpan; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.text.style.TypefaceSpan; -import android.text.style.UnderlineSpan; import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.SubtitleDecoderException; +import com.google.android.exoplayer2.util.ColorParser; import com.google.common.truth.Expect; import java.io.IOException; import java.util.List; @@ -403,14 +403,25 @@ public class WebvttDecoderTest { Spanned s2 = getUniqueSpanTextAt(subtitle, /* timeUs= */ 2345000); Spanned s3 = getUniqueSpanTextAt(subtitle, /* timeUs= */ 20000000); Spanned s4 = getUniqueSpanTextAt(subtitle, /* timeUs= */ 25000000); - assertThat(s1.getSpans(/* start= */ 0, s1.length(), ForegroundColorSpan.class)).hasLength(1); - assertThat(s1.getSpans(/* start= */ 0, s1.length(), BackgroundColorSpan.class)).hasLength(1); - assertThat(s2.getSpans(/* start= */ 0, s2.length(), ForegroundColorSpan.class)).hasLength(1); - assertThat(s3.getSpans(/* start= */ 10, s3.length(), UnderlineSpan.class)).hasLength(1); - assertThat(s4.getSpans(/* start= */ 0, /* end= */ 16, BackgroundColorSpan.class)).hasLength(2); - assertThat(s4.getSpans(/* start= */ 17, s4.length(), StyleSpan.class)).hasLength(1); - assertThat(s4.getSpans(/* start= */ 17, s4.length(), StyleSpan.class)[0].getStyle()) - .isEqualTo(Typeface.BOLD); + assertThat(s1) + .hasForegroundColorSpanBetween(0, s1.length()) + .withColor(ColorParser.parseCssColor("papayawhip")); + assertThat(s1) + .hasBackgroundColorSpanBetween(0, s1.length()) + .withColor(ColorParser.parseCssColor("green")); + assertThat(s2) + .hasForegroundColorSpanBetween(0, s2.length()) + .withColor(ColorParser.parseCssColor("peachpuff")); + + assertThat(s3).hasUnderlineSpanBetween(10, s3.length()); + assertThat(s4) + .hasBackgroundColorSpanBetween(0, 16) + .withColor(ColorParser.parseCssColor("lime")); + assertThat(s4) + .hasBoldSpan( + /* startIndex= */ 17, + /* endIndex= */ s4.length(), + /* flags= */ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } @Test diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java index 0015634c1f..84d40fb6f1 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java @@ -23,8 +23,12 @@ import static com.google.common.truth.Truth.assertAbout; import android.graphics.Typeface; import android.text.Spanned; import android.text.TextUtils; +import android.text.style.BackgroundColorSpan; +import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.text.style.UnderlineSpan; +import androidx.annotation.CheckResult; +import androidx.annotation.ColorInt; import androidx.annotation.Nullable; import com.google.common.truth.FailureMetadata; import com.google.common.truth.Subject; @@ -64,7 +68,7 @@ public final class SpannedSubject extends Subject { failWithoutActual( simpleFact("Expected no spans"), fact("in text", actual), - fact("but found", actualSpansString())); + fact("but found", getAllSpansAsStringWithoutFlags(actual))); } } @@ -76,6 +80,7 @@ public final class SpannedSubject extends Subject { * @param flags The flags of the expected span. See constants on {@link Spanned} for more * information. */ + // TODO: swap this to fluent-style. public void hasItalicSpan(int startIndex, int endIndex, int flags) { hasStyleSpan(startIndex, endIndex, flags, Typeface.ITALIC); } @@ -88,6 +93,7 @@ public final class SpannedSubject extends Subject { * @param flags The flags of the expected span. See constants on {@link Spanned} for more * information. */ + // TODO: swap this to fluent-style. public void hasBoldSpan(int startIndex, int endIndex, int flags) { hasStyleSpan(startIndex, endIndex, flags, Typeface.BOLD); } @@ -104,7 +110,7 @@ public final class SpannedSubject extends Subject { } } - failWithExpectedSpan( + failWithExpectedSpanWithFlags( startIndex, endIndex, flags, @@ -129,6 +135,7 @@ public final class SpannedSubject extends Subject { * @param flags The flags of the expected span. See constants on {@link Spanned} for more * information. */ + // TODO: swap this to fluent-style. public void hasBoldItalicSpan(int startIndex, int endIndex, int flags) { if (actual == null) { failWithoutActual(simpleFact("Spanned must not be null")); @@ -149,11 +156,13 @@ public final class SpannedSubject extends Subject { String spannedSubstring = actual.toString().substring(startIndex, endIndex); String boldSpan = - spanToString(startIndex, endIndex, flags, new StyleSpan(Typeface.BOLD), spannedSubstring); + getSpanAsStringWithFlags( + startIndex, endIndex, flags, new StyleSpan(Typeface.BOLD), spannedSubstring); String italicSpan = - spanToString(startIndex, endIndex, flags, new StyleSpan(Typeface.ITALIC), spannedSubstring); + getSpanAsStringWithFlags( + startIndex, endIndex, flags, new StyleSpan(Typeface.ITALIC), spannedSubstring); String boldItalicSpan = - spanToString( + getSpanAsStringWithFlags( startIndex, endIndex, flags, new StyleSpan(Typeface.BOLD_ITALIC), spannedSubstring); failWithoutActual( @@ -161,34 +170,89 @@ public final class SpannedSubject extends Subject { fact("in text", actual.toString()), fact("expected either", boldItalicSpan), fact("or both", boldSpan + "\n" + italicSpan), - fact("but found", actualSpansString())); + fact("but found", getAllSpansAsStringWithFlags(actual))); } /** - * Checks that the subject has an underline span from {@code startIndex} to {@code endIndex}. + * Checks that the subject has an {@link UnderlineSpan} from {@code start} to {@code end}. * - * @param startIndex The start of the expected span. - * @param endIndex The end of the expected span. - * @param flags The flags of the expected span. See constants on {@link Spanned} for more - * information. + * @param start The start of the expected span. + * @param end The end of the expected span. + * @return A {@link WithSpanFlags} object for optional additional assertions on the flags. */ - public void hasUnderlineSpan(int startIndex, int endIndex, int flags) { + public WithSpanFlags hasUnderlineSpanBetween(int start, int end) { if (actual == null) { failWithoutActual(simpleFact("Spanned must not be null")); - return; + return ALREADY_FAILED_WITH_FLAGS; } - List underlineSpans = - findMatchingSpans(startIndex, endIndex, flags, UnderlineSpan.class); - if (underlineSpans.size() == 1) { - return; + List underlineSpans = findMatchingSpans(start, end, UnderlineSpan.class); + List allFlags = new ArrayList<>(); + for (UnderlineSpan span : underlineSpans) { + allFlags.add(actual.getSpanFlags(span)); } - failWithExpectedSpan( - startIndex, - endIndex, - flags, - new UnderlineSpan(), - actual.toString().substring(startIndex, endIndex)); + if (underlineSpans.size() == 1) { + return check("UnderlineSpan (start=%s,end=%s)", start, end).about(spanFlags()).that(allFlags); + } + failWithExpectedSpanWithoutFlags( + start, end, UnderlineSpan.class, actual.toString().substring(start, end)); + return ALREADY_FAILED_WITH_FLAGS; + } + + /** + * Checks that the subject as a {@link ForegroundColorSpan} from {@code start} to {@code end}. + * + *

The color is asserted in a follow-up method call on the return {@link Colored} object. + * + * @param start The start of the expected span. + * @param end The end of the expected span. + * @return A {@link Colored} object to assert on the color of the matching spans. + */ + @CheckResult + public Colored hasForegroundColorSpanBetween(int start, int end) { + if (actual == null) { + failWithoutActual(simpleFact("Spanned must not be null")); + return ALREADY_FAILED_COLORED; + } + + List foregroundColorSpans = + findMatchingSpans(start, end, ForegroundColorSpan.class); + if (foregroundColorSpans.isEmpty()) { + failWithExpectedSpanWithoutFlags( + start, end, ForegroundColorSpan.class, actual.toString().substring(start, end)); + return ALREADY_FAILED_COLORED; + } + return check("ForegroundColorSpan (start=%s,end=%s)", start, end) + .about(foregroundColorSpans(actual)) + .that(foregroundColorSpans); + } + + /** + * Checks that the subject as a {@link ForegroundColorSpan} from {@code start} to {@code end}. + * + *

The color is asserted in a follow-up method call on the return {@link Colored} object. + * + * @param start The start of the expected span. + * @param end The end of the expected span. + * @return A {@link Colored} object to assert on the color of the matching spans. + */ + @CheckResult + public Colored hasBackgroundColorSpanBetween(int start, int end) { + if (actual == null) { + failWithoutActual(simpleFact("Spanned must not be null")); + return ALREADY_FAILED_COLORED; + } + + List backgroundColorSpans = + findMatchingSpans(start, end, BackgroundColorSpan.class); + if (backgroundColorSpans.isEmpty()) { + failWithExpectedSpanWithoutFlags( + start, end, BackgroundColorSpan.class, actual.toString().substring(start, end)); + return ALREADY_FAILED_COLORED; + } + return check("BackgroundColorSpan (start=%s,end=%s)", start, end) + .about(backgroundColorSpans(actual)) + .that(backgroundColorSpans); } private List findMatchingSpans( @@ -204,27 +268,46 @@ public final class SpannedSubject extends Subject { return spans; } - private void failWithExpectedSpan( + private List findMatchingSpans(int startIndex, int endIndex, Class spanClazz) { + List spans = new ArrayList<>(); + for (T span : actual.getSpans(startIndex, endIndex, spanClazz)) { + if (actual.getSpanStart(span) == startIndex && actual.getSpanEnd(span) == endIndex) { + spans.add(span); + } + } + return spans; + } + + private void failWithExpectedSpanWithFlags( int start, int end, int flags, Object span, String spannedSubstring) { failWithoutActual( simpleFact("No matching span found"), fact("in text", actual), - fact("expected", spanToString(start, end, flags, span, spannedSubstring)), - fact("but found", actualSpansString())); + fact("expected", getSpanAsStringWithFlags(start, end, flags, span, spannedSubstring)), + fact("but found", getAllSpansAsStringWithFlags(actual))); } - private String actualSpansString() { + private void failWithExpectedSpanWithoutFlags( + int start, int end, Class spanType, String spannedSubstring) { + failWithoutActual( + simpleFact("No matching span found"), + fact("in text", actual), + fact("expected", getSpanAsStringWithoutFlags(start, end, spanType, spannedSubstring)), + fact("but found", getAllSpansAsStringWithoutFlags(actual))); + } + + private static String getAllSpansAsStringWithFlags(Spanned spanned) { List actualSpanStrings = new ArrayList<>(); - for (Object span : actual.getSpans(0, actual.length(), /* type= */ Object.class)) { - actualSpanStrings.add(spanToString(span, actual)); + for (Object span : spanned.getSpans(0, spanned.length(), Object.class)) { + actualSpanStrings.add(getSpanAsStringWithFlags(span, spanned)); } return TextUtils.join("\n", actualSpanStrings); } - private static String spanToString(Object span, Spanned spanned) { + private static String getSpanAsStringWithFlags(Object span, Spanned spanned) { int spanStart = spanned.getSpanStart(span); int spanEnd = spanned.getSpanEnd(span); - return spanToString( + return getSpanAsStringWithFlags( spanStart, spanEnd, spanned.getSpanFlags(span), @@ -232,7 +315,7 @@ public final class SpannedSubject extends Subject { spanned.toString().substring(spanStart, spanEnd)); } - private static String spanToString( + private static String getSpanAsStringWithFlags( int start, int end, int flags, Object span, String spannedSubstring) { String suffix; if (span instanceof StyleSpan) { @@ -244,4 +327,177 @@ public final class SpannedSubject extends Subject { "start=%s\tend=%s\tflags=%s\ttype=%s\tsubstring='%s'%s", start, end, flags, span.getClass().getSimpleName(), spannedSubstring, suffix); } + + private static String getAllSpansAsStringWithoutFlags(Spanned spanned) { + List actualSpanStrings = new ArrayList<>(); + for (Object span : spanned.getSpans(0, spanned.length(), Object.class)) { + actualSpanStrings.add(getSpanAsStringWithoutFlags(span, spanned)); + } + return TextUtils.join("\n", actualSpanStrings); + } + + private static String getSpanAsStringWithoutFlags(Object span, Spanned spanned) { + int spanStart = spanned.getSpanStart(span); + int spanEnd = spanned.getSpanEnd(span); + return getSpanAsStringWithoutFlags( + spanStart, spanEnd, span.getClass(), spanned.toString().substring(spanStart, spanEnd)); + } + + private static String getSpanAsStringWithoutFlags( + int start, int end, Class span, String spannedSubstring) { + return String.format( + "start=%s\tend=%s\ttype=%s\tsubstring='%s'", + start, end, span.getSimpleName(), spannedSubstring); + } + + /** + * Allows additional assertions to be made on the flags of matching spans. + * + *

Identical to {@link WithSpanFlags}, but this should be returned from {@code with...()} + * methods while {@link WithSpanFlags} should be returned from {@code has...()} methods. + * + *

See Flag constants on {@link Spanned} for possible values. + */ + public interface AndSpanFlags { + + /** + * Checks that one of the matched spans has the expected {@code flags}. + * + * @param flags The expected flags. See SPAN_* constants on {@link Spanned} for possible values. + */ + void andFlags(int flags); + } + + private static final AndSpanFlags ALREADY_FAILED_AND_FLAGS = flags -> {}; + + /** + * Allows additional assertions to be made on the flags of matching spans. + * + *

Identical to {@link AndSpanFlags}, but this should be returned from {@code has...()} methods + * while {@link AndSpanFlags} should be returned from {@code with...()} methods. + */ + public interface WithSpanFlags { + + /** + * Checks that one of the matched spans has the expected {@code flags}. + * + * @param flags The expected flags. See SPAN_* constants on {@link Spanned} for possible values. + */ + void withFlags(int flags); + } + + private static final WithSpanFlags ALREADY_FAILED_WITH_FLAGS = flags -> {}; + + private static Factory> spanFlags() { + return SpanFlagsSubject::new; + } + + private static final class SpanFlagsSubject extends Subject + implements AndSpanFlags, WithSpanFlags { + + private final List flags; + + private SpanFlagsSubject(FailureMetadata metadata, List flags) { + super(metadata, flags); + this.flags = flags; + } + + @Override + public void andFlags(int flags) { + check("contains()").that(this.flags).contains(flags); + } + + @Override + public void withFlags(int flags) { + andFlags(flags); + } + } + + /** Allows assertions about the color of a span. */ + public interface Colored { + + /** + * Checks that at least one of the matched spans has the expected {@code color}. + * + * @param color The expected color. + * @return A {@link WithSpanFlags} object for optional additional assertions on the flags. + */ + AndSpanFlags withColor(@ColorInt int color); + } + + private static final Colored ALREADY_FAILED_COLORED = color -> ALREADY_FAILED_AND_FLAGS; + + private Factory> foregroundColorSpans( + Spanned actualSpanned) { + return (FailureMetadata metadata, List spans) -> + new ForegroundColorSpansSubject(metadata, spans, actualSpanned); + } + + private static final class ForegroundColorSpansSubject extends Subject implements Colored { + + private final List actualSpans; + private final Spanned actualSpanned; + + private ForegroundColorSpansSubject( + FailureMetadata metadata, List actualSpans, Spanned actualSpanned) { + super(metadata, actualSpans); + this.actualSpans = actualSpans; + this.actualSpanned = actualSpanned; + } + + @Override + public AndSpanFlags withColor(@ColorInt int color) { + List matchingSpanFlags = new ArrayList<>(); + // Use hex strings for comparison so the values in error messages are more human readable. + List spanColors = new ArrayList<>(); + + for (ForegroundColorSpan span : actualSpans) { + spanColors.add(String.format("0x%08X", span.getForegroundColor())); + if (span.getForegroundColor() == color) { + matchingSpanFlags.add(actualSpanned.getSpanFlags(span)); + } + } + + String expectedColorString = String.format("0x%08X", color); + check("foregroundColor").that(spanColors).containsExactly(expectedColorString); + return check("flags").about(spanFlags()).that(matchingSpanFlags); + } + } + + private Factory> backgroundColorSpans( + Spanned actualSpanned) { + return (FailureMetadata metadata, List spans) -> + new BackgroundColorSpansSubject(metadata, spans, actualSpanned); + } + + private static final class BackgroundColorSpansSubject extends Subject implements Colored { + + private final List actualSpans; + private final Spanned actualSpanned; + + private BackgroundColorSpansSubject( + FailureMetadata metadata, List actualSpans, Spanned actualSpanned) { + super(metadata, actualSpans); + this.actualSpans = actualSpans; + this.actualSpanned = actualSpanned; + } + + @Override + public AndSpanFlags withColor(@ColorInt int color) { + List matchingSpanFlags = new ArrayList<>(); + // Use hex strings for comparison so the values in error messages are more human readable. + List spanColors = new ArrayList<>(); + + for (BackgroundColorSpan span : actualSpans) { + spanColors.add(String.format("0x%08X", span.getBackgroundColor())); + if (span.getBackgroundColor() == color) { + matchingSpanFlags.add(actualSpanned.getSpanFlags(span)); + } + } + + String expectedColorString = String.format("0x%08X", color); + check("backgroundColor").that(spanColors).containsExactly(expectedColorString); + return check("flags").about(spanFlags()).that(matchingSpanFlags); + } + } } diff --git a/testutils/src/test/java/com/google/android/exoplayer2/testutil/truth/SpannedSubjectTest.java b/testutils/src/test/java/com/google/android/exoplayer2/testutil/truth/SpannedSubjectTest.java index 37ccef6908..a01f6f66e0 100644 --- a/testutils/src/test/java/com/google/android/exoplayer2/testutil/truth/SpannedSubjectTest.java +++ b/testutils/src/test/java/com/google/android/exoplayer2/testutil/truth/SpannedSubjectTest.java @@ -21,9 +21,12 @@ import static com.google.android.exoplayer2.testutil.truth.SpannedSubject.spanne import static com.google.common.truth.ExpectFailure.assertThat; import static com.google.common.truth.ExpectFailure.expectFailureAbout; +import android.graphics.Color; import android.graphics.Typeface; import android.text.SpannableString; import android.text.Spanned; +import android.text.style.BackgroundColorSpan; +import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.text.style.UnderlineSpan; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -153,7 +156,167 @@ public class SpannedSubjectTest { int end = start + "underlined".length(); spannable.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - assertThat(spannable).hasUnderlineSpan(start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + assertThat(spannable) + .hasUnderlineSpanBetween(start, end) + .withFlags(Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + + @Test + public void foregroundColorSpan_success() { + SpannableString spannable = SpannableString.valueOf("test with cyan section"); + int start = "test with ".length(); + int end = start + "cyan".length(); + spannable.setSpan( + new ForegroundColorSpan(Color.CYAN), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + + assertThat(spannable) + .hasForegroundColorSpanBetween(start, end) + .withColor(Color.CYAN) + .andFlags(Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + + @Test + public void foregroundColorSpan_wrongEndIndex() { + SpannableString spannable = SpannableString.valueOf("test with cyan section"); + int start = "test with ".length(); + int end = start + "cyan".length(); + spannable.setSpan( + new ForegroundColorSpan(Color.CYAN), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + + int incorrectEnd = end + 2; + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting + .that(spannable) + .hasForegroundColorSpanBetween(start, incorrectEnd) + .withColor(Color.CYAN)); + assertThat(expected).factValue("expected").contains("end=" + incorrectEnd); + assertThat(expected).factValue("but found").contains("end=" + end); + } + + @Test + public void foregroundColorSpan_wrongColor() { + SpannableString spannable = SpannableString.valueOf("test with cyan section"); + int start = "test with ".length(); + int end = start + "cyan".length(); + spannable.setSpan( + new ForegroundColorSpan(Color.CYAN), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting + .that(spannable) + .hasForegroundColorSpanBetween(start, end) + .withColor(Color.BLUE)); + assertThat(expected).factValue("value of").contains("foregroundColor"); + assertThat(expected).factValue("expected").contains("0xFF0000FF"); // Color.BLUE + assertThat(expected).factValue("but was").contains("0xFF00FFFF"); // Color.CYAN + } + + @Test + public void foregroundColorSpan_wrongFlags() { + SpannableString spannable = SpannableString.valueOf("test with cyan section"); + int start = "test with ".length(); + int end = start + "cyan".length(); + spannable.setSpan( + new ForegroundColorSpan(Color.CYAN), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting + .that(spannable) + .hasForegroundColorSpanBetween(start, end) + .withColor(Color.CYAN) + .andFlags(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)); + assertThat(expected).factValue("value of").contains("flags"); + assertThat(expected) + .factValue("expected to contain") + .contains(String.valueOf(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)); + assertThat(expected) + .factValue("but was") + .contains(String.valueOf(Spanned.SPAN_INCLUSIVE_EXCLUSIVE)); + } + + @Test + public void backgroundColorSpan_success() { + SpannableString spannable = SpannableString.valueOf("test with cyan section"); + int start = "test with ".length(); + int end = start + "cyan".length(); + spannable.setSpan( + new BackgroundColorSpan(Color.CYAN), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + + assertThat(spannable) + .hasBackgroundColorSpanBetween(start, end) + .withColor(Color.CYAN) + .andFlags(Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + + @Test + public void backgroundColorSpan_wrongEndIndex() { + SpannableString spannable = SpannableString.valueOf("test with cyan section"); + int start = "test with ".length(); + int end = start + "cyan".length(); + spannable.setSpan( + new BackgroundColorSpan(Color.CYAN), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + + int incorrectEnd = end + 2; + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting + .that(spannable) + .hasBackgroundColorSpanBetween(start, incorrectEnd) + .withColor(Color.CYAN)); + assertThat(expected).factValue("expected").contains("end=" + incorrectEnd); + assertThat(expected).factValue("but found").contains("end=" + end); + } + + @Test + public void backgroundColorSpan_wrongColor() { + SpannableString spannable = SpannableString.valueOf("test with cyan section"); + int start = "test with ".length(); + int end = start + "cyan".length(); + spannable.setSpan( + new BackgroundColorSpan(Color.CYAN), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting + .that(spannable) + .hasBackgroundColorSpanBetween(start, end) + .withColor(Color.BLUE)); + assertThat(expected).factValue("value of").contains("backgroundColor"); + assertThat(expected).factValue("expected").contains("0xFF0000FF"); // Color.BLUE + assertThat(expected).factValue("but was").contains("0xFF00FFFF"); // Color.CYAN + } + + @Test + public void backgroundColorSpan_wrongFlags() { + SpannableString spannable = SpannableString.valueOf("test with cyan section"); + int start = "test with ".length(); + int end = start + "cyan".length(); + spannable.setSpan( + new BackgroundColorSpan(Color.CYAN), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + + AssertionError expected = + expectFailure( + whenTesting -> + whenTesting + .that(spannable) + .hasBackgroundColorSpanBetween(start, end) + .withColor(Color.CYAN) + .andFlags(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)); + assertThat(expected).factValue("value of").contains("flags"); + assertThat(expected) + .factValue("expected to contain") + .contains(String.valueOf(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)); + assertThat(expected) + .factValue("but was") + .contains(String.valueOf(Spanned.SPAN_INCLUSIVE_EXCLUSIVE)); } private static AssertionError expectFailure(