From caf7f99335a020b52f876238e9fc026c50b6a251 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Fri, 24 Aug 2018 17:09:23 +0300 Subject: [PATCH] Redefined test format --- .../visitor/SpannableMarkdownVisitorTest.java | 129 ++++++++-------- .../markwon/renderer/visitor/TestData.java | 6 +- .../renderer/visitor/TestDataReader.java | 123 +++++++-------- .../markwon/renderer/visitor/TestEntry.java | 42 ------ .../markwon/renderer/visitor/TestFactory.java | 18 ++- .../markwon/renderer/visitor/TestNode.java | 140 ++++++++++++++++++ .../markwon/renderer/visitor/TestSpan.java | 1 + markwon/src/test/resources/tests/first.yaml | 12 +- markwon/src/test/resources/tests/second.yaml | 11 +- 9 files changed, 307 insertions(+), 175 deletions(-) delete mode 100644 markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestEntry.java create mode 100644 markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestNode.java diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/SpannableMarkdownVisitorTest.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/SpannableMarkdownVisitorTest.java index fa1282cd..fcfd2058 100644 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/SpannableMarkdownVisitorTest.java +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/SpannableMarkdownVisitorTest.java @@ -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() { - @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() { + @Override + public boolean test(Object o) { + return o instanceof TestSpan; + } + }) + .cast(TestSpan.class) + .filter(new IxPredicate() { + @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; } } \ No newline at end of file diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestData.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestData.java index 1e643b26..67807202 100644 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestData.java +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestData.java @@ -10,13 +10,13 @@ class TestData { private final String description; private final String input; private final TestConfig config; - private final List output; + private final List output; TestData( @Nullable String description, @NonNull String input, @NonNull TestConfig config, - @NonNull List output) { + @NonNull List output) { this.description = description; this.input = input; this.config = config; @@ -39,7 +39,7 @@ class TestData { } @NonNull - public List output() { + public List output() { return output; } } diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestDataReader.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestDataReader.java index 32edc4d2..b5e5bb2e 100644 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestDataReader.java +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestDataReader.java @@ -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 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 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 testNodes(@NonNull JsonArray array) { + return testNodes(null, array); + } @NonNull - private List testEntries(@NonNull JsonArray array) { + private List 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 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 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 attributes; - - if (value.isJsonObject()) { - - final JsonObject valueObject = value.getAsJsonObject(); - text = valueObject.get("text").getAsString(); - attributes = attributes(valueObject.get("attrs")); + String name = null; + Map 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 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 diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestEntry.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestEntry.java deleted file mode 100644 index be41dec4..00000000 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestEntry.java +++ /dev/null @@ -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 attributes; - - TestEntry(@NonNull String name, @NonNull String text, @NonNull Map 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 attributes() { - return attributes; - } - - @Override - public String toString() { - return "TestEntry{" + - "name='" + name + '\'' + - ", text='" + text + '\'' + - ", attributes=" + attributes + - '}'; - } -} diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java index 717708eb..e87c6f34 100644 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java @@ -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 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) )); diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestNode.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestNode.java new file mode 100644 index 00000000..9124fd59 --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestNode.java @@ -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 children; + private final Map attributes; + + Span( + @Nullable TestNode parent, + @NonNull String name, + @NonNull List children, + @NonNull Map attributes) { + super(parent); + this.name = name; + this.children = children; + this.attributes = attributes; + } + + @NonNull + public String name() { + return name; + } + + @NonNull + public List children() { + return children; + } + + @NonNull + public Map 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 + + '}'; + } + } +} diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestSpan.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestSpan.java index d07f61e9..f4c8d6ba 100644 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestSpan.java +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestSpan.java @@ -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"; diff --git a/markwon/src/test/resources/tests/first.yaml b/markwon/src/test/resources/tests/first.yaml index b782b28d..0bcd844b 100644 --- a/markwon/src/test/resources/tests/first.yaml +++ b/markwon/src/test/resources/tests/first.yaml @@ -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" diff --git a/markwon/src/test/resources/tests/second.yaml b/markwon/src/test/resources/tests/second.yaml index 01f8f411..bd088dc2 100644 --- a/markwon/src/test/resources/tests/second.yaml +++ b/markwon/src/test/resources/tests/second.yaml @@ -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" \ No newline at end of file + - 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"