diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/spans/BulletListItemSpan.java b/markwon-core/src/main/java/ru/noties/markwon/core/spans/BulletListItemSpan.java index 97f3bb32..86a6c81b 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/spans/BulletListItemSpan.java +++ b/markwon-core/src/main/java/ru/noties/markwon/core/spans/BulletListItemSpan.java @@ -61,14 +61,28 @@ public class BulletListItemSpan implements LeadingMarginSpan { final int marginLeft = (width - side) / 2; + // @since 2.0.2 + // There is a bug in Android Nougat, when this span receives an `x` that + // doesn't correspond to what it should be (text is placed correctly though). + // Let's make this a general rule -> manually calculate difference between expected/actual + // and add this difference to resulting left/right values. If everything goes well + // we do not encounter a bug -> this `diff` value will be 0 + final int diff; + if (dir < 0) { + // rtl + diff = x - (layout.getWidth() - (width * level)); + } else { + diff = (width * level) - x; + } + // in order to support RTL final int l; final int r; { final int left = x + (dir * marginLeft); final int right = left + (dir * side); - l = Math.min(left, right); - r = Math.max(left, right); + l = Math.min(left, right) + (dir * diff); + r = Math.max(left, right) + (dir * diff); } final int t = baseline + (int) ((paint.descent() + paint.ascent()) / 2.F + .5F) - (side / 2); diff --git a/markwon-ext-tasklist/build.gradle b/markwon-ext-tasklist/build.gradle index 764113ab..43a236df 100644 --- a/markwon-ext-tasklist/build.gradle +++ b/markwon-ext-tasklist/build.gradle @@ -15,6 +15,15 @@ android { dependencies { api project(':markwon-core') + + deps['test'].with { + + testImplementation project(':markwon-test-span') + + testImplementation it['junit'] + testImplementation it['robolectric'] + testImplementation it['commons-io'] + } } registerArtifact(this) \ No newline at end of file diff --git a/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListBlockParser.java b/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListBlockParser.java index 842c53ca..417d92a7 100644 --- a/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListBlockParser.java +++ b/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListBlockParser.java @@ -23,7 +23,7 @@ import java.util.regex.Pattern; @SuppressWarnings("WeakerAccess") class TaskListBlockParser extends AbstractBlockParser { - private static final Pattern PATTERN = Pattern.compile("\\s*-\\s+\\[(x|X|\\s)\\]\\s+(.*)"); + private static final Pattern PATTERN = Pattern.compile("\\s*([-*+]|\\d{1,9}[.)])\\s+\\[(x|X|\\s)]\\s+(.*)"); private final TaskListBlock block = new TaskListBlock(); @@ -88,9 +88,9 @@ class TaskListBlockParser extends AbstractBlockParser { continue; } listItem = new TaskListItem() - .done(isDone(matcher.group(1))) + .done(isDone(matcher.group(2))) .indent(item.indent / 2); - inlineParser.parse(matcher.group(2), listItem); + inlineParser.parse(matcher.group(3), listItem); block.appendChild(listItem); } } diff --git a/markwon-ext-tasklist/src/test/java/ru/noties/markwon/ext/tasklist/TaskListTest.java b/markwon-ext-tasklist/src/test/java/ru/noties/markwon/ext/tasklist/TaskListTest.java new file mode 100644 index 00000000..6097312a --- /dev/null +++ b/markwon-ext-tasklist/src/test/java/ru/noties/markwon/ext/tasklist/TaskListTest.java @@ -0,0 +1,101 @@ +package ru.noties.markwon.ext.tasklist; + +import android.support.annotation.NonNull; + +import org.apache.commons.io.IOUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import ru.noties.markwon.AbstractMarkwonPlugin; +import ru.noties.markwon.Markwon; +import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.MarkwonSpansFactory; +import ru.noties.markwon.RenderProps; +import ru.noties.markwon.SpanFactory; +import ru.noties.markwon.test.TestSpan; +import ru.noties.markwon.test.TestSpanMatcher; + +import static ru.noties.markwon.test.TestSpan.args; +import static ru.noties.markwon.test.TestSpan.document; +import static ru.noties.markwon.test.TestSpan.span; +import static ru.noties.markwon.test.TestSpan.text; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class TaskListTest { + + private static final String SPAN = "task-list"; + private static final String IS_DONE = "is-done"; + + @Test + public void test() { + + final TestSpan.Document document = document( + span(SPAN, args(IS_DONE, false), text("First")), + newLine(), + span(SPAN, args(IS_DONE, true), text("Second")), + newLine(), + span(SPAN, args(IS_DONE, true), text("Third")), + newLine(), + span(SPAN, args(IS_DONE, false), text("First star")), + newLine(), + span(SPAN, args(IS_DONE, true), text("Second star")), + newLine(), + span(SPAN, args(IS_DONE, true), text("Third star")), + newLine(), + span(SPAN, args(IS_DONE, false), text("First plus")), + newLine(), + span(SPAN, args(IS_DONE, true), text("Second plus")), + newLine(), + span(SPAN, args(IS_DONE, true), text("Third plus")), + newLine(), + span(SPAN, args(IS_DONE, true), text("Number with dot")), + newLine(), + span(SPAN, args(IS_DONE, false), text("Number")) + ); + + TestSpanMatcher.matches( + markwon().toMarkdown(read("task-lists.md")), + document + ); + } + + @NonNull + private static Markwon markwon() { + return Markwon.builder(RuntimeEnvironment.application) + .usePlugin(TaskListPlugin.create(RuntimeEnvironment.application)) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(TaskListItem.class, new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + return span(SPAN, args(IS_DONE, TaskListProps.DONE.require(props))); + } + }); + } + }) + .build(); + } + + @SuppressWarnings("SameParameterValue") + @NonNull + private static String read(@NonNull String name) { + try { + return IOUtils.resourceToString("tests/" + name, StandardCharsets.UTF_8, TaskListDrawable.class.getClassLoader()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @NonNull + private static TestSpan.Text newLine() { + return text("\n"); + } +} diff --git a/markwon-ext-tasklist/src/test/resources/tests/task-lists.md b/markwon-ext-tasklist/src/test/resources/tests/task-lists.md new file mode 100644 index 00000000..b06f8215 --- /dev/null +++ b/markwon-ext-tasklist/src/test/resources/tests/task-lists.md @@ -0,0 +1,11 @@ +- [ ] First +- [x] Second +- [X] Third +* [ ] First star +* [x] Second star +* [X] Third star ++ [ ] First plus ++ [x] Second plus ++ [X] Third plus +1. [x] Number with dot +3) [ ] Number \ No newline at end of file diff --git a/markwon-test-span/src/main/java/ru/noties/markwon/test/TestSpanMatcher.java b/markwon-test-span/src/main/java/ru/noties/markwon/test/TestSpanMatcher.java index df014266..03bc2ace 100644 --- a/markwon-test-span/src/main/java/ru/noties/markwon/test/TestSpanMatcher.java +++ b/markwon-test-span/src/main/java/ru/noties/markwon/test/TestSpanMatcher.java @@ -3,8 +3,9 @@ package ru.noties.markwon.test; import android.support.annotation.NonNull; import android.text.Spanned; -import junit.framework.Assert; -import junit.framework.ComparisonFailure; + +import org.junit.Assert; +import org.junit.ComparisonFailure; import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; @@ -106,8 +107,8 @@ public abstract class TestSpanMatcher { } throw new AssertionError( - String.format(Locale.US, "Expected span{%s} at {start: %d, end: %d}, found: %s", - expectedSpan, start, end, actualSpan)); + String.format(Locale.US, "Expected span{%s} at {start: %d, end: %d}, found: %s, text: \"%s\"", + expectedSpan, start, end, actualSpan, spanned.subSequence(start, end))); } }