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)).configureVisitor(any(MarkwonVisitor.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
|
||||
verify(plugin, times(0)).processMarkdown(anyString());
|
||||
|
@ -3,6 +3,7 @@ package io.noties.markwon.core;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.method.MovementMethod;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.commonmark.node.BlockQuote;
|
||||
@ -12,6 +13,7 @@ import org.commonmark.node.Emphasis;
|
||||
import org.commonmark.node.FencedCodeBlock;
|
||||
import org.commonmark.node.HardLineBreak;
|
||||
import org.commonmark.node.Heading;
|
||||
import org.commonmark.node.Image;
|
||||
import org.commonmark.node.IndentedCodeBlock;
|
||||
import org.commonmark.node.Link;
|
||||
import org.commonmark.node.ListItem;
|
||||
@ -84,7 +86,8 @@ public class CorePluginTest {
|
||||
SoftLineBreak.class,
|
||||
StrongEmphasis.class,
|
||||
Text.class,
|
||||
ThematicBreak.class
|
||||
ThematicBreak.class,
|
||||
Image.class
|
||||
};
|
||||
|
||||
final CorePlugin plugin = CorePlugin.create();
|
||||
@ -202,6 +205,7 @@ public class CorePluginTest {
|
||||
add("beforeSetText");
|
||||
add("afterSetText");
|
||||
add("priority");
|
||||
add("addOnTextAddedListener");
|
||||
}};
|
||||
|
||||
// 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.core.CorePluginBridge;
|
||||
import io.noties.markwon.core.MarkwonTheme;
|
||||
import io.noties.markwon.html.MarkwonHtmlRenderer;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@ -83,7 +82,7 @@ public class SyntaxHighlightTest {
|
||||
|
||||
final MarkwonConfiguration configuration = MarkwonConfiguration.builder()
|
||||
.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();
|
||||
|
||||
|
@ -1,3 +1,11 @@
|
||||
# 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
|
||||
|
||||
[](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`:
|
||||
|
||||
|
@ -17,10 +17,11 @@ dependencies {
|
||||
|
||||
api project(':markwon-core')
|
||||
|
||||
// todo: note that it includes these implicitly
|
||||
deps.with {
|
||||
compileOnly it['android-gif']
|
||||
compileOnly it['android-svg']
|
||||
compileOnly it['okhttp']
|
||||
api it['android-gif']
|
||||
api it['android-svg']
|
||||
api it['okhttp']
|
||||
}
|
||||
|
||||
deps['test'].with {
|
||||
|
@ -9,7 +9,9 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
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.svg.SvgMediaDecoder;
|
||||
|
||||
class AsyncDrawableLoaderBuilder {
|
||||
|
||||
@ -30,6 +32,15 @@ class AsyncDrawableLoaderBuilder {
|
||||
addSchemeHandler(DataUriSchemeHandler.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();
|
||||
}
|
||||
|
||||
|
@ -92,8 +92,15 @@ class AsyncDrawableLoaderImpl extends AsyncDrawableLoader {
|
||||
Drawable drawable = null;
|
||||
|
||||
try {
|
||||
|
||||
final String scheme = uri.getScheme();
|
||||
if (scheme == null
|
||||
|| scheme.length() == 0) {
|
||||
throw new IllegalStateException("No scheme is found: " + destination);
|
||||
}
|
||||
|
||||
// obtain scheme handler
|
||||
final SchemeHandler schemeHandler = schemeHandlers.get(uri.getScheme());
|
||||
final SchemeHandler schemeHandler = schemeHandlers.get(scheme);
|
||||
if (schemeHandler != null) {
|
||||
|
||||
// handle scheme
|
||||
|
@ -10,9 +10,9 @@ import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import io.noties.markwon.image.DrawableUtils;
|
||||
import io.noties.markwon.image.MediaDecoder;
|
||||
import pl.droidsonroids.gif.GifDrawable;
|
||||
import io.noties.markwon.image.DrawableUtils;
|
||||
|
||||
/**
|
||||
* @since 1.1.0
|
||||
@ -22,11 +22,29 @@ public class GifMediaDecoder extends MediaDecoder {
|
||||
|
||||
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
|
||||
public static GifMediaDecoder create(boolean 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;
|
||||
|
||||
protected GifMediaDecoder(boolean autoPlayGif) {
|
||||
@ -105,7 +123,8 @@ public class GifMediaDecoder extends MediaDecoder {
|
||||
static void validate() {
|
||||
if (!HAS_GIF) {
|
||||
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.Collections;
|
||||
|
||||
import io.noties.markwon.image.MediaDecoder;
|
||||
import io.noties.markwon.image.DrawableUtils;
|
||||
import io.noties.markwon.image.MediaDecoder;
|
||||
|
||||
/**
|
||||
* @since 1.1.0
|
||||
@ -31,7 +31,7 @@ public class SvgMediaDecoder extends MediaDecoder {
|
||||
*/
|
||||
@NonNull
|
||||
public static SvgMediaDecoder create() {
|
||||
return new SvgMediaDecoder(Resources.getSystem());
|
||||
return create(Resources.getSystem());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -39,6 +39,14 @@ public class SvgMediaDecoder extends MediaDecoder {
|
||||
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;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@ -102,7 +110,7 @@ public class SvgMediaDecoder extends MediaDecoder {
|
||||
static void validate() {
|
||||
if (!HAS_SVG) {
|
||||
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.concurrent.ExecutorService;
|
||||
|
||||
import io.noties.markwon.image.network.NetworkSchemeHandler;
|
||||
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.assertFalse;
|
||||
@ -62,7 +62,7 @@ public class AsyncDrawableLoaderBuilderTest {
|
||||
public void defaults_initialized() {
|
||||
// default-media-decoder and executor-service must be initialized
|
||||
|
||||
assertNull(builder.defaultMediaDecoder);
|
||||
assertNotNull(builder.defaultMediaDecoder);
|
||||
assertNull(builder.executorService);
|
||||
|
||||
builder.build();
|
||||
@ -71,6 +71,18 @@ public class AsyncDrawableLoaderBuilderTest {
|
||||
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
|
||||
public void executor() {
|
||||
// supplied executor-service must be used
|
||||
@ -155,7 +167,7 @@ public class AsyncDrawableLoaderBuilderTest {
|
||||
@Test
|
||||
public void default_media_decoder() {
|
||||
|
||||
assertNull(builder.defaultMediaDecoder);
|
||||
assertNotNull(builder.defaultMediaDecoder);
|
||||
|
||||
final MediaDecoder mediaDecoder = mock(MediaDecoder.class);
|
||||
builder.defaultMediaDecoder(mediaDecoder);
|
||||
|
@ -1,5 +1,7 @@
|
||||
package io.noties.markwon.image;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
@ -7,15 +9,31 @@ import android.support.annotation.Nullable;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
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.annotation.Config;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
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.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
@ -40,60 +58,508 @@ public class AsyncDrawableLoaderImplTest {
|
||||
.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 {
|
||||
|
||||
AsyncDrawableLoaderBuilder builder;
|
||||
Handler handler = mock(Handler.class);
|
||||
AsyncDrawableLoaderBuilder _builder = new AsyncDrawableLoaderBuilder();
|
||||
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;
|
||||
}
|
||||
|
||||
public BuilderImpl addSchemeHandler(@NonNull SchemeHandler schemeHandler) {
|
||||
builder.addSchemeHandler(schemeHandler);
|
||||
BuilderImpl addSchemeHandler(@NonNull SchemeHandler schemeHandler) {
|
||||
_builder.addSchemeHandler(schemeHandler);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BuilderImpl addMediaDecoder(@NonNull MediaDecoder mediaDecoder) {
|
||||
builder.addMediaDecoder(mediaDecoder);
|
||||
BuilderImpl addMediaDecoder(@NonNull MediaDecoder mediaDecoder) {
|
||||
_builder.addMediaDecoder(mediaDecoder);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BuilderImpl defaultMediaDecoder(@Nullable MediaDecoder mediaDecoder) {
|
||||
builder.defaultMediaDecoder(mediaDecoder);
|
||||
BuilderImpl defaultMediaDecoder(@Nullable MediaDecoder mediaDecoder) {
|
||||
_builder.defaultMediaDecoder(mediaDecoder);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BuilderImpl removeSchemeHandler(@NonNull String scheme) {
|
||||
builder.removeSchemeHandler(scheme);
|
||||
BuilderImpl placeholderProvider(@NonNull ImagesPlugin.PlaceholderProvider placeholderDrawableProvider) {
|
||||
_builder.placeholderProvider(placeholderDrawableProvider);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BuilderImpl removeMediaDecoder(@NonNull String contentType) {
|
||||
builder.removeMediaDecoder(contentType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BuilderImpl placeholderProvider(@NonNull ImagesPlugin.PlaceholderProvider placeholderDrawableProvider) {
|
||||
builder.placeholderProvider(placeholderDrawableProvider);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BuilderImpl errorHandler(@NonNull ImagesPlugin.ErrorHandler errorHandler) {
|
||||
builder.errorHandler(errorHandler);
|
||||
BuilderImpl errorHandler(@NonNull ErrorHandler errorHandler) {
|
||||
_builder.errorHandler(errorHandler);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public BuilderImpl handler(Handler handler) {
|
||||
this.handler = handler;
|
||||
BuilderImpl handler(Handler handler) {
|
||||
this._handler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
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.MarkwonSpansFactory;
|
||||
import io.noties.markwon.SpanFactory;
|
||||
import ru.noties.markwon.image.R;
|
||||
import io.noties.markwon.image.data.DataUriSchemeHandler;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
Loading…
x
Reference in New Issue
Block a user