registryImpl test
This commit is contained in:
		
							parent
							
								
									173425ed53
								
							
						
					
					
						commit
						a2a5857f06
					
				| @ -8,4 +8,5 @@ | ||||
| * removed priority | ||||
| * images-plugin moved to standalone again | ||||
| * removed MarkwonPlugin#configureHtmlRenderer -> now part of HtmlPlugin | ||||
| * TagHandler now has `supportedTags()` method | ||||
| * TagHandler now has `supportedTags()` method | ||||
| * html is moved completely to html-plugin | ||||
| @ -13,27 +13,24 @@ import java.util.concurrent.Future; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| 
 | ||||
| import io.noties.debug.Debug; | ||||
| import io.noties.markwon.AbstractMarkwonPlugin; | ||||
| import io.noties.markwon.Markwon; | ||||
| import io.noties.markwon.MarkwonConfiguration; | ||||
| import io.noties.markwon.app.gif.GifAwarePlugin; | ||||
| import io.noties.markwon.ext.strikethrough.StrikethroughPlugin; | ||||
| import io.noties.markwon.ext.tables.TablePlugin; | ||||
| import io.noties.markwon.ext.tasklist.TaskListPlugin; | ||||
| import io.noties.markwon.app.gif.GifAwarePlugin; | ||||
| import io.noties.markwon.html.HtmlPlugin; | ||||
| import io.noties.markwon.image.ImagesPlugin; | ||||
| import io.noties.markwon.image.data.DataUriSchemeHandler; | ||||
| import io.noties.markwon.image.file.FileSchemeHandler; | ||||
| import io.noties.markwon.image.gif.GifMediaDecoder; | ||||
| import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler; | ||||
| import io.noties.markwon.image.svg.SvgMediaDecoder; | ||||
| import io.noties.markwon.syntax.Prism4jTheme; | ||||
| import io.noties.markwon.syntax.Prism4jThemeDarkula; | ||||
| import io.noties.markwon.syntax.Prism4jThemeDefault; | ||||
| import io.noties.markwon.syntax.SyntaxHighlightPlugin; | ||||
| import io.noties.markwon.urlprocessor.UrlProcessor; | ||||
| import io.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute; | ||||
| import io.noties.debug.Debug; | ||||
| import ru.noties.prism4j.Prism4j; | ||||
| 
 | ||||
| @ActivityScope | ||||
| @ -102,12 +99,12 @@ public class MarkdownRenderer { | ||||
|                         .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() { | ||||
|                             @Override | ||||
|                             public void configureImages(@NonNull ImagesPlugin plugin) { | ||||
|                                 // data uri scheme handler is added automatically | ||||
|                                 // SVG & GIF will be added if required dependencies are present in the classpath | ||||
|                                 // default-media-decoder is also added automatically | ||||
|                                 plugin | ||||
|                                         .addSchemeHandler(DataUriSchemeHandler.create()) | ||||
|                                         .addSchemeHandler(OkHttpNetworkSchemeHandler.create()) | ||||
|                                         .addSchemeHandler(FileSchemeHandler.createWithAssets(context.getAssets())) | ||||
|                                         .addMediaDecoder(GifMediaDecoder.create(false)) | ||||
|                                         .addMediaDecoder(SvgMediaDecoder.create()); | ||||
|                                         .addSchemeHandler(FileSchemeHandler.createWithAssets(context.getAssets())); | ||||
|                             } | ||||
|                         })) | ||||
|                         .usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme)) | ||||
|  | ||||
| @ -5,7 +5,7 @@ buildscript { | ||||
|     } | ||||
|     dependencies { | ||||
|         classpath 'com.android.tools.build:gradle:3.4.1' | ||||
|         classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0' | ||||
|         classpath 'com.github.ben-manes:gradle-versions-plugin:0.21.0' | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -2,18 +2,14 @@ package io.noties.markwon; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.annotation.VisibleForTesting; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import org.commonmark.parser.Parser; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.HashSet; | ||||
| import java.util.Iterator; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| import io.noties.markwon.core.MarkwonTheme; | ||||
| 
 | ||||
| @ -106,109 +102,8 @@ class MarkwonBuilderImpl implements Markwon.Builder { | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     @VisibleForTesting | ||||
|     @NonNull | ||||
|     static List<MarkwonPlugin> preparePlugins(@NonNull List<MarkwonPlugin> plugins) { | ||||
|     private static List<MarkwonPlugin> preparePlugins(@NonNull List<MarkwonPlugin> plugins) { | ||||
|         return new RegistryImpl(plugins).process(); | ||||
|     } | ||||
| 
 | ||||
|     // @since 4.0.0-SNAPSHOT | ||||
|     private static class RegistryImpl implements MarkwonPlugin.Registry { | ||||
| 
 | ||||
|         private final List<MarkwonPlugin> origin; | ||||
|         private final List<MarkwonPlugin> plugins; | ||||
|         private final Set<MarkwonPlugin> pending; | ||||
| 
 | ||||
|         RegistryImpl(@NonNull List<MarkwonPlugin> origin) { | ||||
|             this.origin = origin; | ||||
|             this.plugins = new ArrayList<>(origin.size()); | ||||
|             this.pending = new HashSet<>(3); | ||||
|         } | ||||
| 
 | ||||
|         @NonNull | ||||
|         @Override | ||||
|         public <P extends MarkwonPlugin> P require(@NonNull Class<P> plugin) { | ||||
|             return get(plugin); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public <P extends MarkwonPlugin> void require( | ||||
|                 @NonNull Class<P> plugin, | ||||
|                 @NonNull MarkwonPlugin.Action<? super P> action) { | ||||
|             action.apply(get(plugin)); | ||||
|         } | ||||
| 
 | ||||
|         @NonNull | ||||
|         List<MarkwonPlugin> process() { | ||||
|             for (MarkwonPlugin plugin : origin) { | ||||
|                 configure(plugin); | ||||
|             } | ||||
|             return plugins; | ||||
|         } | ||||
| 
 | ||||
|         private void configure(@NonNull MarkwonPlugin plugin) { | ||||
| 
 | ||||
|             // important -> check if it's in plugins | ||||
|             //  if it is -> no need to configure (already configured) | ||||
| 
 | ||||
|             if (!plugins.contains(plugin)) { | ||||
| 
 | ||||
|                 if (pending.contains(plugin)) { | ||||
|                     throw new IllegalStateException("Cyclic dependency chain found: " + pending); | ||||
|                 } | ||||
| 
 | ||||
|                 // start tracking plugins that are pending for configuration | ||||
|                 pending.add(plugin); | ||||
| 
 | ||||
|                 plugin.configure(this); | ||||
| 
 | ||||
|                 // stop pending tracking | ||||
|                 pending.remove(plugin); | ||||
| 
 | ||||
|                 // check again if it's included (a child might've configured it already) | ||||
|                 // add to out-collection if not already present | ||||
|                 // this is a bit different from `find` method as it does check for exact instance | ||||
|                 // and not a sub-type | ||||
|                 if (!plugins.contains(plugin)) { | ||||
|                     plugins.add(plugin); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @NonNull | ||||
|         private <P extends MarkwonPlugin> P get(@NonNull Class<P> type) { | ||||
| 
 | ||||
|             // check if present already in plugins | ||||
|             // find in origin, if not found -> throw, else add to out-plugins | ||||
| 
 | ||||
|             P plugin = find(plugins, type); | ||||
| 
 | ||||
|             if (plugin == null) { | ||||
| 
 | ||||
|                 plugin = find(origin, type); | ||||
| 
 | ||||
|                 if (plugin == null) { | ||||
|                     throw new IllegalStateException("Requested plugin is not added: " + | ||||
|                             "" + type.getName() + ", plugins: " + origin); | ||||
|                 } | ||||
| 
 | ||||
|                 configure(plugin); | ||||
|             } | ||||
| 
 | ||||
|             return plugin; | ||||
|         } | ||||
| 
 | ||||
|         @Nullable | ||||
|         private static <P extends MarkwonPlugin> P find( | ||||
|                 @NonNull List<MarkwonPlugin> plugins, | ||||
|                 @NonNull Class<P> type) { | ||||
|             for (MarkwonPlugin plugin : plugins) { | ||||
|                 if (type.isAssignableFrom(plugin.getClass())) { | ||||
|                     //noinspection unchecked | ||||
|                     return (P) plugin; | ||||
|                 } | ||||
|             } | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -4,8 +4,6 @@ import android.support.annotation.NonNull; | ||||
| 
 | ||||
| import io.noties.markwon.core.MarkwonTheme; | ||||
| import io.noties.markwon.core.spans.LinkSpan; | ||||
| import io.noties.markwon.html.MarkwonHtmlParser; | ||||
| import io.noties.markwon.html.MarkwonHtmlRenderer; | ||||
| import io.noties.markwon.image.AsyncDrawableLoader; | ||||
| import io.noties.markwon.image.ImageSizeResolver; | ||||
| import io.noties.markwon.image.ImageSizeResolverDef; | ||||
| @ -31,8 +29,6 @@ public class MarkwonConfiguration { | ||||
|     private final LinkSpan.Resolver linkResolver; | ||||
|     private final UrlProcessor urlProcessor; | ||||
|     private final ImageSizeResolver imageSizeResolver; | ||||
|     private final MarkwonHtmlParser htmlParser; | ||||
|     private final MarkwonHtmlRenderer htmlRenderer; | ||||
| 
 | ||||
|     // @since 3.0.0 | ||||
|     private final MarkwonSpansFactory spansFactory; | ||||
| @ -45,8 +41,6 @@ public class MarkwonConfiguration { | ||||
|         this.urlProcessor = builder.urlProcessor; | ||||
|         this.imageSizeResolver = builder.imageSizeResolver; | ||||
|         this.spansFactory = builder.spansFactory; | ||||
|         this.htmlParser = builder.htmlParser; | ||||
|         this.htmlRenderer = builder.htmlRenderer; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
| @ -79,16 +73,6 @@ public class MarkwonConfiguration { | ||||
|         return imageSizeResolver; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public MarkwonHtmlParser htmlParser() { | ||||
|         return htmlParser; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public MarkwonHtmlRenderer htmlRenderer() { | ||||
|         return htmlRenderer; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @since 3.0.0 | ||||
|      */ | ||||
| @ -106,8 +90,6 @@ public class MarkwonConfiguration { | ||||
|         private LinkSpan.Resolver linkResolver; | ||||
|         private UrlProcessor urlProcessor; | ||||
|         private ImageSizeResolver imageSizeResolver; | ||||
|         private MarkwonHtmlParser htmlParser; | ||||
|         private MarkwonHtmlRenderer htmlRenderer; | ||||
|         private MarkwonSpansFactory spansFactory; | ||||
| 
 | ||||
|         Builder() { | ||||
| @ -122,15 +104,6 @@ public class MarkwonConfiguration { | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * @since 4.0.0-SNAPSHOT | ||||
|          */ | ||||
|         @NonNull | ||||
|         public Builder htmlRenderer(@NonNull MarkwonHtmlRenderer htmlRenderer) { | ||||
|             this.htmlRenderer = htmlRenderer; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         @NonNull | ||||
|         public Builder syntaxHighlight(@NonNull SyntaxHighlight syntaxHighlight) { | ||||
|             this.syntaxHighlight = syntaxHighlight; | ||||
| @ -149,12 +122,6 @@ public class MarkwonConfiguration { | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         @NonNull | ||||
|         public Builder htmlParser(@NonNull MarkwonHtmlParser htmlParser) { | ||||
|             this.htmlParser = htmlParser; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * @since 1.0.1 | ||||
|          */ | ||||
| @ -177,11 +144,6 @@ public class MarkwonConfiguration { | ||||
|                 asyncDrawableLoader = AsyncDrawableLoader.noOp(); | ||||
|             } | ||||
| 
 | ||||
|             // @since 4.0.0-SNAPSHOT | ||||
|             if (htmlRenderer == null) { | ||||
|                 htmlRenderer = MarkwonHtmlRenderer.noOp(); | ||||
|             } | ||||
| 
 | ||||
|             if (syntaxHighlight == null) { | ||||
|                 syntaxHighlight = new SyntaxHighlightNoOp(); | ||||
|             } | ||||
| @ -198,10 +160,6 @@ public class MarkwonConfiguration { | ||||
|                 imageSizeResolver = new ImageSizeResolverDef(); | ||||
|             } | ||||
| 
 | ||||
|             if (htmlParser == null) { | ||||
|                 htmlParser = MarkwonHtmlParser.noOp(); | ||||
|             } | ||||
| 
 | ||||
|             return new MarkwonConfiguration(this); | ||||
|         } | ||||
|     } | ||||
|  | ||||
							
								
								
									
										118
									
								
								markwon-core/src/main/java/io/noties/markwon/RegistryImpl.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								markwon-core/src/main/java/io/noties/markwon/RegistryImpl.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,118 @@ | ||||
| package io.noties.markwon; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashSet; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| import io.noties.markwon.core.CorePlugin; | ||||
| 
 | ||||
| // @since 4.0.0-SNAPSHOT | ||||
| class RegistryImpl implements MarkwonPlugin.Registry { | ||||
| 
 | ||||
|     // todo: core-plugin must be (better be) first any way | ||||
| 
 | ||||
|     private final List<MarkwonPlugin> origin; | ||||
|     private final List<MarkwonPlugin> plugins; | ||||
|     private final Set<MarkwonPlugin> pending; | ||||
| 
 | ||||
|     RegistryImpl(@NonNull List<MarkwonPlugin> origin) { | ||||
|         this.origin = origin; | ||||
|         this.plugins = new ArrayList<>(origin.size()); | ||||
|         this.pending = new HashSet<>(3); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public <P extends MarkwonPlugin> P require(@NonNull Class<P> plugin) { | ||||
|         return get(plugin); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public <P extends MarkwonPlugin> void require( | ||||
|             @NonNull Class<P> plugin, | ||||
|             @NonNull MarkwonPlugin.Action<? super P> action) { | ||||
|         action.apply(get(plugin)); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     List<MarkwonPlugin> process() { | ||||
|         for (MarkwonPlugin plugin : origin) { | ||||
|             configure(plugin); | ||||
|         } | ||||
|         return plugins; | ||||
|     } | ||||
| 
 | ||||
|     private void configure(@NonNull MarkwonPlugin plugin) { | ||||
| 
 | ||||
|         // important -> check if it's in plugins | ||||
|         //  if it is -> no need to configure (already configured) | ||||
| 
 | ||||
|         if (!plugins.contains(plugin)) { | ||||
| 
 | ||||
|             if (pending.contains(plugin)) { | ||||
|                 throw new IllegalStateException("Cyclic dependency chain found: " + pending); | ||||
|             } | ||||
| 
 | ||||
|             // start tracking plugins that are pending for configuration | ||||
|             pending.add(plugin); | ||||
| 
 | ||||
|             plugin.configure(this); | ||||
| 
 | ||||
|             // stop pending tracking | ||||
|             pending.remove(plugin); | ||||
| 
 | ||||
|             // check again if it's included (a child might've configured it already) | ||||
|             // add to out-collection if not already present | ||||
|             // this is a bit different from `find` method as it does check for exact instance | ||||
|             // and not a sub-type | ||||
|             if (!plugins.contains(plugin)) { | ||||
|                 // core-plugin must always be the first one (if it's present) | ||||
|                 if (CorePlugin.class.isAssignableFrom(plugin.getClass())) { | ||||
|                     plugins.add(0, plugin); | ||||
|                 } else { | ||||
|                     plugins.add(plugin); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private <P extends MarkwonPlugin> P get(@NonNull Class<P> type) { | ||||
| 
 | ||||
|         // check if present already in plugins | ||||
|         // find in origin, if not found -> throw, else add to out-plugins | ||||
| 
 | ||||
|         P plugin = find(plugins, type); | ||||
| 
 | ||||
|         if (plugin == null) { | ||||
| 
 | ||||
|             plugin = find(origin, type); | ||||
| 
 | ||||
|             if (plugin == null) { | ||||
|                 throw new IllegalStateException("Requested plugin is not added: " + | ||||
|                         "" + type.getName() + ", plugins: " + origin); | ||||
|             } | ||||
| 
 | ||||
|             configure(plugin); | ||||
|         } | ||||
| 
 | ||||
|         return plugin; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     private static <P extends MarkwonPlugin> P find( | ||||
|             @NonNull List<MarkwonPlugin> plugins, | ||||
|             @NonNull Class<P> type) { | ||||
|         for (MarkwonPlugin plugin : plugins) { | ||||
|             if (type.isAssignableFrom(plugin.getClass())) { | ||||
|                 //noinspection unchecked | ||||
|                 return (P) plugin; | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
| @ -1,27 +0,0 @@ | ||||
| package io.noties.markwon.html; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| 
 | ||||
| class MarkwonHtmlParserNoOp extends MarkwonHtmlParser { | ||||
|     @Override | ||||
|     public <T extends Appendable & CharSequence> void processFragment(@NonNull T output, @NonNull String htmlFragment) { | ||||
|         // no op | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void flushInlineTags(int documentLength, @NonNull FlushAction<HtmlTag.Inline> action) { | ||||
|         action.apply(Collections.<HtmlTag.Inline>emptyList()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void flushBlockTags(int documentLength, @NonNull FlushAction<HtmlTag.Block> action) { | ||||
|         action.apply(Collections.<HtmlTag.Block>emptyList()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void reset() { | ||||
|         // no op | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,15 @@ | ||||
| package io.noties.markwon; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| 
 | ||||
| import org.junit.Assert; | ||||
| 
 | ||||
| public abstract class MarkwonAssert { | ||||
| 
 | ||||
|     public static void assertMessageContains(@NonNull Throwable t, @NonNull String contains) { | ||||
|         Assert.assertTrue(t.getMessage(), t.getMessage().contains(contains)); | ||||
|     } | ||||
| 
 | ||||
|     private MarkwonAssert() { | ||||
|     } | ||||
| } | ||||
| @ -12,7 +12,6 @@ import org.robolectric.RuntimeEnvironment; | ||||
| import org.robolectric.annotation.Config; | ||||
| 
 | ||||
| import io.noties.markwon.core.MarkwonTheme; | ||||
| import io.noties.markwon.html.MarkwonHtmlRenderer; | ||||
| 
 | ||||
| import static org.hamcrest.Matchers.containsString; | ||||
| import static org.junit.Assert.assertThat; | ||||
| @ -27,39 +26,6 @@ import static org.mockito.Mockito.verify; | ||||
| @Config(manifest = Config.NONE) | ||||
| public class MarkwonBuilderImplTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void prepare_plugins() { | ||||
|         // validate that prepare plugins is calling `ensureImplicitCoreIfHasDependents` and | ||||
|         // priority processor | ||||
| 
 | ||||
| //        final PriorityProcessor priorityProcessor = mock(PriorityProcessor.class); | ||||
| //        when(priorityProcessor.process(ArgumentMatchers.<MarkwonPlugin>anyList())) | ||||
| //                .thenAnswer(new Answer<Object>() { | ||||
| //                    @Override | ||||
| //                    public Object answer(InvocationOnMock invocation) { | ||||
| //                        return invocation.getArgument(0); | ||||
| //                    } | ||||
| //                }); | ||||
| // | ||||
| //        final MarkwonPlugin plugin = new AbstractMarkwonPlugin() { | ||||
| //            @NonNull | ||||
| //            @Override | ||||
| //            public Priority priority() { | ||||
| //                return Priority.after(CorePlugin.class); | ||||
| //            } | ||||
| //        }; | ||||
| // | ||||
| //        final List<MarkwonPlugin> plugins = preparePlugins(priorityProcessor, Collections.singletonList(plugin)); | ||||
| //        assertThat(plugins, hasSize(2)); | ||||
| //        assertThat(plugins, hasItem(plugin)); | ||||
| //        assertThat(plugins, hasItem(isA(CorePlugin.class))); | ||||
| // | ||||
| //        verify(priorityProcessor, times(1)) | ||||
| //                .process(ArgumentMatchers.<MarkwonPlugin>anyList()); | ||||
| 
 | ||||
|         fail(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void no_plugins_added_throws() { | ||||
|         // there is no sense in having an instance with no plugins registered | ||||
|  | ||||
| @ -0,0 +1,194 @@ | ||||
| package io.noties.markwon; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.robolectric.RobolectricTestRunner; | ||||
| import org.robolectric.annotation.Config; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import io.noties.markwon.core.CorePlugin; | ||||
| 
 | ||||
| import static io.noties.markwon.MarkwonAssert.assertMessageContains; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.fail; | ||||
| import static org.mockito.Mockito.mock; | ||||
| 
 | ||||
| @RunWith(RobolectricTestRunner.class) | ||||
| @Config(manifest = Config.NONE) | ||||
| public class RegistryImplTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void single_plugin_requires_self() { | ||||
|         // detect recursive require | ||||
| 
 | ||||
|         final class Plugin extends AbstractMarkwonPlugin { | ||||
|             @Override | ||||
|             public void configure(@NonNull Registry registry) { | ||||
|                 registry.require(Plugin.class); | ||||
|             } | ||||
|         } | ||||
|         final MarkwonPlugin plugin = new Plugin(); | ||||
| 
 | ||||
|         final RegistryImpl impl = new RegistryImpl(Collections.singletonList(plugin)); | ||||
| 
 | ||||
|         try { | ||||
|             impl.process(); | ||||
|         } catch (Throwable t) { | ||||
|             assertMessageContains(t, "Cyclic dependency chain found"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void plugins_dependency_cycle() { | ||||
| 
 | ||||
|         final Map<String, Class<? extends MarkwonPlugin>> map = new HashMap<>(); | ||||
| 
 | ||||
|         final class A extends AbstractMarkwonPlugin { | ||||
|             @Override | ||||
|             public void configure(@NonNull Registry registry) { | ||||
|                 //noinspection ConstantConditions | ||||
|                 registry.require(map.get("A")); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         final class B extends AbstractMarkwonPlugin { | ||||
|             @Override | ||||
|             public void configure(@NonNull Registry registry) { | ||||
|                 //noinspection ConstantConditions | ||||
|                 registry.require(map.get("B")); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         final class C extends AbstractMarkwonPlugin { | ||||
|             @Override | ||||
|             public void configure(@NonNull Registry registry) { | ||||
|                 //noinspection ConstantConditions | ||||
|                 registry.require(map.get("C")); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         map.put("A", B.class); | ||||
|         map.put("B", C.class); | ||||
|         map.put("C", A.class); | ||||
| 
 | ||||
|         final RegistryImpl impl = | ||||
|                 new RegistryImpl(Arrays.asList((MarkwonPlugin) new A(), new B(), new C())); | ||||
| 
 | ||||
|         try { | ||||
|             impl.process(); | ||||
|             fail(); | ||||
|         } catch (Throwable t) { | ||||
|             assertMessageContains(t, "Cyclic dependency chain found"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void plugins_no_dependency_cycle() { | ||||
| 
 | ||||
|         final class C extends AbstractMarkwonPlugin { | ||||
|         } | ||||
| 
 | ||||
|         final class B extends AbstractMarkwonPlugin { | ||||
|             @Override | ||||
|             public void configure(@NonNull Registry registry) { | ||||
|                 registry.require(C.class); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         final class A extends AbstractMarkwonPlugin { | ||||
|             @Override | ||||
|             public void configure(@NonNull Registry registry) { | ||||
|                 registry.require(B.class); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         final RegistryImpl impl = | ||||
|                 new RegistryImpl(Arrays.asList((MarkwonPlugin) new A(), new B(), new C())); | ||||
| 
 | ||||
|         impl.process(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void dependency_not_satisfied() { | ||||
|         // when require is called for plugin not added | ||||
| 
 | ||||
|         final class A extends AbstractMarkwonPlugin { | ||||
|         } | ||||
| 
 | ||||
|         final class B extends AbstractMarkwonPlugin { | ||||
|             @Override | ||||
|             public void configure(@NonNull Registry registry) { | ||||
|                 registry.require(A.class); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         final RegistryImpl impl = | ||||
|                 new RegistryImpl(Collections.singletonList((MarkwonPlugin) new B())); | ||||
| 
 | ||||
|         try { | ||||
|             impl.process(); | ||||
|             fail(); | ||||
|         } catch (Throwable t) { | ||||
|             assertMessageContains(t, "Requested plugin is not added"); | ||||
|             assertMessageContains(t, A.class.getName()); // ? if it's null for local class? | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void core_plugin_first() { | ||||
|         // if core-plugin is present, hen it should be the first one | ||||
| 
 | ||||
|         final CorePlugin plugin = CorePlugin.create(); | ||||
| 
 | ||||
|         final RegistryImpl impl = new RegistryImpl(Arrays.asList( | ||||
|                 mock(MarkwonPlugin.class), | ||||
|                 mock(MarkwonPlugin.class), | ||||
|                 plugin | ||||
|         )); | ||||
| 
 | ||||
|         final List<MarkwonPlugin> plugins = impl.process(); | ||||
|         assertEquals(3, plugins.size()); | ||||
|         assertEquals(plugin, plugins.get(0)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void correct_order() { | ||||
| 
 | ||||
|         final class A extends AbstractMarkwonPlugin { | ||||
|         } | ||||
| 
 | ||||
|         final class B extends AbstractMarkwonPlugin { | ||||
|             @Override | ||||
|             public void configure(@NonNull Registry registry) { | ||||
|                 registry.require(A.class); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         final class C extends AbstractMarkwonPlugin { | ||||
|             @Override | ||||
|             public void configure(@NonNull Registry registry) { | ||||
|                 registry.require(B.class); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         final A a = new A(); | ||||
|         final B b = new B(); | ||||
|         final C c = new C(); | ||||
| 
 | ||||
|         final RegistryImpl impl = new RegistryImpl(Arrays.asList( | ||||
|                 (MarkwonPlugin) c, b, a)); | ||||
| 
 | ||||
|         final List<MarkwonPlugin> plugins = impl.process(); | ||||
|         assertEquals(3, plugins.size()); | ||||
|         assertEquals(a, plugins.get(0)); | ||||
|         assertEquals(b, plugins.get(1)); | ||||
|         assertEquals(c, plugins.get(2)); | ||||
|     } | ||||
| } | ||||
| @ -7,13 +7,13 @@ import org.commonmark.node.HtmlBlock; | ||||
| import org.commonmark.node.HtmlInline; | ||||
| import org.commonmark.node.Node; | ||||
| 
 | ||||
| import io.noties.markwon.html.tag.ImageHandler; | ||||
| import io.noties.markwon.AbstractMarkwonPlugin; | ||||
| import io.noties.markwon.MarkwonConfiguration; | ||||
| import io.noties.markwon.MarkwonVisitor; | ||||
| import io.noties.markwon.html.tag.BlockquoteHandler; | ||||
| import io.noties.markwon.html.tag.EmphasisHandler; | ||||
| import io.noties.markwon.html.tag.HeadingHandler; | ||||
| import io.noties.markwon.html.tag.ImageHandler; | ||||
| import io.noties.markwon.html.tag.LinkHandler; | ||||
| import io.noties.markwon.html.tag.ListHandler; | ||||
| import io.noties.markwon.html.tag.StrikeHandler; | ||||
| @ -53,10 +53,13 @@ public class HtmlPlugin extends AbstractMarkwonPlugin { | ||||
|     public static final float SCRIPT_DEF_TEXT_SIZE_RATIO = .75F; | ||||
| 
 | ||||
|     private final MarkwonHtmlRendererImpl.Builder builder; | ||||
|     private final MarkwonHtmlParser htmlParser; | ||||
|     private MarkwonHtmlRenderer htmlRenderer; | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     HtmlPlugin() { | ||||
|         this.builder = new MarkwonHtmlRendererImpl.Builder(); | ||||
|         this.htmlParser = MarkwonHtmlParserImpl.create(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -104,6 +107,8 @@ public class HtmlPlugin extends AbstractMarkwonPlugin { | ||||
|     @Override | ||||
|     public void configureConfiguration(@NonNull MarkwonConfiguration.Builder configurationBuilder) { | ||||
| 
 | ||||
|         // @since 4.0.0-SNAPSHOT we init internal html-renderer here (marks the end of configuration) | ||||
| 
 | ||||
|         final MarkwonHtmlRendererImpl.Builder builder = this.builder; | ||||
| 
 | ||||
|         if (!builder.excludeDefaults()) { | ||||
| @ -123,15 +128,17 @@ public class HtmlPlugin extends AbstractMarkwonPlugin { | ||||
|             builder.addDefaultTagHandler(new HeadingHandler()); | ||||
|         } | ||||
| 
 | ||||
|         configurationBuilder | ||||
|                 .htmlRenderer(builder.build()) | ||||
|                 .htmlParser(MarkwonHtmlParserImpl.create()); | ||||
|         htmlRenderer = builder.build(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor) { | ||||
|         final MarkwonConfiguration configuration = visitor.configuration(); | ||||
|         configuration.htmlRenderer().render(visitor, configuration.htmlParser()); | ||||
|         final MarkwonHtmlRenderer htmlRenderer = this.htmlRenderer; | ||||
|         if (htmlRenderer != null) { | ||||
|             htmlRenderer.render(visitor, htmlParser); | ||||
|         } else { | ||||
|             throw new IllegalStateException("Unexpected state, html-renderer is not defined"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @ -153,7 +160,7 @@ public class HtmlPlugin extends AbstractMarkwonPlugin { | ||||
| 
 | ||||
|     private void visitHtml(@NonNull MarkwonVisitor visitor, @Nullable String html) { | ||||
|         if (html != null) { | ||||
|             visitor.configuration().htmlParser().processFragment(visitor.builder(), html); | ||||
|             htmlParser.processFragment(visitor.builder(), html); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -9,14 +9,6 @@ import java.util.List; | ||||
|  */ | ||||
| public abstract class MarkwonHtmlParser { | ||||
| 
 | ||||
|     /** | ||||
|      * Factory method to create a `no-op` implementation (no parsing) | ||||
|      */ | ||||
|     @NonNull | ||||
|     public static MarkwonHtmlParser noOp() { | ||||
|         return new MarkwonHtmlParserNoOp(); | ||||
|     } | ||||
| 
 | ||||
|     public interface FlushAction<T> { | ||||
|         void apply(@NonNull List<T> tags); | ||||
|     } | ||||
| @ -10,14 +10,6 @@ import io.noties.markwon.MarkwonVisitor; | ||||
|  */ | ||||
| public abstract class MarkwonHtmlRenderer { | ||||
| 
 | ||||
|     /** | ||||
|      * @since 4.0.0-SNAPSHOT | ||||
|      */ | ||||
|     @NonNull | ||||
|     public static MarkwonHtmlRenderer noOp() { | ||||
|         return new MarkwonHtmlRendererNoOp(); | ||||
|     } | ||||
| 
 | ||||
|     public abstract void render( | ||||
|             @NonNull MarkwonVisitor visitor, | ||||
|             @NonNull MarkwonHtmlParser parser | ||||
| @ -3,6 +3,7 @@ package io.noties.markwon.image; | ||||
| import android.graphics.drawable.Drawable; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.annotation.VisibleForTesting; | ||||
| import android.text.Spanned; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| @ -10,16 +11,16 @@ import org.commonmark.node.Image; | ||||
| 
 | ||||
| import java.util.concurrent.ExecutorService; | ||||
| 
 | ||||
| import io.noties.markwon.AbstractMarkwonPlugin; | ||||
| import io.noties.markwon.MarkwonConfiguration; | ||||
| import io.noties.markwon.MarkwonPlugin; | ||||
| import io.noties.markwon.MarkwonSpansFactory; | ||||
| import io.noties.markwon.image.data.DataUriSchemeHandler; | ||||
| import io.noties.markwon.image.file.FileSchemeHandler; | ||||
| import io.noties.markwon.image.gif.GifMediaDecoder; | ||||
| import io.noties.markwon.image.network.NetworkSchemeHandler; | ||||
| import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler; | ||||
| import io.noties.markwon.image.svg.SvgMediaDecoder; | ||||
| import io.noties.markwon.AbstractMarkwonPlugin; | ||||
| import io.noties.markwon.MarkwonConfiguration; | ||||
| import io.noties.markwon.MarkwonSpansFactory; | ||||
| 
 | ||||
| @SuppressWarnings({"UnusedReturnValue", "WeakerAccess"}) | ||||
| public class ImagesPlugin extends AbstractMarkwonPlugin { | ||||
| @ -70,7 +71,18 @@ public class ImagesPlugin extends AbstractMarkwonPlugin { | ||||
|         return plugin; | ||||
|     } | ||||
| 
 | ||||
|     private final AsyncDrawableLoaderBuilder builder = new AsyncDrawableLoaderBuilder(); | ||||
|     private final AsyncDrawableLoaderBuilder builder; | ||||
| 
 | ||||
|     // @since 4.0.0-SNAPSHOT | ||||
|     ImagesPlugin() { | ||||
|         this(new AsyncDrawableLoaderBuilder()); | ||||
|     } | ||||
| 
 | ||||
|     // @since 4.0.0-SNAPSHOT | ||||
|     @VisibleForTesting | ||||
|     ImagesPlugin(@NonNull AsyncDrawableLoaderBuilder builder) { | ||||
|         this.builder = builder; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Optional (by default new cached thread executor will be used) | ||||
|  | ||||
| @ -18,6 +18,7 @@ import static org.junit.Assert.assertFalse; | ||||
| 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; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| @ -203,4 +204,16 @@ public class AsyncDrawableLoaderBuilderTest { | ||||
|         builder.removeMediaDecoder(media); | ||||
|         assertNull(builder.mediaDecoders.get(media)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void cannot_build_twice() { | ||||
| 
 | ||||
|         builder.build(); | ||||
|         try { | ||||
|             builder.build(); | ||||
|             fail(); | ||||
|         } catch (Throwable t) { | ||||
|             assertTrue(t.getMessage(), t.getMessage().contains("has already been configured")); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -27,6 +27,7 @@ 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.verifyNoMoreInteractions; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| @RunWith(RobolectricTestRunner.class) | ||||
| @ -175,4 +176,70 @@ public class ImagesPluginTest { | ||||
|         verify(textView, times(1)) | ||||
|                 .getTag(eq(R.id.markwon_drawables_scheduler_last_text_hashcode)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void methods_redirected_to_builder() { | ||||
| 
 | ||||
|         final AsyncDrawableLoaderBuilder builder = mock(AsyncDrawableLoaderBuilder.class); | ||||
|         final ImagesPlugin plugin = new ImagesPlugin(builder); | ||||
| 
 | ||||
|         // executor service | ||||
|         { | ||||
|             final ExecutorService executorService = mock(ExecutorService.class); | ||||
|             plugin.executorService(executorService); | ||||
|             verify(builder, times(1)).executorService(eq(executorService)); | ||||
|         } | ||||
| 
 | ||||
|         // add scheme-handler | ||||
|         { | ||||
|             final SchemeHandler schemeHandler = mock(SchemeHandler.class); | ||||
|             plugin.addSchemeHandler(schemeHandler); | ||||
|             verify(builder, times(1)).addSchemeHandler(eq(schemeHandler)); | ||||
|         } | ||||
| 
 | ||||
|         // add media-decoder | ||||
|         { | ||||
|             final MediaDecoder mediaDecoder = mock(MediaDecoder.class); | ||||
|             plugin.addMediaDecoder(mediaDecoder); | ||||
|             verify(builder, times(1)).addMediaDecoder(eq(mediaDecoder)); | ||||
|         } | ||||
| 
 | ||||
|         // default-media-decoder | ||||
|         { | ||||
|             final MediaDecoder mediaDecoder = mock(MediaDecoder.class); | ||||
|             plugin.defaultMediaDecoder(mediaDecoder); | ||||
|             verify(builder, times(1)).defaultMediaDecoder(eq(mediaDecoder)); | ||||
|         } | ||||
| 
 | ||||
|         // remove scheme-handler | ||||
|         { | ||||
|             final String scheme = "yo"; | ||||
|             plugin.removeSchemeHandler(scheme); | ||||
|             verify(builder, times(1)).removeSchemeHandler(eq(scheme)); | ||||
|         } | ||||
| 
 | ||||
|         // remove media-decoder | ||||
|         { | ||||
|             final String contentType = "fa/ke"; | ||||
|             plugin.removeMediaDecoder(contentType); | ||||
|             verify(builder, times(1)).removeMediaDecoder(eq(contentType)); | ||||
|         } | ||||
| 
 | ||||
|         // placeholder provider | ||||
|         { | ||||
|             final ImagesPlugin.PlaceholderProvider placeholderProvider = | ||||
|                     mock(ImagesPlugin.PlaceholderProvider.class); | ||||
|             plugin.placeholderProvider(placeholderProvider); | ||||
|             verify(builder, times(1)).placeholderProvider(eq(placeholderProvider)); | ||||
|         } | ||||
| 
 | ||||
|         // error-handler | ||||
|         { | ||||
|             final ImagesPlugin.ErrorHandler errorHandler = mock(ImagesPlugin.ErrorHandler.class); | ||||
|             plugin.errorHandler(errorHandler); | ||||
|             verify(builder, times(1)).errorHandler(eq(errorHandler)); | ||||
|         } | ||||
| 
 | ||||
|         verifyNoMoreInteractions(builder); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dimitry Ivanov
						Dimitry Ivanov