diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java b/markwon/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java index 70545066..326af3e6 100644 --- a/markwon/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java +++ b/markwon/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java @@ -25,22 +25,9 @@ public interface MarkwonSpansFactory { @Nullable SpanFactory get(@NonNull Class node); - /** - * @see #get(Class) - * @see #require(Node) - */ - @Nullable - SpanFactory get(@NonNull N node); - @NonNull SpanFactory require(@NonNull Class node); - /** - * @see #require(Class) - */ - @NonNull - SpanFactory require(@NonNull N node); - interface Builder { diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java b/markwon/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java index 81b5a0c3..bc472bb4 100644 --- a/markwon/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java +++ b/markwon/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java @@ -16,7 +16,7 @@ class MarkwonSpansFactoryImpl implements MarkwonSpansFactory { private final Map, SpanFactory> factories; - private MarkwonSpansFactoryImpl(@NonNull Map, SpanFactory> factories) { + MarkwonSpansFactoryImpl(@NonNull Map, SpanFactory> factories) { this.factories = factories; } @@ -26,28 +26,12 @@ class MarkwonSpansFactoryImpl implements MarkwonSpansFactory { return factories.get(node); } - @Nullable - @Override - public SpanFactory get(@NonNull N node) { - return get(node.getClass()); - } - @NonNull @Override public SpanFactory require(@NonNull Class node) { final SpanFactory f = get(node); if (f == null) { - throw new NullPointerException(); - } - return f; - } - - @NonNull - @Override - public SpanFactory require(@NonNull N node) { - final SpanFactory f = get(node); - if (f == null) { - throw new NullPointerException(); + throw new NullPointerException(node.getName()); } return f; } diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonVisitor.java b/markwon/src/main/java/ru/noties/markwon/MarkwonVisitor.java index 01f9b819..52cc6cb6 100644 --- a/markwon/src/main/java/ru/noties/markwon/MarkwonVisitor.java +++ b/markwon/src/main/java/ru/noties/markwon/MarkwonVisitor.java @@ -95,7 +95,7 @@ public interface MarkwonVisitor extends Visitor { /** * Helper method to obtain and apply spans for supplied Node. Internally queries {@link SpanFactory} - * for the node (via {@link MarkwonSpansFactory#require(Node)} thus throwing an exception + * for the node (via {@link MarkwonSpansFactory#require(Class)} thus throwing an exception * if there is no {@link SpanFactory} registered for the node). * * @param node to retrieve {@link SpanFactory} for @@ -119,7 +119,7 @@ public interface MarkwonVisitor extends Visitor { * {@link MarkwonSpansFactory} instance. Otherwise ignores this call (no spans will be applied). * If there is a need to ensure that specified node has a {@link SpanFactory} registered, * then {@link #setSpansForNode(Node, int)} can be used. {@link #setSpansForNode(Node, int)} internally - * uses {@link MarkwonSpansFactory#require(Node)}. This method uses {@link MarkwonSpansFactory#get(Node)}. + * uses {@link MarkwonSpansFactory#require(Class)}. This method uses {@link MarkwonSpansFactory#get(Class)}. * * @see #setSpansForNode(Node, int) */ diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonVisitorImpl.java b/markwon/src/main/java/ru/noties/markwon/MarkwonVisitorImpl.java index 33a22bc8..d4f0bce6 100644 --- a/markwon/src/main/java/ru/noties/markwon/MarkwonVisitorImpl.java +++ b/markwon/src/main/java/ru/noties/markwon/MarkwonVisitorImpl.java @@ -241,7 +241,7 @@ class MarkwonVisitorImpl implements MarkwonVisitor { @Override public void setSpansForNode(@NonNull N node, int start) { - setSpans(start, configuration.spansFactory().require(node).getSpans(configuration, renderProps)); + setSpansForNode(node.getClass(), start); } @Override @@ -251,10 +251,7 @@ class MarkwonVisitorImpl implements MarkwonVisitor { @Override public void setSpansForNodeOptional(@NonNull N node, int start) { - final SpanFactory factory = configuration.spansFactory().get(node); - if (factory != null) { - setSpans(start, factory.getSpans(configuration, renderProps)); - } + setSpansForNodeOptional(node.getClass(), start); } @Override diff --git a/markwon/src/main/java/ru/noties/markwon/Prop.java b/markwon/src/main/java/ru/noties/markwon/Prop.java index c02c88a7..91b4515b 100644 --- a/markwon/src/main/java/ru/noties/markwon/Prop.java +++ b/markwon/src/main/java/ru/noties/markwon/Prop.java @@ -36,30 +36,30 @@ public class Prop { } @Nullable - public T get(@NonNull RenderProps context) { - return context.get(this); + public T get(@NonNull RenderProps props) { + return props.get(this); } @NonNull - public T get(@NonNull RenderProps context, @NonNull T defValue) { - return context.get(this, defValue); + public T get(@NonNull RenderProps props, @NonNull T defValue) { + return props.get(this, defValue); } @NonNull - public T require(@NonNull RenderProps context) { - final T t = get(context); + public T require(@NonNull RenderProps props) { + final T t = get(props); if (t == null) { - throw new NullPointerException(); + throw new NullPointerException(name); } return t; } - public void set(@NonNull RenderProps context, @Nullable T value) { - context.set(this, value); + public void set(@NonNull RenderProps props, @Nullable T value) { + props.set(this, value); } - public void clear(@NonNull RenderProps context) { - context.clear(this); + public void clear(@NonNull RenderProps props) { + props.clear(this); } @Override diff --git a/markwon/src/main/java/ru/noties/markwon/core/CorePlugin.java b/markwon/src/main/java/ru/noties/markwon/core/CorePlugin.java index 2c315486..3d9d3ff5 100644 --- a/markwon/src/main/java/ru/noties/markwon/core/CorePlugin.java +++ b/markwon/src/main/java/ru/noties/markwon/core/CorePlugin.java @@ -42,6 +42,7 @@ import ru.noties.markwon.core.spans.OrderedListItemSpan; import ru.noties.markwon.priority.Priority; /** + * @see CoreProps * @since 3.0.0 */ public class CorePlugin extends AbstractMarkwonPlugin { diff --git a/markwon/src/main/java/ru/noties/markwon/core/CoreProps.java b/markwon/src/main/java/ru/noties/markwon/core/CoreProps.java index 158c5802..4a722b0f 100644 --- a/markwon/src/main/java/ru/noties/markwon/core/CoreProps.java +++ b/markwon/src/main/java/ru/noties/markwon/core/CoreProps.java @@ -2,6 +2,9 @@ package ru.noties.markwon.core; import ru.noties.markwon.Prop; +/** + * @since 3.0.0 + */ public abstract class CoreProps { public static final Prop LIST_ITEM_TYPE = Prop.of("list-item-type"); diff --git a/markwon/src/main/java/ru/noties/markwon/image/ImagesPlugin.java b/markwon/src/main/java/ru/noties/markwon/image/ImagesPlugin.java index f3b96305..2f6e8117 100644 --- a/markwon/src/main/java/ru/noties/markwon/image/ImagesPlugin.java +++ b/markwon/src/main/java/ru/noties/markwon/image/ImagesPlugin.java @@ -33,6 +33,7 @@ public class ImagesPlugin extends AbstractMarkwonPlugin { /** * Special scheme that is used {@code file:///android_asset/} + * * @param context * @return */ @@ -79,7 +80,7 @@ public class ImagesPlugin extends AbstractMarkwonPlugin { public void visit(@NonNull MarkwonVisitor visitor, @NonNull Image image) { // if there is no image spanFactory, ignore - final SpanFactory spanFactory = visitor.configuration().spansFactory().get(image); + final SpanFactory spanFactory = visitor.configuration().spansFactory().get(Image.class); if (spanFactory == null) { visitor.visitChildren(image); return; diff --git a/markwon/src/main/java/ru/noties/markwon/image/data/DataUriSchemeHandler.java b/markwon/src/main/java/ru/noties/markwon/image/data/DataUriSchemeHandler.java index 8f44bb02..f4c87ed4 100644 --- a/markwon/src/main/java/ru/noties/markwon/image/data/DataUriSchemeHandler.java +++ b/markwon/src/main/java/ru/noties/markwon/image/data/DataUriSchemeHandler.java @@ -10,7 +10,7 @@ import ru.noties.markwon.image.ImageItem; import ru.noties.markwon.image.SchemeHandler; /** - * @since 3.0.0 + * @since 2.0.0 */ public class DataUriSchemeHandler extends SchemeHandler { diff --git a/markwon/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessorAndroidAssets.java b/markwon/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessorAndroidAssets.java index aa7730de..439d7f12 100644 --- a/markwon/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessorAndroidAssets.java +++ b/markwon/src/main/java/ru/noties/markwon/urlprocessor/UrlProcessorAndroidAssets.java @@ -5,6 +5,10 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; +/** + * Processor that will assume that an URL without scheme points to android assets folder. + * URL with a scheme will be processed by {@link #processor} (if it is specified) or returned `as-is`. + */ @SuppressWarnings({"unused", "WeakerAccess"}) public class UrlProcessorAndroidAssets implements UrlProcessor { diff --git a/markwon/src/test/java/ru/noties/markwon/AbstractMarkwonPluginTest.java b/markwon/src/test/java/ru/noties/markwon/AbstractMarkwonPluginTest.java index b5b8884f..1af2b513 100644 --- a/markwon/src/test/java/ru/noties/markwon/AbstractMarkwonPluginTest.java +++ b/markwon/src/test/java/ru/noties/markwon/AbstractMarkwonPluginTest.java @@ -5,7 +5,12 @@ import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import static org.junit.Assert.fail; +import java.util.List; + +import ru.noties.markwon.core.CorePlugin; +import ru.noties.markwon.priority.Priority; + +import static org.junit.Assert.assertEquals; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) @@ -15,13 +20,25 @@ public class AbstractMarkwonPluginTest { public void priority() { // returns CorePlugin dependency - fail(); + final Priority priority = new AbstractMarkwonPlugin() { + }.priority(); + final List> after = priority.after(); + assertEquals(1, after.size()); + assertEquals(CorePlugin.class, after.get(0)); } @Test public void process_markdown() { // returns supplied argument (no-op) - fail(); + final String[] input = { + "hello", + "!\nworld___-976" + }; + + for (String s : input) { + assertEquals(s, new AbstractMarkwonPlugin() { + }.processMarkdown(s)); + } } } \ No newline at end of file diff --git a/markwon/src/test/java/ru/noties/markwon/MarkwonSpansFactoryImplTest.java b/markwon/src/test/java/ru/noties/markwon/MarkwonSpansFactoryImplTest.java index ae4fbe64..6245df0e 100644 --- a/markwon/src/test/java/ru/noties/markwon/MarkwonSpansFactoryImplTest.java +++ b/markwon/src/test/java/ru/noties/markwon/MarkwonSpansFactoryImplTest.java @@ -1,18 +1,90 @@ package ru.noties.markwon; +import org.commonmark.node.Block; +import org.commonmark.node.Emphasis; +import org.commonmark.node.Image; +import org.commonmark.node.Link; +import org.commonmark.node.ListItem; +import org.commonmark.node.Node; +import org.commonmark.node.Paragraph; +import org.commonmark.node.Text; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import java.util.Collections; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class MarkwonSpansFactoryImplTest { @Test - public void test() { - fail(); + public void get_class() { + + // register one TextNode + final MarkwonSpansFactoryImpl impl = new MarkwonSpansFactoryImpl( + Collections., SpanFactory>singletonMap(Text.class, mock(SpanFactory.class))); + + // text must be present + assertNotNull(impl.get(Text.class)); + + // we haven't registered ListItem, so null here + assertNull(impl.get(ListItem.class)); + } + + @Test + public void require_class() { + + // register one TextNode + final MarkwonSpansFactoryImpl impl = new MarkwonSpansFactoryImpl( + Collections., SpanFactory>singletonMap(Text.class, mock(SpanFactory.class))); + + // text must be present + assertNotNull(impl.require(Text.class)); + + // we haven't registered ListItem, so null here + try { + impl.require(ListItem.class); + fail(); + } catch (NullPointerException e) { + assertTrue(true); + } + } + + @Test + public void builder() { + // all passed to builder will be in factory + + final SpanFactory text = mock(SpanFactory.class); + final SpanFactory link = mock(SpanFactory.class); + + final MarkwonSpansFactory factory = new MarkwonSpansFactoryImpl.BuilderImpl() + .setFactory(Text.class, text) + .setFactory(Link.class, link) + .build(); + + assertNotNull(factory.get(Text.class)); + + assertNotNull(factory.get(Link.class)); + + // a bunch of non-present factories + //noinspection unchecked + final Class[] types = new Class[]{ + Image.class, + Block.class, + Emphasis.class, + Paragraph.class + }; + + for (Class type : types) { + assertNull(factory.get(type)); + } } } \ No newline at end of file diff --git a/markwon/src/test/java/ru/noties/markwon/MarkwonVisitorImplTest.java b/markwon/src/test/java/ru/noties/markwon/MarkwonVisitorImplTest.java index ee8da824..1a444d70 100644 --- a/markwon/src/test/java/ru/noties/markwon/MarkwonVisitorImplTest.java +++ b/markwon/src/test/java/ru/noties/markwon/MarkwonVisitorImplTest.java @@ -1,6 +1,8 @@ package ru.noties.markwon; +import org.commonmark.node.BlockQuote; import org.commonmark.node.Node; +import org.commonmark.node.Text; import org.commonmark.node.Visitor; import org.junit.Test; import org.junit.runner.RunWith; @@ -18,10 +20,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.RETURNS_MOCKS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) @@ -132,7 +137,23 @@ public class MarkwonVisitorImplTest { @Test public void non_registered_nodes_children_visited() { - fail(); + // if a node is encountered, but we have no registered visitor -> just visit children + // (node.firstChild.accept) + + final MarkwonVisitorImpl impl = new MarkwonVisitorImpl( + mock(MarkwonConfiguration.class), + mock(RenderProps.class), + mock(SpannableBuilder.class), + Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + + final BlockQuote node = mock(BlockQuote.class); + final Node child = mock(Node.class); + when(node.getFirstChild()).thenReturn(child); + + impl.visit(node); + + verify(node, times(1)).getFirstChild(); + verify(child, times(1)).accept(eq(impl)); } @Test @@ -185,11 +206,61 @@ public class MarkwonVisitorImplTest { @Test public void set_spans_for_node() { - fail(); + // internally requests spanFactory via `require` call (thus throwing exception) + // configuration.spansFactory().require(node).getSpans(configuration, renderProps) + + final MarkwonConfiguration configuration = mock(MarkwonConfiguration.class); + final MarkwonSpansFactory spansFactory = mock(MarkwonSpansFactory.class); + final SpanFactory factory = mock(SpanFactory.class); + + when(configuration.spansFactory()).thenReturn(spansFactory); + when(spansFactory.require(eq(Node.class))).thenReturn(factory); + when(spansFactory.require(eq(Text.class))).thenThrow(new NullPointerException()); + + final MarkwonVisitorImpl impl = new MarkwonVisitorImpl( + configuration, + mock(RenderProps.class), + mock(SpannableBuilder.class), + Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + + impl.setSpansForNode(Node.class, 0); + + verify(configuration, times(1)).spansFactory(); + verify(spansFactory, times(1)).require(eq(Node.class)); + verify(factory, times(1)).getSpans(eq(configuration), any(RenderProps.class)); + + try { + impl.setSpansForNode(Text.class, 0); + fail(); + } catch (NullPointerException e) { + assertTrue(true); + } } @Test public void set_spans_for_node_optional() { - fail(); + // if spanFactory is not found -> nothing will happen (no spans will be applied) + + final MarkwonConfiguration configuration = mock(MarkwonConfiguration.class); + final MarkwonSpansFactory spansFactory = mock(MarkwonSpansFactory.class); + + when(configuration.spansFactory()).thenReturn(spansFactory); + + final SpannableBuilder builder = new SpannableBuilder(); + + final MarkwonVisitorImpl impl = new MarkwonVisitorImpl( + configuration, + mock(RenderProps.class), + builder, + Collections., MarkwonVisitor.NodeVisitor>emptyMap()); + + // append something + builder.append("no-spans-test"); + + assertEquals(0, builder.getSpans(0, builder.length()).size()); + + impl.setSpansForNodeOptional(Node.class, 0); + + assertEquals(0, builder.getSpans(0, builder.length()).size()); } } \ No newline at end of file diff --git a/markwon/src/test/java/ru/noties/markwon/PropTest.java b/markwon/src/test/java/ru/noties/markwon/PropTest.java index bbb98a17..85a81fe1 100644 --- a/markwon/src/test/java/ru/noties/markwon/PropTest.java +++ b/markwon/src/test/java/ru/noties/markwon/PropTest.java @@ -1,18 +1,100 @@ package ru.noties.markwon; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class PropTest { + // get + // get with default + // require + // set + // clear + + private RenderProps props; + private Prop prop; + + @Before + public void before() { + props = mock(RenderProps.class); + prop = new Prop<>("a prop"); + } + + @Test + public void methods_redirected_get() { + + prop.get(props); + + verify(props, times(1)).get(eq(prop)); + } + + @Test + public void methods_redirected_get_with_default() { + + prop.get(props, false); + + verify(props, times(1)).get(eq(prop), eq(false)); + } + + @Test + public void methods_redirected_require() { + // require is a bit different as `require` has no place in renderProps + // instead a Prop will throw an exception if requested prop is not in props + + when(props.get(eq(prop))).thenReturn(true); + + prop.require(props); + + verify(props, times(1)).get(eq(prop)); + } + + @Test + public void methods_redirected_set() { + + prop.set(props, true); + + verify(props, times(1)).set(eq(prop), eq(true)); + } + + @Test + public void methods_redirected_clear() { + + prop.clear(props); + + verify(props, times(1)).clear(eq(prop)); + } + @Test public void require() { - fail(); + + try { + prop.require(props); + fail(); + } catch (NullPointerException e) { + assertTrue(true); + } + } + + @Test + public void has_hashcode_and_equals() { + try { + Prop.class.getDeclaredMethod("hashCode"); + Prop.class.getDeclaredMethod("equals", Object.class); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } } } \ No newline at end of file diff --git a/markwon/src/test/java/ru/noties/markwon/RenderPropsImplTest.java b/markwon/src/test/java/ru/noties/markwon/RenderPropsImplTest.java index a40ba0c4..36a6315b 100644 --- a/markwon/src/test/java/ru/noties/markwon/RenderPropsImplTest.java +++ b/markwon/src/test/java/ru/noties/markwon/RenderPropsImplTest.java @@ -1,18 +1,119 @@ package ru.noties.markwon; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import static org.junit.Assert.fail; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class RenderPropsImplTest { + private RenderPropsImpl props; + private Prop prop; + + @Before + public void before() { + props = new RenderPropsImpl(); + prop = Prop.of("a prop of byte"); + } + @Test - public void test() { - fail(); + public void get() { + + // initial + assertNull(props.get(prop)); + + // update value + props.set(prop, "get-value"); + + assertEquals("get-value", props.get(prop)); + } + + @Test + public void get_with_default() { + + // validate that it's null + assertNull(props.get(prop)); + + assertEquals("a-default", props.get(prop, "a-default")); + + // update value (so, no default will be returned) + props.set(prop, "get-with-default-value"); + + assertEquals("get-with-default-value", props.get(prop, "not-used")); + } + + @Test + public void set() { + + assertNull(props.get(prop)); + + props.set(prop, "set-value"); + assertEquals("set-value", props.get(prop)); + + // update (aka delete) with null value + props.set(prop, null); + assertNull(props.get(prop)); + + // multiple set's (last one will be used, each one replaces previous) + props.set(prop, "value-1"); + props.set(prop, "value-2"); + props.set(prop, "value-3"); + + assertEquals("value-3", props.get(prop)); + } + + @Test + public void clear() { + + props.set(prop, "clear-value"); + + assertEquals("clear-value", props.get(prop)); + + props.clear(prop); + + assertNull(props.get(prop)); + } + + @Test + public void clear_all() { + + final List> list = Arrays.asList( + Prop.of("#1"), + Prop.of("#2"), + Prop.of("#3"), + Prop.of("#4"), + Prop.of("#5")); + + // validate that all nulls + for (Prop prop : list) { + assertNull(props.get(prop)); + } + + // set each + for (Prop prop : list) { + props.set(prop, prop.name()); + } + + // validate that all are not-null + for (Prop prop : list) { + assertNotNull(props.get(prop)); + } + + props.clearAll(); + + // validate that all are nulls + for (Prop prop : list) { + assertNull(props.get(prop)); + } } } \ No newline at end of file diff --git a/markwon/src/test/java/ru/noties/markwon/syntax/SyntaxHighlightTest.java b/markwon/src/test/java/ru/noties/markwon/syntax/SyntaxHighlightTest.java index 440fe488..a8f39248 100644 --- a/markwon/src/test/java/ru/noties/markwon/syntax/SyntaxHighlightTest.java +++ b/markwon/src/test/java/ru/noties/markwon/syntax/SyntaxHighlightTest.java @@ -74,7 +74,7 @@ public class SyntaxHighlightTest { }; final MarkwonSpansFactory spansFactory = mock(MarkwonSpansFactory.class); - when(spansFactory.get(any(FencedCodeBlock.class))).thenReturn(new SpanFactory() { + when(spansFactory.get(any(Class.class))).thenReturn(new SpanFactory() { @Override public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { return codeSpan;