mirror of
https://github.com/samsonjs/media.git
synced 2026-04-04 11:05:47 +00:00
Create Truth SpannedSubject for style assertions
This will be used in subtitle decoding tests I followed this guide: https://truth.dev/extension.html PiperOrigin-RevId: 284787298
This commit is contained in:
parent
c1573106fa
commit
7263699c2e
3 changed files with 385 additions and 0 deletions
|
|
@ -22,6 +22,9 @@
|
|||
* Add support for attaching DRM sessions to clear content in the demo app.
|
||||
* UI: Exclude `DefaultTimeBar` region from system gesture detection
|
||||
([#6685](https://github.com/google/ExoPlayer/issues/6685)).
|
||||
* Add `SpannedSubject` to testutils, for assertions on
|
||||
[Span-styled text]( https://developer.android.com/guide/topics/text/spans)
|
||||
(e.g. subtitles).
|
||||
|
||||
### 2.11.0 (2019-12-11) ###
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* Copyright (C) 2019 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.google.android.exoplayer2.testutil.truth;
|
||||
|
||||
import static com.google.common.truth.Fact.fact;
|
||||
import static com.google.common.truth.Fact.simpleFact;
|
||||
import static com.google.common.truth.Truth.assertAbout;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.text.style.UnderlineSpan;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.common.truth.FailureMetadata;
|
||||
import com.google.common.truth.Subject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** A Truth {@link Subject} for assertions on {@link Spanned} instances containing text styling. */
|
||||
// TODO: add support for more Spans i.e. all those used in com.google.android.exoplayer2.text.
|
||||
public final class SpannedSubject extends Subject {
|
||||
|
||||
@Nullable private final Spanned actual;
|
||||
|
||||
private SpannedSubject(FailureMetadata metadata, @Nullable Spanned actual) {
|
||||
super(metadata, actual);
|
||||
this.actual = actual;
|
||||
}
|
||||
|
||||
public static Factory<SpannedSubject, Spanned> spanned() {
|
||||
return SpannedSubject::new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to create a SpannedSubject.
|
||||
*
|
||||
* <p>Can be statically imported alongside other Truth {@code assertThat} methods.
|
||||
*
|
||||
* @param spanned The subject under test.
|
||||
* @return An object for conducting assertions on the subject.
|
||||
*/
|
||||
public static SpannedSubject assertThat(@Nullable Spanned spanned) {
|
||||
return assertAbout(spanned()).that(spanned);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the subject has an italic span from {@code startIndex} to {@code endIndex}.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public void hasItalicSpan(int startIndex, int endIndex, int flags) {
|
||||
hasStyleSpan(startIndex, endIndex, flags, Typeface.ITALIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the subject has a bold span from {@code startIndex} to {@code endIndex}.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public void hasBoldSpan(int startIndex, int endIndex, int flags) {
|
||||
hasStyleSpan(startIndex, endIndex, flags, Typeface.BOLD);
|
||||
}
|
||||
|
||||
private void hasStyleSpan(int startIndex, int endIndex, int flags, int style) {
|
||||
if (actual == null) {
|
||||
failWithoutActual(simpleFact("Spanned must not be null"));
|
||||
return;
|
||||
}
|
||||
|
||||
for (StyleSpan span : findMatchingSpans(startIndex, endIndex, flags, StyleSpan.class)) {
|
||||
if (span.getStyle() == style) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
failWithExpectedSpan(
|
||||
startIndex,
|
||||
endIndex,
|
||||
flags,
|
||||
new StyleSpan(style),
|
||||
actual.toString().substring(startIndex, endIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the subject has bold and italic styling from {@code startIndex} to {@code
|
||||
* endIndex}.
|
||||
*
|
||||
* <p>This can either be:
|
||||
*
|
||||
* <ul>
|
||||
* <li>A single {@link StyleSpan} with {@code span.getStyle() == Typeface.BOLD_ITALIC}.
|
||||
* <li>Two {@link StyleSpan}s, one with {@code span.getStyle() == Typeface.BOLD} and the other
|
||||
* with {@code span.getStyle() == Typeface.ITALIC}.
|
||||
* </ul>
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public void hasBoldItalicSpan(int startIndex, int endIndex, int flags) {
|
||||
if (actual == null) {
|
||||
failWithoutActual(simpleFact("Spanned must not be null"));
|
||||
return;
|
||||
}
|
||||
|
||||
List<Integer> styles = new ArrayList<>();
|
||||
for (StyleSpan span : findMatchingSpans(startIndex, endIndex, flags, StyleSpan.class)) {
|
||||
styles.add(span.getStyle());
|
||||
}
|
||||
if (styles.size() == 1 && styles.contains(Typeface.BOLD_ITALIC)) {
|
||||
return;
|
||||
} else if (styles.size() == 2
|
||||
&& styles.contains(Typeface.BOLD)
|
||||
&& styles.contains(Typeface.ITALIC)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String spannedSubstring = actual.toString().substring(startIndex, endIndex);
|
||||
String boldSpan =
|
||||
spanToString(startIndex, endIndex, flags, new StyleSpan(Typeface.BOLD), spannedSubstring);
|
||||
String italicSpan =
|
||||
spanToString(startIndex, endIndex, flags, new StyleSpan(Typeface.ITALIC), spannedSubstring);
|
||||
String boldItalicSpan =
|
||||
spanToString(
|
||||
startIndex, endIndex, flags, new StyleSpan(Typeface.BOLD_ITALIC), spannedSubstring);
|
||||
|
||||
failWithoutActual(
|
||||
simpleFact("No matching span found"),
|
||||
fact("in text", actual.toString()),
|
||||
fact("expected either", boldItalicSpan),
|
||||
fact("or both", boldSpan + "\n" + italicSpan),
|
||||
fact("but found", actualSpansString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the subject has an underline span from {@code startIndex} to {@code endIndex}.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public void hasUnderlineSpan(int startIndex, int endIndex, int flags) {
|
||||
if (actual == null) {
|
||||
failWithoutActual(simpleFact("Spanned must not be null"));
|
||||
return;
|
||||
}
|
||||
|
||||
List<UnderlineSpan> underlineSpans =
|
||||
findMatchingSpans(startIndex, endIndex, flags, UnderlineSpan.class);
|
||||
if (underlineSpans.size() == 1) {
|
||||
return;
|
||||
}
|
||||
failWithExpectedSpan(
|
||||
startIndex,
|
||||
endIndex,
|
||||
flags,
|
||||
new UnderlineSpan(),
|
||||
actual.toString().substring(startIndex, endIndex));
|
||||
}
|
||||
|
||||
private <T> List<T> findMatchingSpans(
|
||||
int startIndex, int endIndex, int flags, Class<T> spanClazz) {
|
||||
List<T> spans = new ArrayList<>();
|
||||
for (T span : actual.getSpans(startIndex, endIndex, spanClazz)) {
|
||||
if (actual.getSpanStart(span) == startIndex
|
||||
&& actual.getSpanEnd(span) == endIndex
|
||||
&& actual.getSpanFlags(span) == flags) {
|
||||
spans.add(span);
|
||||
}
|
||||
}
|
||||
return spans;
|
||||
}
|
||||
|
||||
private void failWithExpectedSpan(
|
||||
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()));
|
||||
}
|
||||
|
||||
private String actualSpansString() {
|
||||
List<String> actualSpanStrings = new ArrayList<>();
|
||||
for (Object span : actual.getSpans(0, actual.length(), /* type= */ Object.class)) {
|
||||
actualSpanStrings.add(spanToString(span, actual));
|
||||
}
|
||||
return TextUtils.join("\n", actualSpanStrings);
|
||||
}
|
||||
|
||||
private static String spanToString(Object span, Spanned spanned) {
|
||||
int spanStart = spanned.getSpanStart(span);
|
||||
int spanEnd = spanned.getSpanEnd(span);
|
||||
return spanToString(
|
||||
spanStart,
|
||||
spanEnd,
|
||||
spanned.getSpanFlags(span),
|
||||
span,
|
||||
spanned.toString().substring(spanStart, spanEnd));
|
||||
}
|
||||
|
||||
private static String spanToString(
|
||||
int start, int end, int flags, Object span, String spannedSubstring) {
|
||||
String suffix;
|
||||
if (span instanceof StyleSpan) {
|
||||
suffix = "\tstyle=" + ((StyleSpan) span).getStyle();
|
||||
} else {
|
||||
suffix = "";
|
||||
}
|
||||
return String.format(
|
||||
"start=%s\tend=%s\tflags=%s\ttype=%s\tsubstring='%s'%s",
|
||||
start, end, flags, span.getClass().getSimpleName(), spannedSubstring, suffix);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (C) 2019 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.google.android.exoplayer2.testutil.truth;
|
||||
|
||||
import static com.google.android.exoplayer2.testutil.truth.SpannedSubject.assertThat;
|
||||
import static com.google.android.exoplayer2.testutil.truth.SpannedSubject.spanned;
|
||||
import static com.google.common.truth.ExpectFailure.assertThat;
|
||||
import static com.google.common.truth.ExpectFailure.expectFailureAbout;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.text.style.UnderlineSpan;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.truth.ExpectFailure;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Tests for {@link SpannedSubject}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SpannedSubjectTest {
|
||||
|
||||
@Test
|
||||
public void italicSpan_success() {
|
||||
SpannableString spannable = SpannableString.valueOf("test with italic section");
|
||||
int start = "test with ".length();
|
||||
int end = start + "italic".length();
|
||||
spannable.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
|
||||
assertThat(spannable).hasItalicSpan(start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void italicSpan_mismatchedFlags() {
|
||||
SpannableString spannable = SpannableString.valueOf("test with italic section");
|
||||
int start = "test with ".length();
|
||||
int end = start + "italic".length();
|
||||
spannable.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
AssertionError failure =
|
||||
expectFailure(
|
||||
whenTesting ->
|
||||
whenTesting
|
||||
.that(spannable)
|
||||
.hasItalicSpan(start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE));
|
||||
|
||||
assertThat(failure).factKeys().contains("No matching span found");
|
||||
assertThat(failure).factValue("in text").isEqualTo(spannable.toString());
|
||||
assertThat(failure).factValue("expected").contains("flags=" + Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
assertThat(failure)
|
||||
.factValue("but found")
|
||||
.contains("flags=" + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void italicSpan_null() {
|
||||
AssertionError failure =
|
||||
expectFailure(
|
||||
whenTesting ->
|
||||
whenTesting.that(null).hasItalicSpan(0, 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE));
|
||||
|
||||
assertThat(failure).factKeys().containsExactly("Spanned must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void boldSpan_success() {
|
||||
SpannableString spannable = SpannableString.valueOf("test with bold section");
|
||||
int start = "test with ".length();
|
||||
int end = start + "bold".length();
|
||||
spannable.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
|
||||
assertThat(spannable).hasBoldSpan(start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void boldItalicSpan_withOneSpan() {
|
||||
SpannableString spannable = SpannableString.valueOf("test with bold & italic section");
|
||||
int start = "test with ".length();
|
||||
int end = start + "bold & italic".length();
|
||||
spannable.setSpan(
|
||||
new StyleSpan(Typeface.BOLD_ITALIC), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
|
||||
assertThat(spannable).hasBoldItalicSpan(start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void boldItalicSpan_withTwoSpans() {
|
||||
SpannableString spannable = SpannableString.valueOf("test with bold & italic section");
|
||||
int start = "test with ".length();
|
||||
int end = start + "bold & italic".length();
|
||||
spannable.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
spannable.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
|
||||
assertThat(spannable).hasBoldItalicSpan(start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void boldItalicSpan_mismatchedStartIndex() {
|
||||
SpannableString spannable = SpannableString.valueOf("test with bold & italic section");
|
||||
int start = "test with ".length();
|
||||
int end = start + "bold & italic".length();
|
||||
spannable.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
spannable.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
|
||||
int incorrectStart = start - 2;
|
||||
AssertionError expected =
|
||||
expectFailure(
|
||||
whenTesting ->
|
||||
whenTesting
|
||||
.that(spannable)
|
||||
.hasBoldItalicSpan(incorrectStart, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE));
|
||||
assertThat(expected).factValue("expected either").contains("start=" + incorrectStart);
|
||||
assertThat(expected).factValue("but found").contains("start=" + start);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void underlineSpan_success() {
|
||||
SpannableString spannable = SpannableString.valueOf("test with underlined section");
|
||||
int start = "test with ".length();
|
||||
int end = start + "underlined".length();
|
||||
spannable.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
|
||||
assertThat(spannable).hasUnderlineSpan(start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
private static AssertionError expectFailure(
|
||||
ExpectFailure.SimpleSubjectBuilderCallback<SpannedSubject, Spanned> callback) {
|
||||
return expectFailureAbout(spanned(), callback);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue