Redefined test format
This commit is contained in:
parent
db4be0eee3
commit
caf7f99335
@ -9,7 +9,6 @@ import org.junit.runner.RunWith;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import ix.Ix;
|
||||
@ -54,67 +53,83 @@ public class SpannableMarkdownVisitorTest {
|
||||
node.accept(visitor);
|
||||
|
||||
final SpannableStringBuilder stringBuilder = builder.spannableStringBuilder();
|
||||
final String raw = stringBuilder.toString();
|
||||
|
||||
System.out.printf("%n%s%n", stringBuilder);
|
||||
|
||||
int index = 0;
|
||||
int lastIndex = 0;
|
||||
|
||||
for (TestEntry entry : data.output()) {
|
||||
|
||||
final String expected = entry.text();
|
||||
|
||||
final boolean isText = "text".equals(entry.name());
|
||||
|
||||
final int start;
|
||||
final int end;
|
||||
|
||||
if (isText) {
|
||||
start = lastIndex;
|
||||
end = start + expected.length();
|
||||
index = lastIndex = end;
|
||||
} else {
|
||||
start = raw.indexOf(expected, index);
|
||||
if (start < 0) {
|
||||
throw new AssertionError(String.format("Cannot find `%s` starting at index: %d, raw: %n###%n%s%n###",
|
||||
expected, start, raw
|
||||
));
|
||||
}
|
||||
end = start + expected.length();
|
||||
lastIndex = Math.max(end, lastIndex);
|
||||
}
|
||||
|
||||
if (!expected.equals(raw.substring(start, end))) {
|
||||
throw new AssertionError(String.format("Expected: `%s`, actual: `%s`, start: %d, raw: %n###%n%s%n###",
|
||||
expected, raw.substring(start, end), start, raw
|
||||
));
|
||||
}
|
||||
|
||||
final Object[] spans = stringBuilder.getSpans(start, end, Object.class);
|
||||
final int length = spans != null ? spans.length : 0;
|
||||
|
||||
if (isText) {
|
||||
// validate no spans
|
||||
assertEquals(Arrays.toString(spans), 0, length);
|
||||
} else {
|
||||
assertTrue(length > 0);
|
||||
final Object span = Ix.fromArray(spans)
|
||||
.filter(new IxPredicate<Object>() {
|
||||
@Override
|
||||
public boolean test(Object o) {
|
||||
return start == stringBuilder.getSpanStart(o)
|
||||
&& end == stringBuilder.getSpanEnd(o);
|
||||
}
|
||||
})
|
||||
.first(null);
|
||||
assertNotNull(span);
|
||||
assertTrue(span instanceof TestSpan);
|
||||
final TestSpan testSpan = (TestSpan) span;
|
||||
assertEquals(entry.name(), testSpan.name());
|
||||
assertEquals(entry.attributes(), testSpan.attributes());
|
||||
}
|
||||
for (TestNode testNode : data.output()) {
|
||||
index = validate(stringBuilder, index, testNode);
|
||||
}
|
||||
}
|
||||
|
||||
private int validate(@NonNull SpannableStringBuilder builder, int index, @NonNull TestNode node) {
|
||||
|
||||
if (node.isText()) {
|
||||
|
||||
final String text;
|
||||
{
|
||||
final String content = node.getAsText().text();
|
||||
|
||||
// code is a special case as we wrap it around non-breakable spaces
|
||||
final TestNode parent = node.parent();
|
||||
if (parent != null) {
|
||||
final TestNode.Span span = parent.getAsSpan();
|
||||
if (TestSpan.CODE.equals(span.name())) {
|
||||
text = "\u00a0" + content + "\u00a0";
|
||||
} else if (TestSpan.CODE_BLOCK.equals(span.name())) {
|
||||
text = "\u00a0\n" + content + "\n\u00a0";
|
||||
} else {
|
||||
text = content;
|
||||
}
|
||||
} else {
|
||||
text = content;
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(text, builder.subSequence(index, index + text.length()).toString());
|
||||
|
||||
return index + text.length();
|
||||
}
|
||||
|
||||
final TestNode.Span span = node.getAsSpan();
|
||||
|
||||
int out = index;
|
||||
|
||||
for (TestNode child : span.children()) {
|
||||
out = validate(builder, out, child);
|
||||
}
|
||||
|
||||
final String info = node.toString();
|
||||
|
||||
// we can possibly have parent spans here, should filter them
|
||||
final Object[] spans = builder.getSpans(index, out, Object.class);
|
||||
assertTrue(info, spans != null);
|
||||
|
||||
final TestSpan testSpan = Ix.fromArray(spans)
|
||||
.filter(new IxPredicate<Object>() {
|
||||
@Override
|
||||
public boolean test(Object o) {
|
||||
return o instanceof TestSpan;
|
||||
}
|
||||
})
|
||||
.cast(TestSpan.class)
|
||||
.filter(new IxPredicate<TestSpan>() {
|
||||
@Override
|
||||
public boolean test(TestSpan testSpan) {
|
||||
return span.name().equals(testSpan.name());
|
||||
}
|
||||
})
|
||||
.first(null);
|
||||
|
||||
assertNotNull(info, testSpan);
|
||||
|
||||
assertEquals(info, span.name(), testSpan.name());
|
||||
assertEquals(info, span.attributes(), testSpan.attributes());
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private SpannableConfiguration configuration(@NonNull TestConfig config) {
|
||||
|
||||
@ -126,7 +141,5 @@ public class SpannableMarkdownVisitorTest {
|
||||
.linkResolver(mock(LinkResolverDef.class))
|
||||
.factory(factory)
|
||||
.build();
|
||||
|
||||
// return configuration;
|
||||
}
|
||||
}
|
@ -10,13 +10,13 @@ class TestData {
|
||||
private final String description;
|
||||
private final String input;
|
||||
private final TestConfig config;
|
||||
private final List<TestEntry> output;
|
||||
private final List<TestNode> output;
|
||||
|
||||
TestData(
|
||||
@Nullable String description,
|
||||
@NonNull String input,
|
||||
@NonNull TestConfig config,
|
||||
@NonNull List<TestEntry> output) {
|
||||
@NonNull List<TestNode> output) {
|
||||
this.description = description;
|
||||
this.input = input;
|
||||
this.config = config;
|
||||
@ -39,7 +39,7 @@ class TestData {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<TestEntry> output() {
|
||||
public List<TestNode> output() {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +79,9 @@ abstract class TestDataReader {
|
||||
|
||||
static class Reader {
|
||||
|
||||
private static final String ATTRS = "attrs";
|
||||
private static final String TEXT = "text";
|
||||
|
||||
private final String file;
|
||||
|
||||
Reader(@NonNull String file) {
|
||||
@ -120,95 +123,99 @@ abstract class TestDataReader {
|
||||
|
||||
final String input = jsonObject.get("input").getAsString();
|
||||
if (TextUtils.isEmpty(input)) {
|
||||
throw new RuntimeException(String.format("Test case file `%s` is missing input parameter", file));
|
||||
throw new RuntimeException(String.format("Test case file `%s` is missing " +
|
||||
"input parameter", file));
|
||||
}
|
||||
|
||||
final List<TestEntry> testSpans = testEntries(jsonObject.get("output").getAsJsonArray());
|
||||
if (testSpans.size() == 0) {
|
||||
throw new RuntimeException(String.format("Test case file `%s` has no output specified", file));
|
||||
final TestConfig testConfig = testConfig(jsonObject.get("config"));
|
||||
|
||||
final List<TestNode> testNodes = testNodes(jsonObject.get("output").getAsJsonArray());
|
||||
if (testNodes.size() == 0) {
|
||||
throw new RuntimeException(String.format("Test case file `%s` has no " +
|
||||
"output specified", file));
|
||||
}
|
||||
|
||||
return new TestData(
|
||||
description,
|
||||
input,
|
||||
testConfig(jsonObject.get("config")),
|
||||
testSpans
|
||||
testConfig,
|
||||
testNodes
|
||||
);
|
||||
}
|
||||
|
||||
// todo: rename TestNode -> it's not a node... but... what?
|
||||
@NonNull
|
||||
private List<TestNode> testNodes(@NonNull JsonArray array) {
|
||||
return testNodes(null, array);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<TestEntry> testEntries(@NonNull JsonArray array) {
|
||||
private List<TestNode> testNodes(@Nullable TestNode parent, @NonNull JsonArray array) {
|
||||
|
||||
// all items are contained in this array
|
||||
// key is the name of an item
|
||||
// item can be defined like this:
|
||||
// link: "text-content-of-the-link"
|
||||
// link:
|
||||
// attributes:
|
||||
// - href: "my-href-attribute"
|
||||
// text: "and here is the text content"
|
||||
// text node can be found only at root level
|
||||
// an item in array is a JsonObject
|
||||
|
||||
final int length = array.size();
|
||||
// it can be "b": "bold" -> means Span(name="b", children=[Text(bold)]
|
||||
// or b:
|
||||
// - text: "bold" -> which is the same as above
|
||||
|
||||
final List<TestEntry> testSpans = new ArrayList<>(length);
|
||||
// it can additionally contain "attrs" key which is the attributes
|
||||
// b:
|
||||
// - text: "bold"
|
||||
// attrs:
|
||||
// href: "my-href"
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
final int size = array.size();
|
||||
|
||||
final JsonElement element = array.get(i);
|
||||
final List<TestNode> testNodes = new ArrayList<>(size);
|
||||
|
||||
if (element.isJsonObject()) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
|
||||
final JsonObject object = element.getAsJsonObject();
|
||||
final JsonObject object = array.get(i).getAsJsonObject();
|
||||
|
||||
// objects must have exactly 1 key: name of the node
|
||||
// it's value can be different: JsonPrimitive (String) or an JsonObject (with attributes and text)
|
||||
|
||||
final String name = object.keySet().iterator().next();
|
||||
final JsonElement value = object.get(name);
|
||||
|
||||
final String text;
|
||||
final Map<String, String> attributes;
|
||||
|
||||
if (value.isJsonObject()) {
|
||||
|
||||
final JsonObject valueObject = value.getAsJsonObject();
|
||||
text = valueObject.get("text").getAsString();
|
||||
attributes = attributes(valueObject.get("attrs"));
|
||||
String name = null;
|
||||
Map<String, String> attributes = null;
|
||||
|
||||
for (String key : object.keySet()) {
|
||||
if (ATTRS.equals(key)) {
|
||||
attributes = attributes(object.get(key));
|
||||
} else if (name == null) {
|
||||
name = key;
|
||||
} else {
|
||||
// we allow only 2 keys: span and/or attributes and no more
|
||||
throw new RuntimeException("Unexpected key in object: " + object);
|
||||
}
|
||||
}
|
||||
|
||||
final JsonPrimitive primitive;
|
||||
if (name == null) {
|
||||
throw new RuntimeException("Object is missing tag name: " + object);
|
||||
}
|
||||
|
||||
if (value.isJsonPrimitive()) {
|
||||
primitive = value.getAsJsonPrimitive();
|
||||
} else {
|
||||
primitive = null;
|
||||
}
|
||||
if (attributes == null) {
|
||||
attributes = Collections.emptyMap();
|
||||
}
|
||||
|
||||
if (primitive == null
|
||||
|| !primitive.isString()) {
|
||||
throw new RuntimeException(String.format("Unexpected json element at index: `%d` in array: `%s`",
|
||||
i, array
|
||||
));
|
||||
}
|
||||
final JsonElement element = object.get(name);
|
||||
|
||||
text = primitive.getAsString();
|
||||
attributes = Collections.emptyMap();
|
||||
if (TEXT.equals(name)) {
|
||||
testNodes.add(new TestNode.Text(parent, element.getAsString()));
|
||||
} else {
|
||||
|
||||
final List<TestNode> children = new ArrayList<>(1);
|
||||
final TestNode.Span span = new TestNode.Span(parent, name, children, attributes);
|
||||
|
||||
// if it's primitive string -> just append text node
|
||||
if (element.isJsonPrimitive()) {
|
||||
children.add(new TestNode.Text(span, element.getAsString()));
|
||||
} else if (element.isJsonArray()) {
|
||||
children.addAll(testNodes(span, element.getAsJsonArray()));
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected element: " + object);
|
||||
}
|
||||
|
||||
testSpans.add(new TestEntry(name, text, attributes));
|
||||
|
||||
} else {
|
||||
throw new RuntimeException(String.format("Unexpected json element at index: `%d` in array: `%s`",
|
||||
i, array
|
||||
));
|
||||
testNodes.add(span);
|
||||
}
|
||||
}
|
||||
|
||||
return testSpans;
|
||||
return testNodes;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@ -1,42 +0,0 @@
|
||||
package ru.noties.markwon.renderer.visitor;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class TestEntry {
|
||||
|
||||
private final String name;
|
||||
private final String text;
|
||||
private final Map<String, String> attributes;
|
||||
|
||||
TestEntry(@NonNull String name, @NonNull String text, @NonNull Map<String, String> attributes) {
|
||||
this.name = name;
|
||||
this.text = text;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String text() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Map<String, String> attributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TestEntry{" +
|
||||
"name='" + name + '\'' +
|
||||
", text='" + text + '\'' +
|
||||
", attributes=" + attributes +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ import ru.noties.markwon.spans.TableRowSpan;
|
||||
import static ru.noties.markwon.renderer.visitor.TestSpan.BLOCK_QUOTE;
|
||||
import static ru.noties.markwon.renderer.visitor.TestSpan.BULLET_LIST;
|
||||
import static ru.noties.markwon.renderer.visitor.TestSpan.CODE;
|
||||
import static ru.noties.markwon.renderer.visitor.TestSpan.CODE_BLOCK;
|
||||
import static ru.noties.markwon.renderer.visitor.TestSpan.EMPHASIS;
|
||||
import static ru.noties.markwon.renderer.visitor.TestSpan.HEADING;
|
||||
import static ru.noties.markwon.renderer.visitor.TestSpan.IMAGE;
|
||||
@ -63,13 +64,16 @@ class TestFactory implements SpannableFactory {
|
||||
@Nullable
|
||||
@Override
|
||||
public Object code(@NonNull SpannableTheme theme, boolean multiline) {
|
||||
return new TestSpan(CODE, map("multiline", multiline));
|
||||
final String name = multiline
|
||||
? CODE_BLOCK
|
||||
: CODE;
|
||||
return new TestSpan(name);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object orderedListItem(@NonNull SpannableTheme theme, int startNumber) {
|
||||
return new TestSpan(ORDERED_LIST, map("startNumber", startNumber));
|
||||
return new TestSpan(ORDERED_LIST, map("start", startNumber));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -87,7 +91,7 @@ class TestFactory implements SpannableFactory {
|
||||
@Nullable
|
||||
@Override
|
||||
public Object heading(@NonNull SpannableTheme theme, int level) {
|
||||
return new TestSpan(HEADING, map("level", level));
|
||||
return new TestSpan(HEADING + level);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -101,7 +105,7 @@ class TestFactory implements SpannableFactory {
|
||||
public Object taskListItem(@NonNull SpannableTheme theme, int blockIndent, boolean isDone) {
|
||||
return new TestSpan(TASK_LIST, map(
|
||||
Pair.of("blockIdent", blockIndent),
|
||||
Pair.of("isDone", isDone)
|
||||
Pair.of("done", isDone)
|
||||
));
|
||||
}
|
||||
|
||||
@ -110,8 +114,8 @@ class TestFactory implements SpannableFactory {
|
||||
public Object tableRow(@NonNull SpannableTheme theme, @NonNull List<TableRowSpan.Cell> cells, boolean isHeader, boolean isOdd) {
|
||||
return new TestSpan(TABLE_ROW, map(
|
||||
Pair.of("cells", cells),
|
||||
Pair.of("isHeader", isHeader),
|
||||
Pair.of("isOdd", isOdd)
|
||||
Pair.of("header", isHeader),
|
||||
Pair.of("odd", isOdd)
|
||||
));
|
||||
}
|
||||
|
||||
@ -127,7 +131,7 @@ class TestFactory implements SpannableFactory {
|
||||
@Override
|
||||
public Object image(@NonNull SpannableTheme theme, @NonNull String destination, @NonNull AsyncDrawable.Loader loader, @NonNull ImageSizeResolver imageSizeResolver, @Nullable ImageSize imageSize, boolean replacementTextIsLink) {
|
||||
return new TestSpan(IMAGE, map(
|
||||
Pair.of("destination", destination),
|
||||
Pair.of("src", destination),
|
||||
Pair.of("imageSize", imageSize),
|
||||
Pair.of("replacementTextIsLink", replacementTextIsLink)
|
||||
));
|
||||
|
@ -0,0 +1,140 @@
|
||||
package ru.noties.markwon.renderer.visitor;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
abstract class TestNode {
|
||||
|
||||
private final TestNode parent;
|
||||
|
||||
TestNode(@Nullable TestNode parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TestNode parent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
abstract boolean isText();
|
||||
|
||||
abstract boolean isSpan();
|
||||
|
||||
@NonNull
|
||||
abstract Text getAsText();
|
||||
|
||||
@NonNull
|
||||
abstract Span getAsSpan();
|
||||
|
||||
|
||||
static class Text extends TestNode {
|
||||
|
||||
private final String text;
|
||||
|
||||
Text(@Nullable TestNode parent, @NonNull String text) {
|
||||
super(parent);
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String text() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isText() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isSpan() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
Text getAsText() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
Span getAsSpan() {
|
||||
throw new ClassCastException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Text{" +
|
||||
"text='" + text + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
static class Span extends TestNode {
|
||||
|
||||
private final String name;
|
||||
private final List<TestNode> children;
|
||||
private final Map<String, String> attributes;
|
||||
|
||||
Span(
|
||||
@Nullable TestNode parent,
|
||||
@NonNull String name,
|
||||
@NonNull List<TestNode> children,
|
||||
@NonNull Map<String, String> attributes) {
|
||||
super(parent);
|
||||
this.name = name;
|
||||
this.children = children;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<TestNode> children() {
|
||||
return children;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Map<String, String> attributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isText() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isSpan() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
Text getAsText() {
|
||||
throw new ClassCastException();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
Span getAsSpan() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Span{" +
|
||||
"name='" + name + '\'' +
|
||||
", children=" + children +
|
||||
", attributes=" + attributes +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ class TestSpan {
|
||||
static final String EMPHASIS = "i";
|
||||
static final String BLOCK_QUOTE = "blockquote";
|
||||
static final String CODE = "code";
|
||||
static final String CODE_BLOCK = "code-block";
|
||||
static final String ORDERED_LIST = "ol";
|
||||
static final String BULLET_LIST = "ul";
|
||||
static final String THEMATIC_BREAK = "hr";
|
||||
|
@ -13,11 +13,13 @@ config:
|
||||
output:
|
||||
- text: "Here is some "
|
||||
- a:
|
||||
attrs:
|
||||
href: "https://my.href"
|
||||
text: "link"
|
||||
- text: "link"
|
||||
attrs:
|
||||
href: "https://my.href"
|
||||
- text: " "
|
||||
- b: "bold bold italic bold"
|
||||
- i: "bold italic"
|
||||
- b:
|
||||
- text: "bold "
|
||||
- i: "bold italic" #equals to: `- i: - text: "bold italic"`
|
||||
- text: " bold"
|
||||
- text: " normal"
|
||||
|
||||
|
@ -16,10 +16,17 @@ input: |-
|
||||
output:
|
||||
- text: "First "
|
||||
- b: "line"
|
||||
- text: " "
|
||||
- text: " is "
|
||||
- i: "always"
|
||||
- text: " "
|
||||
- s: "strike"
|
||||
- text: " down\n\n"
|
||||
- blockquote: "Some quote here!"
|
||||
- text: "\n\n"
|
||||
- text: "\n\n"
|
||||
- h1: "Header 1"
|
||||
- text: "\n\n"
|
||||
- h2: "Header 2"
|
||||
- text: "\n\nand "
|
||||
- code: "some code"
|
||||
- text: " and more:\n\n"
|
||||
- code-block: "the code in multiline"
|
||||
|
Loading…
x
Reference in New Issue
Block a user