Add more tests

This commit is contained in:
Dimitry Ivanov 2018-08-25 21:54:44 +03:00
parent fddeb885a2
commit 7caa376bcb
14 changed files with 358 additions and 137 deletions

View File

@ -1,11 +1,9 @@
package ru.noties.markwon.renderer.visitor; package ru.noties.markwon.renderer.visitor;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import org.commonmark.node.Node; import org.commonmark.node.Node;
import org.junit.ComparisonFailure;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.ParameterizedRobolectricTestRunner; import org.robolectric.ParameterizedRobolectricTestRunner;
@ -13,11 +11,7 @@ import org.robolectric.annotation.Config;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import ix.Ix;
import ix.IxPredicate;
import ru.noties.markwon.LinkResolverDef; import ru.noties.markwon.LinkResolverDef;
import ru.noties.markwon.Markwon; import ru.noties.markwon.Markwon;
import ru.noties.markwon.SpannableBuilder; import ru.noties.markwon.SpannableBuilder;
@ -28,8 +22,6 @@ import ru.noties.markwon.renderer.SpannableMarkdownVisitor;
import ru.noties.markwon.spans.SpannableTheme; import ru.noties.markwon.spans.SpannableTheme;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@RunWith(ParameterizedRobolectricTestRunner.class) @RunWith(ParameterizedRobolectricTestRunner.class)
@ -60,99 +52,23 @@ public class SpannableMarkdownVisitorTest {
final SpannableStringBuilder stringBuilder = builder.spannableStringBuilder(); final SpannableStringBuilder stringBuilder = builder.spannableStringBuilder();
// System.out.printf("%s: %s%n", file, Arrays.toString(stringBuilder.getSpans(0, stringBuilder.length(), Object.class))); final TestValidator validator = TestValidator.create(file);
int index = 0; int index = 0;
for (TestNode testNode : data.output()) { for (TestNode testNode : data.output()) {
index = validate(stringBuilder, index, testNode); index = validator.validate(stringBuilder, index, testNode);
} }
// assert that the whole thing is processed // assert that the whole thing is processed
assertEquals(stringBuilder.length(), index); assertEquals(stringBuilder.length(), index);
}
private int validate(@NonNull final SpannableStringBuilder builder, final int index, @NonNull TestNode node) { final Object[] spans = stringBuilder.getSpans(0, stringBuilder.length(), Object.class);
final int length = spans != null
? spans.length
: 0;
if (node.isText()) { assertEquals(Arrays.toString(spans), validator.processedSpanNodesCount(), length);
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 int end = out;
final String info = node.toString();
// System.out.printf("%s: %s%n", file, builder.subSequence(index, out));
// 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())
&& index == builder.getSpanStart(testSpan)
&& end == builder.getSpanEnd(testSpan);
}
})
.first(null);
assertNotNull(
format("info: %s, spans: %s", info, Arrays.toString(spans)),
testSpan
);
assertEquals(info, span.name(), testSpan.name());
// for correct tracking of nested blocks we must validate expected start/end
assertEquals(info, index, builder.getSpanStart(testSpan));
assertEquals(info, out, builder.getSpanEnd(testSpan));
System.out.printf("%s: expected: %s, actual: %s%n", file, span.attributes(), testSpan.attributes());
assertMapEquals(info, span.attributes(), testSpan.attributes());
return out;
} }
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
@ -164,57 +80,16 @@ public class SpannableMarkdownVisitorTest {
? null ? null
: MarkwonHtmlParser.noOp(); : MarkwonHtmlParser.noOp();
// todo: rest omitted for now final boolean softBreakAddsNewLine = config.hasOption(TestConfig.SOFT_BREAK_ADDS_NEW_LINE);
final boolean htmlAllowNonClosedTags = config.hasOption(TestConfig.HTML_ALLOW_NON_CLOSED_TAGS);
return SpannableConfiguration.builder(null) return SpannableConfiguration.builder(null)
.theme(mock(SpannableTheme.class)) .theme(mock(SpannableTheme.class))
.linkResolver(mock(LinkResolverDef.class)) .linkResolver(mock(LinkResolverDef.class))
.htmlParser(htmlParser) .htmlParser(htmlParser)
.factory(factory) .factory(factory)
.softBreakAddsNewLine(softBreakAddsNewLine)
.htmlAllowNonClosedTags(htmlAllowNonClosedTags)
.build(); .build();
} }
private static void assertMapEquals(
@NonNull String message,
@NonNull Map<String, String> expected,
@NonNull Map<String, String> actual) {
boolean result = expected.size() == actual.size();
if (result) {
for (Map.Entry<String, String> entry : expected.entrySet()) {
if (!actual.containsKey(entry.getKey())
|| !equals(entry.getValue(), actual.get(entry.getKey()))) {
result = false;
break;
}
}
}
if (!result) {
final Comparator<Map.Entry<String, String>> comparator = new Comparator<Map.Entry<String, String>>() {
@Override
public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
return o1.getKey().compareTo(o2.getKey());
}
};
final String e = Ix.from(expected.entrySet())
.orderBy(comparator)
.toList()
.toString();
final String a = Ix.from(actual.entrySet())
.orderBy(comparator)
.toList()
.toString();
throw new ComparisonFailure(message, e, a);
}
}
private static boolean equals(@Nullable Object o1, @Nullable Object o2) {
return o1 != null
? o1.equals(o2)
: o2 == null;
}
@NonNull
private static String format(@NonNull String message, Object... args) {
return String.format(message, args);
}
} }

View File

@ -0,0 +1,191 @@
package ru.noties.markwon.renderer.visitor;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import java.util.Arrays;
import java.util.Map;
import ix.Ix;
import ix.IxPredicate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
abstract class TestValidator {
abstract int validate(
@NonNull SpannableStringBuilder builder,
int index,
@NonNull TestNode node);
abstract int processedSpanNodesCount();
@NonNull
static TestValidator create(@NonNull String id) {
return new Impl(id);
}
static class Impl extends TestValidator {
private final String id;
private int processedCount;
Impl(@NonNull String id) {
this.id = id;
}
@Override
int validate(
@NonNull final SpannableStringBuilder builder,
final 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();
processedCount += 1;
int out = index;
for (TestNode child : span.children()) {
out = validate(builder, out, child);
}
final int end = out;
// we can possibly have parent spans here, should filter them
final Object[] spans = builder.getSpans(index, out, Object.class);
// expected span{name, attributes} at position{start-end}, with text: `%s`, spans: []
assertTrue(
message(span, index, end, builder, spans),
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) {
// in case of nested spans with the same name (lists)
// we also must validate attributes
// and thus we are moving most of assertions to this filter method
return span.name().equals(testSpan.name())
&& index == builder.getSpanStart(testSpan)
&& end == builder.getSpanEnd(testSpan)
&& mapEquals(span.attributes(), testSpan.attributes());
}
})
.first(null);
assertNotNull(
message(span, index, end, builder, spans),
testSpan
);
return out;
}
@Override
int processedSpanNodesCount() {
return processedCount;
}
private static boolean mapEquals(
@NonNull Map<String, String> expected,
@NonNull Map<String, String> actual) {
if (expected.size() != actual.size()) {
return false;
}
boolean result = true;
for (Map.Entry<String, String> entry : expected.entrySet()) {
if (!actual.containsKey(entry.getKey())
|| !equals(entry.getValue(), actual.get(entry.getKey()))) {
result = false;
break;
}
}
return result;
}
private static boolean equals(@Nullable Object o1, @Nullable Object o2) {
return o1 != null
? o1.equals(o2)
: o2 == null;
}
@NonNull
private static String message(
@NonNull TestNode.Span span,
int start,
int end,
@NonNull Spanned text,
@Nullable Object[] spans) {
final String spansText;
if (spans == null
|| spans.length == 0) {
spansText = "[]";
} else {
final StringBuilder builder = new StringBuilder();
for (Object o: spans) {
final TestSpan testSpan = (TestSpan) o;
if (builder.length() > 0) {
builder.append(", ");
}
builder
.append("{name: '").append(testSpan.name()).append('\'')
.append(", position{").append(start).append(", ").append(end).append('}')
.append(", attributes: ").append(testSpan.attributes())
.append('}');
}
spansText = builder.toString();
}
return String.format("Expected span: %s at position{%d-%d} with text `%s`, spans: %s",
span, start, end, text.subSequence(start, end), spansText
);
}
}
}

View File

@ -0,0 +1,5 @@
input: "**_bold italic_**"
output:
- b:
- i: "bold italic"

View File

@ -0,0 +1,19 @@
input: |-
<i>italic
<b>bold italic
<u>underline bold italic
<s>strike underline bold italic
config:
use-html: true
html-allow-non-closed-tags: true
output:
- i:
- text: "italic "
- b:
- text: "bold italic "
- u:
- text: "underline bold italic "
- s:
- text: "strike underline bold italic"

View File

@ -0,0 +1,12 @@
input: |-
<i>no italic here
<b>bold yeah</b>
<u>no underline
config:
use-html: true
output:
- text: "no italic here "
- b: "bold yeah"
- text: " no underline"

View File

@ -0,0 +1,12 @@
input: |-
This could be a paragraph
But it is not and this one is not also
config:
use-paragraphs: false
output:
- text: "This could be a paragraph"
- text: "\n\n"
- text: "But it is not and this one is not also"

View File

@ -0,0 +1,16 @@
description: "Will be rendered as simple flat list"
input: |-
1. First
2. Second
3. Third
output:
- ol: "First"
start: 1
- text: "\n"
- ol: "Second"
start: 2
- text: "\n"
- ol: "Third"
start: 3

View File

@ -0,0 +1,14 @@
input: |-
5. Five
6. Six
7. Seven
output:
- ol: "Five"
start: 5
- text: "\n"
- ol: "Six"
start: 6
- text: "\n"
- ol: "Seven"
start: 7

View File

@ -0,0 +1,14 @@
input: |-
1. First
1. Second
1. Third
output:
- ol:
- text: "First\n"
- ol:
- text: "Second\n"
- ol: "Third"
start: 1
start: 1
start: 1

View File

@ -0,0 +1,12 @@
input: |-
So, this is a paragraph
And this one is another
config:
use-paragraphs: true
output:
- p: "So, this is a paragraph"
- text: "\n\n"
- p: "And this one is another"

View File

@ -0,0 +1,10 @@
input: |-
hello there!
this one is on the next line
hard break to the full extend
config:
soft-break-adds-new-line: true
output:
- text: "hello there!\nthis one is on the next line\nhard break to the full extend"

View File

@ -0,0 +1,7 @@
input: |-
First line
same line but with space between
this is also the first line
output:
- text: "First line same line but with space between this is also the first line"

View File

@ -0,0 +1,20 @@
input: |-
* First
* * Second
* * * Third
output:
- ul: "First"
level: 0
- text: "\n"
- ul:
- ul: "Second"
level: 1
- text: "\n"
level: 0
- ul:
- ul:
- ul: "Third"
level: 2
level: 1
level: 0

View File

@ -0,0 +1,14 @@
input: |-
* First
* Second
* Third
output:
- ul:
- text: "First\n"
- ul:
- text: "Second\n"
- ul: "Third"
level: 2
level: 1
level: 0