Image loader tests
This commit is contained in:
parent
ab4c80dca5
commit
173425ed53
@ -88,7 +88,6 @@ public class MarkwonBuilderImplTest {
|
|||||||
verify(plugin, times(1)).configureConfiguration(any(MarkwonConfiguration.Builder.class));
|
verify(plugin, times(1)).configureConfiguration(any(MarkwonConfiguration.Builder.class));
|
||||||
verify(plugin, times(1)).configureVisitor(any(MarkwonVisitor.Builder.class));
|
verify(plugin, times(1)).configureVisitor(any(MarkwonVisitor.Builder.class));
|
||||||
verify(plugin, times(1)).configureSpansFactory(any(MarkwonSpansFactory.Builder.class));
|
verify(plugin, times(1)).configureSpansFactory(any(MarkwonSpansFactory.Builder.class));
|
||||||
verify(plugin, times(1)).configureHtmlRenderer(any(MarkwonHtmlRenderer.Builder.class));
|
|
||||||
|
|
||||||
// note, no render props -> they must be configured on render stage
|
// note, no render props -> they must be configured on render stage
|
||||||
verify(plugin, times(0)).processMarkdown(anyString());
|
verify(plugin, times(0)).processMarkdown(anyString());
|
||||||
|
@ -3,6 +3,7 @@ package io.noties.markwon.core;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.method.MovementMethod;
|
import android.text.method.MovementMethod;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.commonmark.node.BlockQuote;
|
import org.commonmark.node.BlockQuote;
|
||||||
@ -12,6 +13,7 @@ import org.commonmark.node.Emphasis;
|
|||||||
import org.commonmark.node.FencedCodeBlock;
|
import org.commonmark.node.FencedCodeBlock;
|
||||||
import org.commonmark.node.HardLineBreak;
|
import org.commonmark.node.HardLineBreak;
|
||||||
import org.commonmark.node.Heading;
|
import org.commonmark.node.Heading;
|
||||||
|
import org.commonmark.node.Image;
|
||||||
import org.commonmark.node.IndentedCodeBlock;
|
import org.commonmark.node.IndentedCodeBlock;
|
||||||
import org.commonmark.node.Link;
|
import org.commonmark.node.Link;
|
||||||
import org.commonmark.node.ListItem;
|
import org.commonmark.node.ListItem;
|
||||||
@ -84,7 +86,8 @@ public class CorePluginTest {
|
|||||||
SoftLineBreak.class,
|
SoftLineBreak.class,
|
||||||
StrongEmphasis.class,
|
StrongEmphasis.class,
|
||||||
Text.class,
|
Text.class,
|
||||||
ThematicBreak.class
|
ThematicBreak.class,
|
||||||
|
Image.class
|
||||||
};
|
};
|
||||||
|
|
||||||
final CorePlugin plugin = CorePlugin.create();
|
final CorePlugin plugin = CorePlugin.create();
|
||||||
@ -202,6 +205,7 @@ public class CorePluginTest {
|
|||||||
add("beforeSetText");
|
add("beforeSetText");
|
||||||
add("afterSetText");
|
add("afterSetText");
|
||||||
add("priority");
|
add("priority");
|
||||||
|
add("addOnTextAddedListener");
|
||||||
}};
|
}};
|
||||||
|
|
||||||
// we will use declaredMethods because it won't return inherited ones
|
// we will use declaredMethods because it won't return inherited ones
|
||||||
|
@ -26,7 +26,6 @@ import io.noties.markwon.SpanFactory;
|
|||||||
import io.noties.markwon.SpannableBuilder;
|
import io.noties.markwon.SpannableBuilder;
|
||||||
import io.noties.markwon.core.CorePluginBridge;
|
import io.noties.markwon.core.CorePluginBridge;
|
||||||
import io.noties.markwon.core.MarkwonTheme;
|
import io.noties.markwon.core.MarkwonTheme;
|
||||||
import io.noties.markwon.html.MarkwonHtmlRenderer;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
@ -83,7 +82,7 @@ public class SyntaxHighlightTest {
|
|||||||
|
|
||||||
final MarkwonConfiguration configuration = MarkwonConfiguration.builder()
|
final MarkwonConfiguration configuration = MarkwonConfiguration.builder()
|
||||||
.syntaxHighlight(highlight)
|
.syntaxHighlight(highlight)
|
||||||
.build(mock(MarkwonTheme.class), mock(MarkwonHtmlRenderer.class), spansFactory);
|
.build(mock(MarkwonTheme.class), spansFactory);
|
||||||
|
|
||||||
final Map<Class<? extends Node>, MarkwonVisitor.NodeVisitor<? extends Node>> visitorMap = Collections.emptyMap();
|
final Map<Class<? extends Node>, MarkwonVisitor.NodeVisitor<? extends Node>> visitorMap = Collections.emptyMap();
|
||||||
|
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
# LaTeX
|
# LaTeX
|
||||||
|
|
||||||
[Documentation](https://noties.github.io/Markwon/docs/ext-latex)
|

|
||||||
|

|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
implementation "io.noties.markwon:ext-strikethrough:${markwonVersion}"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
[Documentation](https://noties.github.io/Markwon/docs/v3/ext-latex)
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
# Strikethrough
|
# Strikethrough
|
||||||
|
|
||||||
[](http://search.maven.org/#search|ga|1|g%3A%22io.noties.markwon%22%20AND%20a%3A%22ext-strikethrough%22)
|

|
||||||
|

|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
implementation "io.noties.markwon:ext-strikethrough:${markwonVersion}"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
This module adds `strikethrough` functionality to `Markwon` via `StrikethroughPlugin`:
|
This module adds `strikethrough` functionality to `Markwon` via `StrikethroughPlugin`:
|
||||||
|
|
||||||
|
@ -17,10 +17,11 @@ dependencies {
|
|||||||
|
|
||||||
api project(':markwon-core')
|
api project(':markwon-core')
|
||||||
|
|
||||||
|
// todo: note that it includes these implicitly
|
||||||
deps.with {
|
deps.with {
|
||||||
compileOnly it['android-gif']
|
api it['android-gif']
|
||||||
compileOnly it['android-svg']
|
api it['android-svg']
|
||||||
compileOnly it['okhttp']
|
api it['okhttp']
|
||||||
}
|
}
|
||||||
|
|
||||||
deps['test'].with {
|
deps['test'].with {
|
||||||
|
@ -9,7 +9,9 @@ import java.util.concurrent.ExecutorService;
|
|||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import io.noties.markwon.image.data.DataUriSchemeHandler;
|
import io.noties.markwon.image.data.DataUriSchemeHandler;
|
||||||
|
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.svg.SvgMediaDecoder;
|
||||||
|
|
||||||
class AsyncDrawableLoaderBuilder {
|
class AsyncDrawableLoaderBuilder {
|
||||||
|
|
||||||
@ -30,6 +32,15 @@ class AsyncDrawableLoaderBuilder {
|
|||||||
addSchemeHandler(DataUriSchemeHandler.create());
|
addSchemeHandler(DataUriSchemeHandler.create());
|
||||||
addSchemeHandler(NetworkSchemeHandler.create());
|
addSchemeHandler(NetworkSchemeHandler.create());
|
||||||
|
|
||||||
|
// add SVG and GIF, but only if they are present in the class-path
|
||||||
|
if (SvgMediaDecoder.available()) {
|
||||||
|
addMediaDecoder(SvgMediaDecoder.create());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GifMediaDecoder.available()) {
|
||||||
|
addMediaDecoder(GifMediaDecoder.create());
|
||||||
|
}
|
||||||
|
|
||||||
defaultMediaDecoder = DefaultImageMediaDecoder.create();
|
defaultMediaDecoder = DefaultImageMediaDecoder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,8 +92,15 @@ class AsyncDrawableLoaderImpl extends AsyncDrawableLoader {
|
|||||||
Drawable drawable = null;
|
Drawable drawable = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
final String scheme = uri.getScheme();
|
||||||
|
if (scheme == null
|
||||||
|
|| scheme.length() == 0) {
|
||||||
|
throw new IllegalStateException("No scheme is found: " + destination);
|
||||||
|
}
|
||||||
|
|
||||||
// obtain scheme handler
|
// obtain scheme handler
|
||||||
final SchemeHandler schemeHandler = schemeHandlers.get(uri.getScheme());
|
final SchemeHandler schemeHandler = schemeHandlers.get(scheme);
|
||||||
if (schemeHandler != null) {
|
if (schemeHandler != null) {
|
||||||
|
|
||||||
// handle scheme
|
// handle scheme
|
||||||
|
@ -10,9 +10,9 @@ import java.io.InputStream;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import io.noties.markwon.image.DrawableUtils;
|
||||||
import io.noties.markwon.image.MediaDecoder;
|
import io.noties.markwon.image.MediaDecoder;
|
||||||
import pl.droidsonroids.gif.GifDrawable;
|
import pl.droidsonroids.gif.GifDrawable;
|
||||||
import io.noties.markwon.image.DrawableUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
@ -22,11 +22,29 @@ public class GifMediaDecoder extends MediaDecoder {
|
|||||||
|
|
||||||
public static final String CONTENT_TYPE = "image/gif";
|
public static final String CONTENT_TYPE = "image/gif";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link GifMediaDecoder} with {@code autoPlayGif = true}
|
||||||
|
*
|
||||||
|
* @since 4.0.0-SNAPSHOT
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static GifMediaDecoder create() {
|
||||||
|
return create(true);
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static GifMediaDecoder create(boolean autoPlayGif) {
|
public static GifMediaDecoder create(boolean autoPlayGif) {
|
||||||
return new GifMediaDecoder(autoPlayGif);
|
return new GifMediaDecoder(autoPlayGif);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return boolean indicating if GIF dependency is satisfied
|
||||||
|
* @since 4.0.0-SNAPSHOT
|
||||||
|
*/
|
||||||
|
public static boolean available() {
|
||||||
|
return Holder.HAS_GIF;
|
||||||
|
}
|
||||||
|
|
||||||
private final boolean autoPlayGif;
|
private final boolean autoPlayGif;
|
||||||
|
|
||||||
protected GifMediaDecoder(boolean autoPlayGif) {
|
protected GifMediaDecoder(boolean autoPlayGif) {
|
||||||
@ -105,7 +123,8 @@ public class GifMediaDecoder extends MediaDecoder {
|
|||||||
static void validate() {
|
static void validate() {
|
||||||
if (!HAS_GIF) {
|
if (!HAS_GIF) {
|
||||||
throw new IllegalStateException("`pl.droidsonroids.gif:android-gif-drawable:*` " +
|
throw new IllegalStateException("`pl.droidsonroids.gif:android-gif-drawable:*` " +
|
||||||
"dependency is missing, please add to your project explicitly");
|
"dependency is missing, please add to your project explicitly if you " +
|
||||||
|
"wish to use GIF media decoder");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,8 @@ import java.io.InputStream;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import io.noties.markwon.image.MediaDecoder;
|
|
||||||
import io.noties.markwon.image.DrawableUtils;
|
import io.noties.markwon.image.DrawableUtils;
|
||||||
|
import io.noties.markwon.image.MediaDecoder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
@ -31,7 +31,7 @@ public class SvgMediaDecoder extends MediaDecoder {
|
|||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static SvgMediaDecoder create() {
|
public static SvgMediaDecoder create() {
|
||||||
return new SvgMediaDecoder(Resources.getSystem());
|
return create(Resources.getSystem());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -39,6 +39,14 @@ public class SvgMediaDecoder extends MediaDecoder {
|
|||||||
return new SvgMediaDecoder(resources);
|
return new SvgMediaDecoder(resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return boolean indicating if SVG dependency is satisfied
|
||||||
|
* @since 4.0.0-SNAPSHOT
|
||||||
|
*/
|
||||||
|
public static boolean available() {
|
||||||
|
return Holder.HAS_SVG;
|
||||||
|
}
|
||||||
|
|
||||||
private final Resources resources;
|
private final Resources resources;
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
@ -102,7 +110,7 @@ public class SvgMediaDecoder extends MediaDecoder {
|
|||||||
static void validate() {
|
static void validate() {
|
||||||
if (!HAS_SVG) {
|
if (!HAS_SVG) {
|
||||||
throw new IllegalStateException("`com.caverock:androidsvg:*` dependency is missing, " +
|
throw new IllegalStateException("`com.caverock:androidsvg:*` dependency is missing, " +
|
||||||
"please add to your project explicitly");
|
"please add to your project explicitly if you wish to use SVG media decoder");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
import io.noties.markwon.image.network.NetworkSchemeHandler;
|
|
||||||
import io.noties.markwon.image.data.DataUriSchemeHandler;
|
import io.noties.markwon.image.data.DataUriSchemeHandler;
|
||||||
|
import io.noties.markwon.image.network.NetworkSchemeHandler;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@ -62,7 +62,7 @@ public class AsyncDrawableLoaderBuilderTest {
|
|||||||
public void defaults_initialized() {
|
public void defaults_initialized() {
|
||||||
// default-media-decoder and executor-service must be initialized
|
// default-media-decoder and executor-service must be initialized
|
||||||
|
|
||||||
assertNull(builder.defaultMediaDecoder);
|
assertNotNull(builder.defaultMediaDecoder);
|
||||||
assertNull(builder.executorService);
|
assertNull(builder.executorService);
|
||||||
|
|
||||||
builder.build();
|
builder.build();
|
||||||
@ -71,6 +71,18 @@ public class AsyncDrawableLoaderBuilderTest {
|
|||||||
assertNotNull(builder.executorService);
|
assertNotNull(builder.executorService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void default_media_decoder_removed() {
|
||||||
|
// we init default-media-decoder right away, but further it can be removed (nulled-out)
|
||||||
|
|
||||||
|
assertNotNull(builder.defaultMediaDecoder);
|
||||||
|
|
||||||
|
builder.defaultMediaDecoder(null);
|
||||||
|
builder.build();
|
||||||
|
|
||||||
|
assertNull(builder.defaultMediaDecoder);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void executor() {
|
public void executor() {
|
||||||
// supplied executor-service must be used
|
// supplied executor-service must be used
|
||||||
@ -155,7 +167,7 @@ public class AsyncDrawableLoaderBuilderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void default_media_decoder() {
|
public void default_media_decoder() {
|
||||||
|
|
||||||
assertNull(builder.defaultMediaDecoder);
|
assertNotNull(builder.defaultMediaDecoder);
|
||||||
|
|
||||||
final MediaDecoder mediaDecoder = mock(MediaDecoder.class);
|
final MediaDecoder mediaDecoder = mock(MediaDecoder.class);
|
||||||
builder.defaultMediaDecoder(mediaDecoder);
|
builder.defaultMediaDecoder(mediaDecoder);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package io.noties.markwon.image;
|
package io.noties.markwon.image;
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
@ -7,15 +9,31 @@ import android.support.annotation.Nullable;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
import io.noties.markwon.image.ImagesPlugin.ErrorHandler;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
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.when;
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
@Config(manifest = Config.NONE)
|
@Config(manifest = Config.NONE)
|
||||||
@ -40,60 +58,508 @@ public class AsyncDrawableLoaderImplTest {
|
|||||||
.providePlaceholder(any(AsyncDrawable.class));
|
.providePlaceholder(any(AsyncDrawable.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void load_cancel() {
|
||||||
|
// verify that load/cancel works as expected
|
||||||
|
|
||||||
|
final ExecutorService executorService = mock(ExecutorService.class);
|
||||||
|
final Future future = mock(Future.class);
|
||||||
|
{
|
||||||
|
//noinspection unchecked
|
||||||
|
when(executorService.submit(any(Runnable.class)))
|
||||||
|
.thenReturn(future);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Handler handler = mock(Handler.class);
|
||||||
|
|
||||||
|
final AsyncDrawable drawable = mock(AsyncDrawable.class);
|
||||||
|
|
||||||
|
impl = builder
|
||||||
|
.executorService(executorService)
|
||||||
|
.handler(handler)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
impl.load(drawable);
|
||||||
|
|
||||||
|
verify(executorService, times(1)).submit(any(Runnable.class));
|
||||||
|
|
||||||
|
impl.cancel(drawable);
|
||||||
|
|
||||||
|
verify(future, times(1)).cancel(eq(true));
|
||||||
|
verify(handler, times(1)).removeCallbacksAndMessages(eq(drawable));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void load_no_scheme_handler() {
|
||||||
|
// when loading is triggered for a scheme which has no registered scheme-handler
|
||||||
|
|
||||||
|
final ErrorHandler errorHandler = mock(ErrorHandler.class);
|
||||||
|
|
||||||
|
impl = builder
|
||||||
|
.executorService(immediateExecutorService())
|
||||||
|
.errorHandler(errorHandler)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final String destination = "blah://blah.JPEG";
|
||||||
|
|
||||||
|
impl.load(asyncDrawable(destination));
|
||||||
|
|
||||||
|
final ArgumentCaptor<Throwable> throwableCaptor = ArgumentCaptor.forClass(Throwable.class);
|
||||||
|
verify(errorHandler, times(1))
|
||||||
|
.handleError(eq(destination), throwableCaptor.capture());
|
||||||
|
final Throwable value = throwableCaptor.getValue();
|
||||||
|
assertTrue(value.getClass().getName(), value instanceof IllegalStateException);
|
||||||
|
assertTrue(value.getMessage(), value.getMessage().contains("No scheme-handler is found"));
|
||||||
|
assertTrue(value.getMessage(), value.getMessage().contains(destination));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void load_scheme_handler_throws() {
|
||||||
|
|
||||||
|
final ErrorHandler errorHandler = mock(ErrorHandler.class);
|
||||||
|
final SchemeHandler schemeHandler = new SchemeHandler() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
||||||
|
throw new RuntimeException("We throw!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedSchemes() {
|
||||||
|
return Collections.singleton("hey");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
impl = builder
|
||||||
|
.executorService(immediateExecutorService())
|
||||||
|
.errorHandler(errorHandler)
|
||||||
|
.addSchemeHandler(schemeHandler)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final String destination = "hey://whe.er";
|
||||||
|
|
||||||
|
impl.load(asyncDrawable(destination));
|
||||||
|
|
||||||
|
final ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
|
||||||
|
verify(errorHandler, times(1))
|
||||||
|
.handleError(eq(destination), captor.capture());
|
||||||
|
|
||||||
|
final Throwable throwable = captor.getValue();
|
||||||
|
assertTrue(throwable.getClass().getName(), throwable instanceof RuntimeException);
|
||||||
|
assertEquals("We throw!", throwable.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void load_scheme_handler_returns_result() {
|
||||||
|
|
||||||
|
final Drawable drawable = mock(Drawable.class);
|
||||||
|
final SchemeHandler schemeHandler = new SchemeHandler() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
||||||
|
return ImageItem.withResult(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedSchemes() {
|
||||||
|
return Collections.singleton("*");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final String destination = "*://yo";
|
||||||
|
|
||||||
|
final Future future = mock(Future.class);
|
||||||
|
final ExecutorService executorService = immediateExecutorService(future);
|
||||||
|
final Handler handler = mock(Handler.class);
|
||||||
|
|
||||||
|
impl = builder
|
||||||
|
.executorService(executorService)
|
||||||
|
.handler(handler)
|
||||||
|
.addSchemeHandler(schemeHandler)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final AsyncDrawable asyncDrawable = asyncDrawable(destination);
|
||||||
|
|
||||||
|
impl.load(asyncDrawable);
|
||||||
|
|
||||||
|
verify(executorService, times(1))
|
||||||
|
.submit(any(Runnable.class));
|
||||||
|
|
||||||
|
// we must use captor in order to let the internal (async) logic settle
|
||||||
|
final ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
|
||||||
|
verify(handler, times(1))
|
||||||
|
.postAtTime(captor.capture(), eq(asyncDrawable), anyLong());
|
||||||
|
|
||||||
|
captor.getValue().run();
|
||||||
|
|
||||||
|
verify(asyncDrawable, times(1))
|
||||||
|
.setResult(eq(drawable));
|
||||||
|
|
||||||
|
// now, let's cancel the request (at this point it must be removed from referencing)
|
||||||
|
impl.cancel(asyncDrawable);
|
||||||
|
|
||||||
|
verify(future, never()).cancel(anyBoolean());
|
||||||
|
|
||||||
|
// this method will be called anyway (we have no mean to check if token has queue)
|
||||||
|
// verify(handler, never()).removeCallbacksAndMessages(eq(asyncDrawable));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void load_scheme_handler_returns_decoding_default_used() {
|
||||||
|
// we won't be registering media decoder, but provide a default one (which must be used)
|
||||||
|
|
||||||
|
final MediaDecoder mediaDecoder = mock(MediaDecoder.class);
|
||||||
|
final InputStream inputStream = mock(InputStream.class);
|
||||||
|
final Drawable drawable = mock(Drawable.class);
|
||||||
|
|
||||||
|
{
|
||||||
|
when(mediaDecoder.decode(any(String.class), any(InputStream.class)))
|
||||||
|
.thenReturn(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl = builder
|
||||||
|
.executorService(immediateExecutorService(mock(Future.class)))
|
||||||
|
.defaultMediaDecoder(mediaDecoder)
|
||||||
|
.addSchemeHandler(new SchemeHandler() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
||||||
|
return ImageItem.withDecodingNeeded("no/op", inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedSchemes() {
|
||||||
|
return Collections.singleton("whatever");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final String destination = "whatever://yeah-yeah-yeah";
|
||||||
|
final AsyncDrawable asyncDrawable = asyncDrawable(destination);
|
||||||
|
|
||||||
|
impl.load(asyncDrawable);
|
||||||
|
|
||||||
|
verify(mediaDecoder, times(1))
|
||||||
|
.decode(eq("no/op"), eq(inputStream));
|
||||||
|
|
||||||
|
final ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
|
||||||
|
verify(builder._handler, times(1))
|
||||||
|
.postAtTime(captor.capture(), eq(asyncDrawable), anyLong());
|
||||||
|
|
||||||
|
captor.getValue().run();
|
||||||
|
|
||||||
|
verify(asyncDrawable, times(1))
|
||||||
|
.setResult(eq(drawable));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void load_no_media_decoder_present() {
|
||||||
|
// if some content-type is requested (and it has no registered media-decoder),
|
||||||
|
// and default-media-decoder is not added -> throws
|
||||||
|
|
||||||
|
final ErrorHandler errorHandler = mock(ErrorHandler.class);
|
||||||
|
|
||||||
|
impl = builder
|
||||||
|
.defaultMediaDecoder(null)
|
||||||
|
.executorService(immediateExecutorService())
|
||||||
|
.errorHandler(errorHandler)
|
||||||
|
.addSchemeHandler(new SchemeHandler() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
||||||
|
return ImageItem.withDecodingNeeded("np/op", mock(InputStream.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedSchemes() {
|
||||||
|
return Collections.singleton("ftp");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final String destination = "ftp://xxx";
|
||||||
|
final AsyncDrawable asyncDrawable = asyncDrawable(destination);
|
||||||
|
|
||||||
|
impl.load(asyncDrawable);
|
||||||
|
|
||||||
|
final ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
|
||||||
|
|
||||||
|
verify(errorHandler, times(1))
|
||||||
|
.handleError(eq(destination), captor.capture());
|
||||||
|
|
||||||
|
final Throwable throwable = captor.getValue();
|
||||||
|
assertTrue(throwable.getClass().getName(), throwable instanceof IllegalStateException);
|
||||||
|
assertTrue(throwable.getMessage(), throwable.getMessage().contains("No media-decoder is found"));
|
||||||
|
assertTrue(throwable.getMessage(), throwable.getMessage().contains(destination));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void load_error_handler_drawable() {
|
||||||
|
// error-handler can return optional error-drawable that can be used as a result
|
||||||
|
|
||||||
|
final ErrorHandler errorHandler = mock(ErrorHandler.class);
|
||||||
|
final Drawable drawable = mock(Drawable.class);
|
||||||
|
{
|
||||||
|
when(errorHandler.handleError(any(String.class), any(Throwable.class)))
|
||||||
|
.thenReturn(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl = builder
|
||||||
|
.executorService(immediateExecutorService(mock(Future.class)))
|
||||||
|
.errorHandler(errorHandler)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// we will rely on _internal_ error, which is also delivered to error-handler
|
||||||
|
// in this case -> no scheme-handler
|
||||||
|
|
||||||
|
final String destination = "uo://uo?true=false";
|
||||||
|
final AsyncDrawable asyncDrawable = asyncDrawable(destination);
|
||||||
|
|
||||||
|
impl.load(asyncDrawable);
|
||||||
|
|
||||||
|
verify(errorHandler, times(1))
|
||||||
|
.handleError(eq(destination), any(Throwable.class));
|
||||||
|
|
||||||
|
final ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
|
||||||
|
verify(builder._handler, times(1))
|
||||||
|
.postAtTime(captor.capture(), eq(asyncDrawable), anyLong());
|
||||||
|
|
||||||
|
captor.getValue().run();
|
||||||
|
|
||||||
|
verify(asyncDrawable, times(1))
|
||||||
|
.setResult(eq(drawable));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void load_success_request_cancelled() {
|
||||||
|
// when loading finishes it must check if request had been cancelled and not deliver result
|
||||||
|
|
||||||
|
impl = builder
|
||||||
|
.executorService(immediateExecutorService(mock(Future.class)))
|
||||||
|
.addSchemeHandler(new SchemeHandler() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
||||||
|
return ImageItem.withResult(mock(Drawable.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedSchemes() {
|
||||||
|
return Collections.singleton("ja");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final String destination = "ja://jajaja";
|
||||||
|
final AsyncDrawable asyncDrawable = asyncDrawable(destination);
|
||||||
|
|
||||||
|
impl.load(asyncDrawable);
|
||||||
|
|
||||||
|
final ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
|
||||||
|
verify(builder._handler, times(1))
|
||||||
|
.postAtTime(captor.capture(), eq(asyncDrawable), anyLong());
|
||||||
|
|
||||||
|
// now, cancel
|
||||||
|
impl.cancel(asyncDrawable);
|
||||||
|
|
||||||
|
captor.getValue().run();
|
||||||
|
|
||||||
|
verify(asyncDrawable, never())
|
||||||
|
.setResult(any(Drawable.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void load_success_async_drawable_not_attached() {
|
||||||
|
// when loading finishes, it must check if async-drawable is attached
|
||||||
|
|
||||||
|
impl = builder
|
||||||
|
.executorService(immediateExecutorService(mock(Future.class)))
|
||||||
|
.addSchemeHandler(new SchemeHandler() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
||||||
|
return ImageItem.withResult(mock(Drawable.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedSchemes() {
|
||||||
|
return Collections.singleton("ha");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final String destination = "ha://hahaha";
|
||||||
|
final AsyncDrawable asyncDrawable = asyncDrawable(destination);
|
||||||
|
when(asyncDrawable.isAttached()).thenReturn(false);
|
||||||
|
|
||||||
|
impl.load(asyncDrawable);
|
||||||
|
|
||||||
|
final ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
|
||||||
|
verify(builder._handler, times(1))
|
||||||
|
.postAtTime(captor.capture(), eq(asyncDrawable), anyLong());
|
||||||
|
|
||||||
|
captor.getValue().run();
|
||||||
|
|
||||||
|
verify(asyncDrawable, never())
|
||||||
|
.setResult(any(Drawable.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void load_success_result_null() {
|
||||||
|
// if result is null (but no exception) - no result must be delivered
|
||||||
|
|
||||||
|
// we won't be adding scheme-handler, thus causing internal error
|
||||||
|
// (will have to mock error-handler because for the tests we re-throw errors)
|
||||||
|
impl = builder
|
||||||
|
.executorService(immediateExecutorService(mock(Future.class)))
|
||||||
|
.errorHandler(mock(ErrorHandler.class))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final String destination = "xa://xaxaxa";
|
||||||
|
final AsyncDrawable asyncDrawable = asyncDrawable(destination);
|
||||||
|
|
||||||
|
impl.load(asyncDrawable);
|
||||||
|
|
||||||
|
final ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
|
||||||
|
verify(builder._handler, times(1))
|
||||||
|
.postAtTime(captor.capture(), eq(asyncDrawable), anyLong());
|
||||||
|
|
||||||
|
captor.getValue().run();
|
||||||
|
|
||||||
|
verify(asyncDrawable, never())
|
||||||
|
.setResult(any(Drawable.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void media_decoder_is_used() {
|
||||||
|
|
||||||
|
final MediaDecoder mediaDecoder = mock(MediaDecoder.class);
|
||||||
|
|
||||||
|
{
|
||||||
|
when(mediaDecoder.decode(any(String.class), any(InputStream.class)))
|
||||||
|
.thenReturn(mock(Drawable.class));
|
||||||
|
when(mediaDecoder.supportedTypes())
|
||||||
|
.thenReturn(Collections.singleton("fa/ke"));
|
||||||
|
}
|
||||||
|
|
||||||
|
impl = builder.executorService(immediateExecutorService())
|
||||||
|
.addSchemeHandler(new SchemeHandler() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
||||||
|
return ImageItem.withDecodingNeeded("fa/ke", mock(InputStream.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedSchemes() {
|
||||||
|
return Collections.singleton("fake");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addMediaDecoder(mediaDecoder)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final String destination = "fake://1234";
|
||||||
|
|
||||||
|
impl.load(asyncDrawable(destination));
|
||||||
|
|
||||||
|
verify(mediaDecoder, times(1))
|
||||||
|
.decode(eq("fa/ke"), any(InputStream.class));
|
||||||
|
}
|
||||||
|
|
||||||
private static class BuilderImpl {
|
private static class BuilderImpl {
|
||||||
|
|
||||||
AsyncDrawableLoaderBuilder builder;
|
AsyncDrawableLoaderBuilder _builder = new AsyncDrawableLoaderBuilder();
|
||||||
Handler handler = mock(Handler.class);
|
Handler _handler = mock(Handler.class);
|
||||||
|
|
||||||
public BuilderImpl executorService(@NonNull ExecutorService executorService) {
|
{
|
||||||
builder.executorService(executorService);
|
// be default it just logs the exception, let's rethrow
|
||||||
|
_builder.errorHandler(new ErrorHandler() {
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Drawable handleError(@NonNull String url, @NonNull Throwable throwable) {
|
||||||
|
throw new AsyncDrawableException(url, throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
BuilderImpl executorService(@NonNull ExecutorService executorService) {
|
||||||
|
_builder.executorService(executorService);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BuilderImpl addSchemeHandler(@NonNull SchemeHandler schemeHandler) {
|
BuilderImpl addSchemeHandler(@NonNull SchemeHandler schemeHandler) {
|
||||||
builder.addSchemeHandler(schemeHandler);
|
_builder.addSchemeHandler(schemeHandler);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BuilderImpl addMediaDecoder(@NonNull MediaDecoder mediaDecoder) {
|
BuilderImpl addMediaDecoder(@NonNull MediaDecoder mediaDecoder) {
|
||||||
builder.addMediaDecoder(mediaDecoder);
|
_builder.addMediaDecoder(mediaDecoder);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BuilderImpl defaultMediaDecoder(@Nullable MediaDecoder mediaDecoder) {
|
BuilderImpl defaultMediaDecoder(@Nullable MediaDecoder mediaDecoder) {
|
||||||
builder.defaultMediaDecoder(mediaDecoder);
|
_builder.defaultMediaDecoder(mediaDecoder);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BuilderImpl removeSchemeHandler(@NonNull String scheme) {
|
BuilderImpl placeholderProvider(@NonNull ImagesPlugin.PlaceholderProvider placeholderDrawableProvider) {
|
||||||
builder.removeSchemeHandler(scheme);
|
_builder.placeholderProvider(placeholderDrawableProvider);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BuilderImpl removeMediaDecoder(@NonNull String contentType) {
|
BuilderImpl errorHandler(@NonNull ErrorHandler errorHandler) {
|
||||||
builder.removeMediaDecoder(contentType);
|
_builder.errorHandler(errorHandler);
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BuilderImpl placeholderProvider(@NonNull ImagesPlugin.PlaceholderProvider placeholderDrawableProvider) {
|
|
||||||
builder.placeholderProvider(placeholderDrawableProvider);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BuilderImpl errorHandler(@NonNull ImagesPlugin.ErrorHandler errorHandler) {
|
|
||||||
builder.errorHandler(errorHandler);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public BuilderImpl handler(Handler handler) {
|
BuilderImpl handler(Handler handler) {
|
||||||
this.handler = handler;
|
this._handler = handler;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
AsyncDrawableLoaderImpl build() {
|
AsyncDrawableLoaderImpl build() {
|
||||||
return new AsyncDrawableLoaderImpl(builder, handler);
|
return new AsyncDrawableLoaderImpl(_builder, _handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AsyncDrawableException extends RuntimeException {
|
||||||
|
AsyncDrawableException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static ExecutorService immediateExecutorService() {
|
||||||
|
return immediateExecutorService(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static ExecutorService immediateExecutorService(@Nullable final Future future) {
|
||||||
|
final ExecutorService service = mock(ExecutorService.class);
|
||||||
|
when(service.submit(any(Runnable.class))).then(new Answer<Future>() {
|
||||||
|
@Override
|
||||||
|
public Future answer(InvocationOnMock invocation) {
|
||||||
|
((Runnable) invocation.getArgument(0)).run();
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static AsyncDrawable asyncDrawable(@NonNull String destination) {
|
||||||
|
final AsyncDrawable drawable = mock(AsyncDrawable.class);
|
||||||
|
when(drawable.getDestination()).thenReturn(destination);
|
||||||
|
when(drawable.isAttached()).thenReturn(true);
|
||||||
|
return drawable;
|
||||||
|
}
|
||||||
}
|
}
|
@ -18,7 +18,6 @@ import java.util.concurrent.ExecutorService;
|
|||||||
import io.noties.markwon.MarkwonConfiguration;
|
import io.noties.markwon.MarkwonConfiguration;
|
||||||
import io.noties.markwon.MarkwonSpansFactory;
|
import io.noties.markwon.MarkwonSpansFactory;
|
||||||
import io.noties.markwon.SpanFactory;
|
import io.noties.markwon.SpanFactory;
|
||||||
import ru.noties.markwon.image.R;
|
|
||||||
import io.noties.markwon.image.data.DataUriSchemeHandler;
|
import io.noties.markwon.image.data.DataUriSchemeHandler;
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user