Add {Strikethrough,Alignment}Span support to SpannedSubject

I'm going to use these in TtmlDecoderTest

PiperOrigin-RevId: 288862274
This commit is contained in:
ibaker 2020-01-09 10:35:01 +00:00 committed by Oliver Woodman
parent f22ac32c2c
commit bae4d786e2
2 changed files with 312 additions and 0 deletions

View File

@ -21,10 +21,13 @@ import static com.google.common.truth.Fact.simpleFact;
import static com.google.common.truth.Truth.assertAbout;
import android.graphics.Typeface;
import android.text.Layout.Alignment;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.AlignmentSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import android.text.style.UnderlineSpan;
@ -220,6 +223,84 @@ public final class SpannedSubject extends Subject {
hasNoSpansOfTypeBetween(UnderlineSpan.class, start, end);
}
/**
* Checks that the subject has an {@link StrikethroughSpan} from {@code start} to {@code end}.
*
* @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 WithSpanFlags hasStrikethroughSpanBetween(int start, int end) {
if (actual == null) {
failWithoutActual(simpleFact("Spanned must not be null"));
return ALREADY_FAILED_WITH_FLAGS;
}
List<StrikethroughSpan> strikethroughSpans =
findMatchingSpans(start, end, StrikethroughSpan.class);
if (strikethroughSpans.size() == 1) {
return check("StrikethroughSpan (start=%s,end=%s)", start, end)
.about(spanFlags())
.that(Collections.singletonList(actual.getSpanFlags(strikethroughSpans.get(0))));
}
failWithExpectedSpan(
start, end, StrikethroughSpan.class, actual.toString().substring(start, end));
return ALREADY_FAILED_WITH_FLAGS;
}
/**
* Checks that the subject has no {@link StrikethroughSpan}s on any of the text between {@code
* start} and {@code end}.
*
* <p>This fails even if the start and end indexes don't exactly match.
*
* @param start The start index to start searching for spans.
* @param end The end index to stop searching for spans.
*/
public void hasNoStrikethroughSpanBetween(int start, int end) {
hasNoSpansOfTypeBetween(StrikethroughSpan.class, start, end);
}
/**
* Checks that the subject has a {@link AlignmentSpan} from {@code start} to {@code end}.
*
* <p>The alignment is asserted in a follow-up method call on the return {@link Aligned} object.
*
* @param start The start of the expected span.
* @param end The end of the expected span.
* @return A {@link Aligned} object to assert on the alignment of the matching spans.
*/
@CheckResult
public Aligned hasAlignmentSpanBetween(int start, int end) {
if (actual == null) {
failWithoutActual(simpleFact("Spanned must not be null"));
return ALREADY_FAILED_ALIGNED;
}
List<AlignmentSpan> alignmentSpans = findMatchingSpans(start, end, AlignmentSpan.class);
if (alignmentSpans.isEmpty()) {
failWithExpectedSpan(
start, end, AlignmentSpan.class, actual.toString().substring(start, end));
return ALREADY_FAILED_ALIGNED;
}
return check("AlignmentSpan (start=%s,end=%s)", start, end)
.about(alignmentSpans(actual))
.that(alignmentSpans);
}
/**
* Checks that the subject has no {@link AlignmentSpan}s on any of the text between {@code start}
* and {@code end}.
*
* <p>This fails even if the start and end indexes don't exactly match.
*
* @param start The start index to start searching for spans.
* @param end The end index to stop searching for spans.
*/
public void hasNoAlignmentSpanBetween(int start, int end) {
hasNoSpansOfTypeBetween(AlignmentSpan.class, start, end);
}
/**
* Checks that the subject has a {@link ForegroundColorSpan} from {@code start} to {@code end}.
*
@ -550,6 +631,55 @@ public final class SpannedSubject extends Subject {
}
}
/** Allows assertions about the alignment of a span. */
public interface Aligned {
/**
* Checks that at least one of the matched spans has the expected {@code alignment}.
*
* @param alignment The expected alignment.
* @return A {@link WithSpanFlags} object for optional additional assertions on the flags.
*/
AndSpanFlags withAlignment(Alignment alignment);
}
private static final Aligned ALREADY_FAILED_ALIGNED = alignment -> ALREADY_FAILED_AND_FLAGS;
private static Factory<AlignmentSpansSubject, List<AlignmentSpan>> alignmentSpans(
Spanned actualSpanned) {
return (FailureMetadata metadata, List<AlignmentSpan> spans) ->
new AlignmentSpansSubject(metadata, spans, actualSpanned);
}
private static final class AlignmentSpansSubject extends Subject implements Aligned {
private final List<AlignmentSpan> actualSpans;
private final Spanned actualSpanned;
private AlignmentSpansSubject(
FailureMetadata metadata, List<AlignmentSpan> actualSpans, Spanned actualSpanned) {
super(metadata, actualSpans);
this.actualSpans = actualSpans;
this.actualSpanned = actualSpanned;
}
@Override
public AndSpanFlags withAlignment(Alignment alignment) {
List<Integer> matchingSpanFlags = new ArrayList<>();
List<Alignment> spanAlignments = new ArrayList<>();
for (AlignmentSpan span : actualSpans) {
spanAlignments.add(span.getAlignment());
if (span.getAlignment().equals(alignment)) {
matchingSpanFlags.add(actualSpanned.getSpanFlags(span));
}
}
check("alignment").that(spanAlignments).containsExactly(alignment);
return check("flags").about(spanFlags()).that(matchingSpanFlags);
}
}
/** Allows assertions about the color of a span. */
public interface Colored {

View File

@ -23,10 +23,13 @@ import static com.google.common.truth.ExpectFailure.expectFailureAbout;
import android.graphics.Color;
import android.graphics.Typeface;
import android.text.Layout.Alignment;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.AlignmentSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import android.text.style.UnderlineSpan;
@ -283,6 +286,185 @@ public class SpannedSubjectTest {
assertThat(expected).factValue("but found").contains("start=" + start);
}
@Test
public void strikethroughSpan_success() {
SpannableString spannable = SpannableString.valueOf("test with crossed-out section");
int start = "test with ".length();
int end = start + "crossed-out".length();
spannable.setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
assertThat(spannable)
.hasStrikethroughSpanBetween(start, end)
.withFlags(Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
@Test
public void noStrikethroughSpan_success() {
SpannableString spannable =
SpannableString.valueOf("test with underline then crossed-out spans");
spannable.setSpan(
new UnderlineSpan(),
"test with ".length(),
"test with underline".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(
new UnderlineSpan(),
"test with underline then ".length(),
"test with italic then crossed-out".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
assertThat(spannable).hasNoStrikethroughSpanBetween(0, "test with underline then".length());
}
@Test
public void noStrikethroughSpan_failure() {
SpannableString spannable = SpannableString.valueOf("test with crossed-out section");
int start = "test with ".length();
int end = start + "crossed-out".length();
spannable.setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
AssertionError expected =
expectFailure(
whenTesting ->
whenTesting.that(spannable).hasNoStrikethroughSpanBetween(start + 1, end));
assertThat(expected)
.factKeys()
.contains(
"Found unexpected StrikethroughSpans between start=" + (start + 1) + ",end=" + end);
assertThat(expected).factKeys().contains("expected none");
assertThat(expected).factValue("but found").contains("start=" + start);
}
@Test
public void alignmentSpan_success() {
SpannableString spannable = SpannableString.valueOf("test with right-aligned section");
int start = "test with ".length();
int end = start + "right-aligned".length();
spannable.setSpan(
new AlignmentSpan.Standard(Alignment.ALIGN_OPPOSITE),
start,
end,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
assertThat(spannable)
.hasAlignmentSpanBetween(start, end)
.withAlignment(Alignment.ALIGN_OPPOSITE)
.andFlags(Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
@Test
public void alignmentSpan_wrongEndIndex() {
SpannableString spannable = SpannableString.valueOf("test with right-aligned section");
int start = "test with ".length();
int end = start + "right-aligned".length();
spannable.setSpan(
new AlignmentSpan.Standard(Alignment.ALIGN_OPPOSITE),
start,
end,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
int incorrectEnd = end + 2;
AssertionError expected =
expectFailure(
whenTesting ->
whenTesting
.that(spannable)
.hasAlignmentSpanBetween(start, incorrectEnd)
.withAlignment(Alignment.ALIGN_OPPOSITE));
assertThat(expected).factValue("expected").contains("end=" + incorrectEnd);
assertThat(expected).factValue("but found").contains("end=" + end);
}
@Test
public void alignmentSpan_wrongAlignment() {
SpannableString spannable = SpannableString.valueOf("test with right-aligned section");
int start = "test with ".length();
int end = start + "right-aligned".length();
spannable.setSpan(
new AlignmentSpan.Standard(Alignment.ALIGN_OPPOSITE),
start,
end,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
AssertionError expected =
expectFailure(
whenTesting ->
whenTesting
.that(spannable)
.hasAlignmentSpanBetween(start, end)
.withAlignment(Alignment.ALIGN_CENTER));
assertThat(expected).factValue("value of").contains("alignment");
assertThat(expected).factValue("expected").contains("ALIGN_CENTER");
assertThat(expected).factValue("but was").contains("ALIGN_OPPOSITE");
}
@Test
public void alignmentSpan_wrongFlags() {
SpannableString spannable = SpannableString.valueOf("test with right-aligned section");
int start = "test with ".length();
int end = start + "right-aligned".length();
spannable.setSpan(
new AlignmentSpan.Standard(Alignment.ALIGN_OPPOSITE),
start,
end,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
AssertionError expected =
expectFailure(
whenTesting ->
whenTesting
.that(spannable)
.hasAlignmentSpanBetween(start, end)
.withAlignment(Alignment.ALIGN_OPPOSITE)
.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 noAlignmentSpan_success() {
SpannableString spannable =
SpannableString.valueOf("test with underline then right-aligned spans");
spannable.setSpan(
new UnderlineSpan(),
"test with ".length(),
"test with underline".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(
new AlignmentSpan.Standard(Alignment.ALIGN_OPPOSITE),
"test with underline then ".length(),
"test with underline then cyan".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
assertThat(spannable).hasNoAlignmentSpanBetween(0, "test with underline then".length());
}
@Test
public void noAlignmentSpan_failure() {
SpannableString spannable = SpannableString.valueOf("test with right-aligned section");
int start = "test with ".length();
int end = start + "cyan".length();
spannable.setSpan(
new AlignmentSpan.Standard(Alignment.ALIGN_OPPOSITE),
start,
end,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
AssertionError expected =
expectFailure(
whenTesting -> whenTesting.that(spannable).hasNoAlignmentSpanBetween(start + 1, end));
assertThat(expected)
.factKeys()
.contains("Found unexpected AlignmentSpans between start=" + (start + 1) + ",end=" + end);
assertThat(expected).factKeys().contains("expected none");
assertThat(expected).factValue("but found").contains("start=" + start);
}
@Test
public void foregroundColorSpan_success() {
SpannableString spannable = SpannableString.valueOf("test with cyan section");