registryImpl test
This commit is contained in:
		
							parent
							
								
									173425ed53
								
							
						
					
					
						commit
						a2a5857f06
					
				@ -9,3 +9,4 @@
 | 
				
			|||||||
* images-plugin moved to standalone again
 | 
					* images-plugin moved to standalone again
 | 
				
			||||||
* removed MarkwonPlugin#configureHtmlRenderer -> now part of HtmlPlugin
 | 
					* 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 javax.inject.Inject;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io.noties.debug.Debug;
 | 
				
			||||||
import io.noties.markwon.AbstractMarkwonPlugin;
 | 
					import io.noties.markwon.AbstractMarkwonPlugin;
 | 
				
			||||||
import io.noties.markwon.Markwon;
 | 
					import io.noties.markwon.Markwon;
 | 
				
			||||||
import io.noties.markwon.MarkwonConfiguration;
 | 
					import io.noties.markwon.MarkwonConfiguration;
 | 
				
			||||||
 | 
					import io.noties.markwon.app.gif.GifAwarePlugin;
 | 
				
			||||||
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
 | 
					import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
 | 
				
			||||||
import io.noties.markwon.ext.tables.TablePlugin;
 | 
					import io.noties.markwon.ext.tables.TablePlugin;
 | 
				
			||||||
import io.noties.markwon.ext.tasklist.TaskListPlugin;
 | 
					import io.noties.markwon.ext.tasklist.TaskListPlugin;
 | 
				
			||||||
import io.noties.markwon.app.gif.GifAwarePlugin;
 | 
					 | 
				
			||||||
import io.noties.markwon.html.HtmlPlugin;
 | 
					import io.noties.markwon.html.HtmlPlugin;
 | 
				
			||||||
import io.noties.markwon.image.ImagesPlugin;
 | 
					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.file.FileSchemeHandler;
 | 
				
			||||||
import io.noties.markwon.image.gif.GifMediaDecoder;
 | 
					 | 
				
			||||||
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler;
 | 
					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.Prism4jTheme;
 | 
				
			||||||
import io.noties.markwon.syntax.Prism4jThemeDarkula;
 | 
					import io.noties.markwon.syntax.Prism4jThemeDarkula;
 | 
				
			||||||
import io.noties.markwon.syntax.Prism4jThemeDefault;
 | 
					import io.noties.markwon.syntax.Prism4jThemeDefault;
 | 
				
			||||||
import io.noties.markwon.syntax.SyntaxHighlightPlugin;
 | 
					import io.noties.markwon.syntax.SyntaxHighlightPlugin;
 | 
				
			||||||
import io.noties.markwon.urlprocessor.UrlProcessor;
 | 
					import io.noties.markwon.urlprocessor.UrlProcessor;
 | 
				
			||||||
import io.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
 | 
					import io.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
 | 
				
			||||||
import io.noties.debug.Debug;
 | 
					 | 
				
			||||||
import ru.noties.prism4j.Prism4j;
 | 
					import ru.noties.prism4j.Prism4j;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ActivityScope
 | 
					@ActivityScope
 | 
				
			||||||
@ -102,12 +99,12 @@ public class MarkdownRenderer {
 | 
				
			|||||||
                        .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
 | 
					                        .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
 | 
				
			||||||
                            @Override
 | 
					                            @Override
 | 
				
			||||||
                            public void configureImages(@NonNull ImagesPlugin plugin) {
 | 
					                            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
 | 
					                                plugin
 | 
				
			||||||
                                        .addSchemeHandler(DataUriSchemeHandler.create())
 | 
					 | 
				
			||||||
                                        .addSchemeHandler(OkHttpNetworkSchemeHandler.create())
 | 
					                                        .addSchemeHandler(OkHttpNetworkSchemeHandler.create())
 | 
				
			||||||
                                        .addSchemeHandler(FileSchemeHandler.createWithAssets(context.getAssets()))
 | 
					                                        .addSchemeHandler(FileSchemeHandler.createWithAssets(context.getAssets()));
 | 
				
			||||||
                                        .addMediaDecoder(GifMediaDecoder.create(false))
 | 
					 | 
				
			||||||
                                        .addMediaDecoder(SvgMediaDecoder.create());
 | 
					 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }))
 | 
					                        }))
 | 
				
			||||||
                        .usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
 | 
					                        .usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,7 @@ buildscript {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    dependencies {
 | 
					    dependencies {
 | 
				
			||||||
        classpath 'com.android.tools.build:gradle:3.4.1'
 | 
					        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.content.Context;
 | 
				
			||||||
import android.support.annotation.NonNull;
 | 
					import android.support.annotation.NonNull;
 | 
				
			||||||
import android.support.annotation.Nullable;
 | 
					 | 
				
			||||||
import android.support.annotation.VisibleForTesting;
 | 
					 | 
				
			||||||
import android.widget.TextView;
 | 
					import android.widget.TextView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.commonmark.parser.Parser;
 | 
					import org.commonmark.parser.Parser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.Collections;
 | 
					import java.util.Collections;
 | 
				
			||||||
import java.util.HashSet;
 | 
					 | 
				
			||||||
import java.util.Iterator;
 | 
					import java.util.Iterator;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Set;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import io.noties.markwon.core.MarkwonTheme;
 | 
					import io.noties.markwon.core.MarkwonTheme;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -106,109 +102,8 @@ class MarkwonBuilderImpl implements Markwon.Builder {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @VisibleForTesting
 | 
					 | 
				
			||||||
    @NonNull
 | 
					    @NonNull
 | 
				
			||||||
    static List<MarkwonPlugin> preparePlugins(@NonNull List<MarkwonPlugin> plugins) {
 | 
					    private static List<MarkwonPlugin> preparePlugins(@NonNull List<MarkwonPlugin> plugins) {
 | 
				
			||||||
        return new RegistryImpl(plugins).process();
 | 
					        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.MarkwonTheme;
 | 
				
			||||||
import io.noties.markwon.core.spans.LinkSpan;
 | 
					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.AsyncDrawableLoader;
 | 
				
			||||||
import io.noties.markwon.image.ImageSizeResolver;
 | 
					import io.noties.markwon.image.ImageSizeResolver;
 | 
				
			||||||
import io.noties.markwon.image.ImageSizeResolverDef;
 | 
					import io.noties.markwon.image.ImageSizeResolverDef;
 | 
				
			||||||
@ -31,8 +29,6 @@ public class MarkwonConfiguration {
 | 
				
			|||||||
    private final LinkSpan.Resolver linkResolver;
 | 
					    private final LinkSpan.Resolver linkResolver;
 | 
				
			||||||
    private final UrlProcessor urlProcessor;
 | 
					    private final UrlProcessor urlProcessor;
 | 
				
			||||||
    private final ImageSizeResolver imageSizeResolver;
 | 
					    private final ImageSizeResolver imageSizeResolver;
 | 
				
			||||||
    private final MarkwonHtmlParser htmlParser;
 | 
					 | 
				
			||||||
    private final MarkwonHtmlRenderer htmlRenderer;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // @since 3.0.0
 | 
					    // @since 3.0.0
 | 
				
			||||||
    private final MarkwonSpansFactory spansFactory;
 | 
					    private final MarkwonSpansFactory spansFactory;
 | 
				
			||||||
@ -45,8 +41,6 @@ public class MarkwonConfiguration {
 | 
				
			|||||||
        this.urlProcessor = builder.urlProcessor;
 | 
					        this.urlProcessor = builder.urlProcessor;
 | 
				
			||||||
        this.imageSizeResolver = builder.imageSizeResolver;
 | 
					        this.imageSizeResolver = builder.imageSizeResolver;
 | 
				
			||||||
        this.spansFactory = builder.spansFactory;
 | 
					        this.spansFactory = builder.spansFactory;
 | 
				
			||||||
        this.htmlParser = builder.htmlParser;
 | 
					 | 
				
			||||||
        this.htmlRenderer = builder.htmlRenderer;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @NonNull
 | 
					    @NonNull
 | 
				
			||||||
@ -79,16 +73,6 @@ public class MarkwonConfiguration {
 | 
				
			|||||||
        return imageSizeResolver;
 | 
					        return imageSizeResolver;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @NonNull
 | 
					 | 
				
			||||||
    public MarkwonHtmlParser htmlParser() {
 | 
					 | 
				
			||||||
        return htmlParser;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @NonNull
 | 
					 | 
				
			||||||
    public MarkwonHtmlRenderer htmlRenderer() {
 | 
					 | 
				
			||||||
        return htmlRenderer;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @since 3.0.0
 | 
					     * @since 3.0.0
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@ -106,8 +90,6 @@ public class MarkwonConfiguration {
 | 
				
			|||||||
        private LinkSpan.Resolver linkResolver;
 | 
					        private LinkSpan.Resolver linkResolver;
 | 
				
			||||||
        private UrlProcessor urlProcessor;
 | 
					        private UrlProcessor urlProcessor;
 | 
				
			||||||
        private ImageSizeResolver imageSizeResolver;
 | 
					        private ImageSizeResolver imageSizeResolver;
 | 
				
			||||||
        private MarkwonHtmlParser htmlParser;
 | 
					 | 
				
			||||||
        private MarkwonHtmlRenderer htmlRenderer;
 | 
					 | 
				
			||||||
        private MarkwonSpansFactory spansFactory;
 | 
					        private MarkwonSpansFactory spansFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Builder() {
 | 
					        Builder() {
 | 
				
			||||||
@ -122,15 +104,6 @@ public class MarkwonConfiguration {
 | 
				
			|||||||
            return this;
 | 
					            return this;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /**
 | 
					 | 
				
			||||||
         * @since 4.0.0-SNAPSHOT
 | 
					 | 
				
			||||||
         */
 | 
					 | 
				
			||||||
        @NonNull
 | 
					 | 
				
			||||||
        public Builder htmlRenderer(@NonNull MarkwonHtmlRenderer htmlRenderer) {
 | 
					 | 
				
			||||||
            this.htmlRenderer = htmlRenderer;
 | 
					 | 
				
			||||||
            return this;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @NonNull
 | 
					        @NonNull
 | 
				
			||||||
        public Builder syntaxHighlight(@NonNull SyntaxHighlight syntaxHighlight) {
 | 
					        public Builder syntaxHighlight(@NonNull SyntaxHighlight syntaxHighlight) {
 | 
				
			||||||
            this.syntaxHighlight = syntaxHighlight;
 | 
					            this.syntaxHighlight = syntaxHighlight;
 | 
				
			||||||
@ -149,12 +122,6 @@ public class MarkwonConfiguration {
 | 
				
			|||||||
            return this;
 | 
					            return this;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @NonNull
 | 
					 | 
				
			||||||
        public Builder htmlParser(@NonNull MarkwonHtmlParser htmlParser) {
 | 
					 | 
				
			||||||
            this.htmlParser = htmlParser;
 | 
					 | 
				
			||||||
            return this;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * @since 1.0.1
 | 
					         * @since 1.0.1
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
@ -177,11 +144,6 @@ public class MarkwonConfiguration {
 | 
				
			|||||||
                asyncDrawableLoader = AsyncDrawableLoader.noOp();
 | 
					                asyncDrawableLoader = AsyncDrawableLoader.noOp();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // @since 4.0.0-SNAPSHOT
 | 
					 | 
				
			||||||
            if (htmlRenderer == null) {
 | 
					 | 
				
			||||||
                htmlRenderer = MarkwonHtmlRenderer.noOp();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (syntaxHighlight == null) {
 | 
					            if (syntaxHighlight == null) {
 | 
				
			||||||
                syntaxHighlight = new SyntaxHighlightNoOp();
 | 
					                syntaxHighlight = new SyntaxHighlightNoOp();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -198,10 +160,6 @@ public class MarkwonConfiguration {
 | 
				
			|||||||
                imageSizeResolver = new ImageSizeResolverDef();
 | 
					                imageSizeResolver = new ImageSizeResolverDef();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (htmlParser == null) {
 | 
					 | 
				
			||||||
                htmlParser = MarkwonHtmlParser.noOp();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return new MarkwonConfiguration(this);
 | 
					            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 org.robolectric.annotation.Config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import io.noties.markwon.core.MarkwonTheme;
 | 
					import io.noties.markwon.core.MarkwonTheme;
 | 
				
			||||||
import io.noties.markwon.html.MarkwonHtmlRenderer;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static org.hamcrest.Matchers.containsString;
 | 
					import static org.hamcrest.Matchers.containsString;
 | 
				
			||||||
import static org.junit.Assert.assertThat;
 | 
					import static org.junit.Assert.assertThat;
 | 
				
			||||||
@ -27,39 +26,6 @@ import static org.mockito.Mockito.verify;
 | 
				
			|||||||
@Config(manifest = Config.NONE)
 | 
					@Config(manifest = Config.NONE)
 | 
				
			||||||
public class MarkwonBuilderImplTest {
 | 
					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
 | 
					    @Test
 | 
				
			||||||
    public void no_plugins_added_throws() {
 | 
					    public void no_plugins_added_throws() {
 | 
				
			||||||
        // there is no sense in having an instance with no plugins registered
 | 
					        // 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.HtmlInline;
 | 
				
			||||||
import org.commonmark.node.Node;
 | 
					import org.commonmark.node.Node;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import io.noties.markwon.html.tag.ImageHandler;
 | 
					 | 
				
			||||||
import io.noties.markwon.AbstractMarkwonPlugin;
 | 
					import io.noties.markwon.AbstractMarkwonPlugin;
 | 
				
			||||||
import io.noties.markwon.MarkwonConfiguration;
 | 
					import io.noties.markwon.MarkwonConfiguration;
 | 
				
			||||||
import io.noties.markwon.MarkwonVisitor;
 | 
					import io.noties.markwon.MarkwonVisitor;
 | 
				
			||||||
import io.noties.markwon.html.tag.BlockquoteHandler;
 | 
					import io.noties.markwon.html.tag.BlockquoteHandler;
 | 
				
			||||||
import io.noties.markwon.html.tag.EmphasisHandler;
 | 
					import io.noties.markwon.html.tag.EmphasisHandler;
 | 
				
			||||||
import io.noties.markwon.html.tag.HeadingHandler;
 | 
					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.LinkHandler;
 | 
				
			||||||
import io.noties.markwon.html.tag.ListHandler;
 | 
					import io.noties.markwon.html.tag.ListHandler;
 | 
				
			||||||
import io.noties.markwon.html.tag.StrikeHandler;
 | 
					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;
 | 
					    public static final float SCRIPT_DEF_TEXT_SIZE_RATIO = .75F;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final MarkwonHtmlRendererImpl.Builder builder;
 | 
					    private final MarkwonHtmlRendererImpl.Builder builder;
 | 
				
			||||||
 | 
					    private final MarkwonHtmlParser htmlParser;
 | 
				
			||||||
 | 
					    private MarkwonHtmlRenderer htmlRenderer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @SuppressWarnings("WeakerAccess")
 | 
					    @SuppressWarnings("WeakerAccess")
 | 
				
			||||||
    HtmlPlugin() {
 | 
					    HtmlPlugin() {
 | 
				
			||||||
        this.builder = new MarkwonHtmlRendererImpl.Builder();
 | 
					        this.builder = new MarkwonHtmlRendererImpl.Builder();
 | 
				
			||||||
 | 
					        this.htmlParser = MarkwonHtmlParserImpl.create();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -104,6 +107,8 @@ public class HtmlPlugin extends AbstractMarkwonPlugin {
 | 
				
			|||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void configureConfiguration(@NonNull MarkwonConfiguration.Builder configurationBuilder) {
 | 
					    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;
 | 
					        final MarkwonHtmlRendererImpl.Builder builder = this.builder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!builder.excludeDefaults()) {
 | 
					        if (!builder.excludeDefaults()) {
 | 
				
			||||||
@ -123,15 +128,17 @@ public class HtmlPlugin extends AbstractMarkwonPlugin {
 | 
				
			|||||||
            builder.addDefaultTagHandler(new HeadingHandler());
 | 
					            builder.addDefaultTagHandler(new HeadingHandler());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        configurationBuilder
 | 
					        htmlRenderer = builder.build();
 | 
				
			||||||
                .htmlRenderer(builder.build())
 | 
					 | 
				
			||||||
                .htmlParser(MarkwonHtmlParserImpl.create());
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor) {
 | 
					    public void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor) {
 | 
				
			||||||
        final MarkwonConfiguration configuration = visitor.configuration();
 | 
					        final MarkwonHtmlRenderer htmlRenderer = this.htmlRenderer;
 | 
				
			||||||
        configuration.htmlRenderer().render(visitor, configuration.htmlParser());
 | 
					        if (htmlRenderer != null) {
 | 
				
			||||||
 | 
					            htmlRenderer.render(visitor, htmlParser);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            throw new IllegalStateException("Unexpected state, html-renderer is not defined");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@ -153,7 +160,7 @@ public class HtmlPlugin extends AbstractMarkwonPlugin {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private void visitHtml(@NonNull MarkwonVisitor visitor, @Nullable String html) {
 | 
					    private void visitHtml(@NonNull MarkwonVisitor visitor, @Nullable String html) {
 | 
				
			||||||
        if (html != null) {
 | 
					        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 {
 | 
					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> {
 | 
					    public interface FlushAction<T> {
 | 
				
			||||||
        void apply(@NonNull List<T> tags);
 | 
					        void apply(@NonNull List<T> tags);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -10,14 +10,6 @@ import io.noties.markwon.MarkwonVisitor;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public abstract class MarkwonHtmlRenderer {
 | 
					public abstract class MarkwonHtmlRenderer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @since 4.0.0-SNAPSHOT
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @NonNull
 | 
					 | 
				
			||||||
    public static MarkwonHtmlRenderer noOp() {
 | 
					 | 
				
			||||||
        return new MarkwonHtmlRendererNoOp();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public abstract void render(
 | 
					    public abstract void render(
 | 
				
			||||||
            @NonNull MarkwonVisitor visitor,
 | 
					            @NonNull MarkwonVisitor visitor,
 | 
				
			||||||
            @NonNull MarkwonHtmlParser parser
 | 
					            @NonNull MarkwonHtmlParser parser
 | 
				
			||||||
@ -3,6 +3,7 @@ package io.noties.markwon.image;
 | 
				
			|||||||
import android.graphics.drawable.Drawable;
 | 
					import android.graphics.drawable.Drawable;
 | 
				
			||||||
import android.support.annotation.NonNull;
 | 
					import android.support.annotation.NonNull;
 | 
				
			||||||
import android.support.annotation.Nullable;
 | 
					import android.support.annotation.Nullable;
 | 
				
			||||||
 | 
					import android.support.annotation.VisibleForTesting;
 | 
				
			||||||
import android.text.Spanned;
 | 
					import android.text.Spanned;
 | 
				
			||||||
import android.widget.TextView;
 | 
					import android.widget.TextView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -10,16 +11,16 @@ import org.commonmark.node.Image;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.util.concurrent.ExecutorService;
 | 
					import java.util.concurrent.ExecutorService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io.noties.markwon.AbstractMarkwonPlugin;
 | 
				
			||||||
 | 
					import io.noties.markwon.MarkwonConfiguration;
 | 
				
			||||||
import io.noties.markwon.MarkwonPlugin;
 | 
					import io.noties.markwon.MarkwonPlugin;
 | 
				
			||||||
 | 
					import io.noties.markwon.MarkwonSpansFactory;
 | 
				
			||||||
import io.noties.markwon.image.data.DataUriSchemeHandler;
 | 
					import io.noties.markwon.image.data.DataUriSchemeHandler;
 | 
				
			||||||
import io.noties.markwon.image.file.FileSchemeHandler;
 | 
					import io.noties.markwon.image.file.FileSchemeHandler;
 | 
				
			||||||
import io.noties.markwon.image.gif.GifMediaDecoder;
 | 
					import io.noties.markwon.image.gif.GifMediaDecoder;
 | 
				
			||||||
import io.noties.markwon.image.network.NetworkSchemeHandler;
 | 
					import io.noties.markwon.image.network.NetworkSchemeHandler;
 | 
				
			||||||
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler;
 | 
					import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler;
 | 
				
			||||||
import io.noties.markwon.image.svg.SvgMediaDecoder;
 | 
					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"})
 | 
					@SuppressWarnings({"UnusedReturnValue", "WeakerAccess"})
 | 
				
			||||||
public class ImagesPlugin extends AbstractMarkwonPlugin {
 | 
					public class ImagesPlugin extends AbstractMarkwonPlugin {
 | 
				
			||||||
@ -70,7 +71,18 @@ public class ImagesPlugin extends AbstractMarkwonPlugin {
 | 
				
			|||||||
        return plugin;
 | 
					        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)
 | 
					     * 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.assertNotNull;
 | 
				
			||||||
import static org.junit.Assert.assertNull;
 | 
					import static org.junit.Assert.assertNull;
 | 
				
			||||||
import static org.junit.Assert.assertTrue;
 | 
					import static org.junit.Assert.assertTrue;
 | 
				
			||||||
 | 
					import static org.junit.Assert.fail;
 | 
				
			||||||
import static org.mockito.Mockito.mock;
 | 
					import static org.mockito.Mockito.mock;
 | 
				
			||||||
import static org.mockito.Mockito.when;
 | 
					import static org.mockito.Mockito.when;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -203,4 +204,16 @@ public class AsyncDrawableLoaderBuilderTest {
 | 
				
			|||||||
        builder.removeMediaDecoder(media);
 | 
					        builder.removeMediaDecoder(media);
 | 
				
			||||||
        assertNull(builder.mediaDecoders.get(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.mock;
 | 
				
			||||||
import static org.mockito.Mockito.times;
 | 
					import static org.mockito.Mockito.times;
 | 
				
			||||||
import static org.mockito.Mockito.verify;
 | 
					import static org.mockito.Mockito.verify;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.verifyNoMoreInteractions;
 | 
				
			||||||
import static org.mockito.Mockito.when;
 | 
					import static org.mockito.Mockito.when;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@RunWith(RobolectricTestRunner.class)
 | 
					@RunWith(RobolectricTestRunner.class)
 | 
				
			||||||
@ -175,4 +176,70 @@ public class ImagesPluginTest {
 | 
				
			|||||||
        verify(textView, times(1))
 | 
					        verify(textView, times(1))
 | 
				
			||||||
                .getTag(eq(R.id.markwon_drawables_scheduler_last_text_hashcode));
 | 
					                .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