Add tests for markwon-image module
This commit is contained in:
parent
cedb3971a0
commit
6bf04e38ad
@ -165,14 +165,17 @@ public class CorePlugin extends AbstractMarkwonPlugin {
|
||||
@Override
|
||||
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Text text) {
|
||||
|
||||
final int length = visitor.length();
|
||||
final String literal = text.getLiteral();
|
||||
|
||||
visitor.builder().append(literal);
|
||||
|
||||
// @since 4.0.0-SNAPSHOT
|
||||
for (OnTextAddedListener onTextAddedListener : onTextAddedListeners) {
|
||||
onTextAddedListener.onTextAdded(visitor, literal, length);
|
||||
if (!onTextAddedListeners.isEmpty()) {
|
||||
// calculate the start position
|
||||
final int length = visitor.length() - literal.length();
|
||||
for (OnTextAddedListener onTextAddedListener : onTextAddedListeners) {
|
||||
onTextAddedListener.onTextAdded(visitor, literal, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -27,6 +27,4 @@ public abstract class AsyncDrawableLoader {
|
||||
@Nullable
|
||||
public abstract Drawable placeholder(@NonNull AsyncDrawable drawable);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
public class AsyncDrawableLoaderNoOp extends AsyncDrawableLoader {
|
||||
class AsyncDrawableLoaderNoOp extends AsyncDrawableLoader {
|
||||
@Override
|
||||
public void load(@NonNull AsyncDrawable drawable) {
|
||||
|
||||
|
@ -93,6 +93,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
|
||||
|
||||
private final JLatextAsyncDrawableLoader jLatextAsyncDrawableLoader;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
JLatexMathPlugin(@NonNull Config config) {
|
||||
this.jLatextAsyncDrawableLoader = new JLatextAsyncDrawableLoader(config);
|
||||
}
|
||||
|
@ -8,6 +8,9 @@ import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import ru.noties.markwon.image.data.DataUriSchemeHandler;
|
||||
import ru.noties.markwon.image.network.NetworkSchemeHandler;
|
||||
|
||||
class AsyncDrawableLoaderBuilder {
|
||||
|
||||
ExecutorService executorService;
|
||||
@ -19,6 +22,15 @@ class AsyncDrawableLoaderBuilder {
|
||||
|
||||
boolean isBuilt;
|
||||
|
||||
AsyncDrawableLoaderBuilder() {
|
||||
|
||||
// @since 4.0.0-SNAPSHOT
|
||||
// okay, let's add supported schemes at the start, this would be : data-uri and default network
|
||||
// we should not use file-scheme as it's a bit complicated to assume file usage (lack of permissions)
|
||||
addSchemeHandler(DataUriSchemeHandler.create());
|
||||
addSchemeHandler(NetworkSchemeHandler.create());
|
||||
}
|
||||
|
||||
void executorService(@NonNull ExecutorService executorService) {
|
||||
this.executorService = executorService;
|
||||
}
|
||||
@ -66,12 +78,6 @@ class AsyncDrawableLoaderBuilder {
|
||||
|
||||
isBuilt = true;
|
||||
|
||||
// we must have schemeHandlers registered (we will provide
|
||||
// default media decoder if it's absent)
|
||||
if (schemeHandlers.size() == 0) {
|
||||
return new AsyncDrawableLoaderNoOp();
|
||||
}
|
||||
|
||||
// @since 4.0.0-SNAPSHOT
|
||||
if (defaultMediaDecoder == null) {
|
||||
defaultMediaDecoder = DefaultImageMediaDecoder.create();
|
||||
@ -83,5 +89,4 @@ class AsyncDrawableLoaderBuilder {
|
||||
|
||||
return new AsyncDrawableLoaderImpl(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import ru.noties.markwon.AbstractMarkwonPlugin;
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.MarkwonSpansFactory;
|
||||
|
||||
@SuppressWarnings({"UnusedReturnValue", "WeakerAccess"})
|
||||
public class ImagesPlugin extends AbstractMarkwonPlugin {
|
||||
|
||||
/**
|
||||
@ -91,6 +92,12 @@ public class ImagesPlugin extends AbstractMarkwonPlugin {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DefaultImageMediaDecoder
|
||||
* @see ru.noties.markwon.image.svg.SvgMediaDecoder
|
||||
* @see ru.noties.markwon.image.gif.GifMediaDecoder
|
||||
* @since 4.0.0-SNAPSHOT
|
||||
*/
|
||||
@NonNull
|
||||
public ImagesPlugin addMediaDecoder(@NonNull MediaDecoder mediaDecoder) {
|
||||
checkBuilderState();
|
||||
@ -98,13 +105,23 @@ public class ImagesPlugin extends AbstractMarkwonPlugin {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Please note that if not specified a {@link DefaultImageMediaDecoder} will be used. So
|
||||
* if you need to disable default-image-media-decoder specify here own no-op implementation.
|
||||
*
|
||||
* @see DefaultImageMediaDecoder
|
||||
* @since 4.0.0-SNAPSHOT
|
||||
*/
|
||||
@NonNull
|
||||
public ImagesPlugin defaultMediaDecoder(@Nullable MediaDecoder mediaDecoder) {
|
||||
public ImagesPlugin defaultMediaDecoder(@NonNull MediaDecoder mediaDecoder) {
|
||||
checkBuilderState();
|
||||
builder.defaultMediaDecoder(mediaDecoder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.0.0-SNAPSHOT
|
||||
*/
|
||||
@NonNull
|
||||
public ImagesPlugin removeSchemeHandler(@NonNull String scheme) {
|
||||
checkBuilderState();
|
||||
@ -112,6 +129,9 @@ public class ImagesPlugin extends AbstractMarkwonPlugin {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.0.0-SNAPSHOT
|
||||
*/
|
||||
@NonNull
|
||||
public ImagesPlugin removeMediaDecoder(@NonNull String contentType) {
|
||||
checkBuilderState();
|
||||
@ -119,6 +139,9 @@ public class ImagesPlugin extends AbstractMarkwonPlugin {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.0.0-SNAPSHOT
|
||||
*/
|
||||
@NonNull
|
||||
public ImagesPlugin placeholderProvider(@NonNull PlaceholderProvider placeholderProvider) {
|
||||
checkBuilderState();
|
||||
@ -126,6 +149,10 @@ public class ImagesPlugin extends AbstractMarkwonPlugin {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ErrorHandler
|
||||
* @since 4.0.0-SNAPSHOT
|
||||
*/
|
||||
@NonNull
|
||||
public ImagesPlugin errorHandler(@NonNull ErrorHandler errorHandler) {
|
||||
checkBuilderState();
|
||||
|
@ -0,0 +1,194 @@
|
||||
package ru.noties.markwon.image;
|
||||
|
||||
import org.junit.Before;
|
||||
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.concurrent.ExecutorService;
|
||||
|
||||
import ru.noties.markwon.image.data.DataUriSchemeHandler;
|
||||
import ru.noties.markwon.image.network.NetworkSchemeHandler;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class AsyncDrawableLoaderBuilderTest {
|
||||
|
||||
private AsyncDrawableLoaderBuilder builder;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
builder = new AsyncDrawableLoaderBuilder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void default_scheme_handlers() {
|
||||
// builder adds default data-uri and network scheme-handlers
|
||||
|
||||
final String[] registered = {
|
||||
DataUriSchemeHandler.SCHEME,
|
||||
NetworkSchemeHandler.SCHEME_HTTP,
|
||||
NetworkSchemeHandler.SCHEME_HTTPS
|
||||
};
|
||||
|
||||
for (String scheme : registered) {
|
||||
assertNotNull(scheme, builder.schemeHandlers.get(scheme));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void built_flag() {
|
||||
// isBuilt flag must be set after `build` method call
|
||||
|
||||
assertFalse(builder.isBuilt);
|
||||
|
||||
builder.build();
|
||||
|
||||
assertTrue(builder.isBuilt);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaults_initialized() {
|
||||
// default-media-decoder and executor-service must be initialized
|
||||
|
||||
assertNull(builder.defaultMediaDecoder);
|
||||
assertNull(builder.executorService);
|
||||
|
||||
builder.build();
|
||||
|
||||
assertNotNull(builder.defaultMediaDecoder);
|
||||
assertNotNull(builder.executorService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executor() {
|
||||
// supplied executor-service must be used
|
||||
|
||||
assertNull(builder.executorService);
|
||||
|
||||
final ExecutorService service = mock(ExecutorService.class);
|
||||
builder.executorService(service);
|
||||
|
||||
builder.build();
|
||||
|
||||
assertEquals(service, builder.executorService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void add_scheme_handler() {
|
||||
|
||||
final String scheme = "mock";
|
||||
assertNull(builder.schemeHandlers.get(scheme));
|
||||
|
||||
final SchemeHandler schemeHandler = mock(SchemeHandler.class);
|
||||
when(schemeHandler.supportedSchemes()).thenReturn(Collections.singleton(scheme));
|
||||
|
||||
builder.addSchemeHandler(schemeHandler);
|
||||
builder.build();
|
||||
|
||||
assertEquals(schemeHandler, builder.schemeHandlers.get(scheme));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void add_scheme_handler_multiple_types() {
|
||||
// all supported types are registered
|
||||
|
||||
final String[] schemes = {
|
||||
"mock-1",
|
||||
"mock-2"
|
||||
};
|
||||
|
||||
final SchemeHandler schemeHandler = mock(SchemeHandler.class);
|
||||
when(schemeHandler.supportedSchemes()).thenReturn(Arrays.asList(schemes));
|
||||
|
||||
builder.addSchemeHandler(schemeHandler);
|
||||
|
||||
for (String scheme : schemes) {
|
||||
assertEquals(scheme, schemeHandler, builder.schemeHandlers.get(scheme));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void add_media_decoder() {
|
||||
|
||||
final String media = "mocked/type";
|
||||
assertNull(builder.mediaDecoders.get(media));
|
||||
|
||||
final MediaDecoder mediaDecoder = mock(MediaDecoder.class);
|
||||
when(mediaDecoder.supportedTypes()).thenReturn(Collections.singleton(media));
|
||||
|
||||
builder.addMediaDecoder(mediaDecoder);
|
||||
builder.build();
|
||||
|
||||
assertEquals(mediaDecoder, builder.mediaDecoders.get(media));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void add_media_decoder_multiple_types() {
|
||||
|
||||
final String[] types = {
|
||||
"mock/type1",
|
||||
"mock/type2"
|
||||
};
|
||||
|
||||
final MediaDecoder mediaDecoder = mock(MediaDecoder.class);
|
||||
when(mediaDecoder.supportedTypes()).thenReturn(Arrays.asList(types));
|
||||
|
||||
builder.addMediaDecoder(mediaDecoder);
|
||||
|
||||
for (String type : types) {
|
||||
assertEquals(type, mediaDecoder, builder.mediaDecoders.get(type));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void default_media_decoder() {
|
||||
|
||||
assertNull(builder.defaultMediaDecoder);
|
||||
|
||||
final MediaDecoder mediaDecoder = mock(MediaDecoder.class);
|
||||
builder.defaultMediaDecoder(mediaDecoder);
|
||||
builder.build();
|
||||
|
||||
assertEquals(mediaDecoder, builder.defaultMediaDecoder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remove_scheme_handler() {
|
||||
|
||||
final String scheme = "mock";
|
||||
final SchemeHandler schemeHandler = mock(SchemeHandler.class);
|
||||
when(schemeHandler.supportedSchemes()).thenReturn(Collections.singleton(scheme));
|
||||
|
||||
assertNull(builder.schemeHandlers.get(scheme));
|
||||
builder.addSchemeHandler(schemeHandler);
|
||||
assertNotNull(builder.schemeHandlers.get(scheme));
|
||||
builder.removeSchemeHandler(scheme);
|
||||
assertNull(builder.schemeHandlers.get(scheme));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remove_media_decoder() {
|
||||
|
||||
final String media = "mock/type";
|
||||
final MediaDecoder mediaDecoder = mock(MediaDecoder.class);
|
||||
when(mediaDecoder.supportedTypes()).thenReturn(Collections.singleton(media));
|
||||
|
||||
assertNull(builder.mediaDecoders.get(media));
|
||||
builder.addMediaDecoder(mediaDecoder);
|
||||
assertNotNull(builder.mediaDecoders.get(media));
|
||||
builder.removeMediaDecoder(media);
|
||||
assertNull(builder.mediaDecoders.get(media));
|
||||
}
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
package ru.noties.markwon.image;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.Spanned;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.commonmark.node.Image;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import ru.noties.markwon.MarkwonConfiguration;
|
||||
import ru.noties.markwon.MarkwonSpansFactory;
|
||||
import ru.noties.markwon.SpanFactory;
|
||||
import ru.noties.markwon.image.data.DataUriSchemeHandler;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class ImagesPluginTest {
|
||||
|
||||
private ImagesPlugin plugin;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
plugin = ImagesPlugin.create();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void build_state() {
|
||||
// it's not possible to mutate images-plugin after `configureConfiguration` call
|
||||
|
||||
// validate that it doesn't throw here
|
||||
plugin.addSchemeHandler(DataUriSchemeHandler.create());
|
||||
|
||||
// mark the state
|
||||
plugin.configureConfiguration(mock(MarkwonConfiguration.Builder.class));
|
||||
|
||||
final class Throws {
|
||||
private void assertThrows(@NonNull Runnable action) {
|
||||
//noinspection CatchMayIgnoreException
|
||||
try {
|
||||
action.run();
|
||||
fail();
|
||||
} catch (Throwable t) {
|
||||
assertTrue(t.getMessage(), t.getMessage().contains("ImagesPlugin has already been configured"));
|
||||
}
|
||||
}
|
||||
}
|
||||
final Throws check = new Throws();
|
||||
|
||||
// executor-service
|
||||
check.assertThrows(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.executorService(mock(ExecutorService.class));
|
||||
}
|
||||
});
|
||||
|
||||
// add-scheme-handler
|
||||
check.assertThrows(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.addSchemeHandler(mock(SchemeHandler.class));
|
||||
}
|
||||
});
|
||||
|
||||
// add-media-decoder
|
||||
check.assertThrows(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.addMediaDecoder(mock(MediaDecoder.class));
|
||||
}
|
||||
});
|
||||
|
||||
// default-media-decoder
|
||||
check.assertThrows(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.defaultMediaDecoder(mock(MediaDecoder.class));
|
||||
}
|
||||
});
|
||||
|
||||
// remove-scheme-handler
|
||||
check.assertThrows(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.removeSchemeHandler("mock");
|
||||
}
|
||||
});
|
||||
|
||||
// remove-media-decoder
|
||||
check.assertThrows(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.removeMediaDecoder("mock/type");
|
||||
}
|
||||
});
|
||||
|
||||
// placeholder-provider
|
||||
check.assertThrows(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.placeholderProvider(mock(ImagesPlugin.PlaceholderProvider.class));
|
||||
}
|
||||
});
|
||||
|
||||
// error-handler
|
||||
check.assertThrows(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.errorHandler(mock(ImagesPlugin.ErrorHandler.class));
|
||||
}
|
||||
});
|
||||
|
||||
// final check if for actual `configureConfiguration` call (must be called only once)
|
||||
check.assertThrows(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.configureConfiguration(mock(MarkwonConfiguration.Builder.class));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void image_span_factory_registered() {
|
||||
|
||||
final MarkwonSpansFactory.Builder builder = mock(MarkwonSpansFactory.Builder.class);
|
||||
|
||||
plugin.configureSpansFactory(builder);
|
||||
|
||||
final ArgumentCaptor<SpanFactory> captor = ArgumentCaptor.forClass(SpanFactory.class);
|
||||
|
||||
verify(builder, times(1))
|
||||
.setFactory(eq(Image.class), captor.capture());
|
||||
|
||||
assertNotNull(captor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void before_set_text() {
|
||||
// verify that AsyncDrawableScheduler is called
|
||||
|
||||
final TextView textView = mock(TextView.class);
|
||||
|
||||
plugin.beforeSetText(textView, mock(Spanned.class));
|
||||
|
||||
verify(textView, times(1))
|
||||
.getTag(eq(R.id.markwon_drawables_scheduler_last_text_hashcode));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void after_set_text() {
|
||||
// verify that AsyncDrawableScheduler is called
|
||||
|
||||
final TextView textView = mock(TextView.class);
|
||||
when(textView.getText()).thenReturn("some text");
|
||||
|
||||
plugin.afterSetText(textView);
|
||||
|
||||
verify(textView, times(1))
|
||||
.getTag(eq(R.id.markwon_drawables_scheduler_last_text_hashcode));
|
||||
}
|
||||
}
|
5
markwon-linkify/README.md
Normal file
5
markwon-linkify/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Linkify
|
||||
|
||||
Use this module (or take a hint from it) if you would need _linkify_ capabilities. Do not
|
||||
use `TextView.setAutolinkMask` (or specify `autolink` in XML) because it will remove all
|
||||
existing links and keep only the ones it creates.
|
@ -18,8 +18,7 @@ public class LinkifyPlugin extends AbstractMarkwonPlugin {
|
||||
@IntDef(flag = true, value = {
|
||||
Linkify.EMAIL_ADDRESSES,
|
||||
Linkify.PHONE_NUMBERS,
|
||||
Linkify.WEB_URLS,
|
||||
Linkify.ALL
|
||||
Linkify.WEB_URLS
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface LinkifyMask {
|
||||
|
Loading…
x
Reference in New Issue
Block a user