latex block parsing tests
This commit is contained in:
parent
5c3763a9a1
commit
db660d2a40
@ -16,6 +16,7 @@
|
||||
* add `JLatexMathPlugin.ErrorHandler` to catch latex rendering errors and (optionally) display error drawable ([#204])
|
||||
* add `SoftBreakAddsNewLinePlugin` plugin (`core` module)
|
||||
* `LinkResolverDef` defaults to `https` when a link does not have scheme information ([#75])
|
||||
* add `option` abstraction for `sample` module allowing switching of multiple cases in runtime via menu
|
||||
|
||||
[#75]: https://github.com/noties/Markwon/issues/75
|
||||
[#204]: https://github.com/noties/Markwon/issues/204
|
||||
|
@ -133,18 +133,19 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
|
||||
return new Builder(JLatexMathTheme.builder(inlineTextSize, blockTextSize));
|
||||
}
|
||||
|
||||
public static class Config {
|
||||
@VisibleForTesting
|
||||
static class Config {
|
||||
|
||||
// @since 4.3.0-SNAPSHOT
|
||||
private final JLatexMathTheme theme;
|
||||
final JLatexMathTheme theme;
|
||||
|
||||
// @since 4.3.0-SNAPSHOT
|
||||
private final RenderMode renderMode;
|
||||
final RenderMode renderMode;
|
||||
|
||||
// @since 4.3.0-SNAPSHOT
|
||||
private final ErrorHandler errorHandler;
|
||||
final ErrorHandler errorHandler;
|
||||
|
||||
private final ExecutorService executorService;
|
||||
final ExecutorService executorService;
|
||||
|
||||
Config(@NonNull Builder builder) {
|
||||
this.theme = builder.theme.build();
|
||||
@ -159,7 +160,9 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private final Config config;
|
||||
@VisibleForTesting
|
||||
final Config config;
|
||||
|
||||
private final JLatextAsyncDrawableLoader jLatextAsyncDrawableLoader;
|
||||
private final JLatexBlockImageSizeResolver jLatexBlockImageSizeResolver;
|
||||
private final ImageSizeResolver inlineImageSizeResolver;
|
||||
|
@ -0,0 +1,173 @@
|
||||
package io.noties.markwon.ext.latex;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.commonmark.internal.BlockContinueImpl;
|
||||
import org.commonmark.internal.BlockStartImpl;
|
||||
import org.commonmark.internal.util.Parsing;
|
||||
import org.commonmark.parser.block.BlockStart;
|
||||
import org.commonmark.parser.block.ParserState;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
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;
|
||||
|
||||
public class JLatexMathBlockParserTest {
|
||||
|
||||
private static final String[] NO_MATCH = {
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
"$ ",
|
||||
" $ $",
|
||||
"-$$",
|
||||
" -$$",
|
||||
"$$-",
|
||||
" $$ -",
|
||||
" $$ -",
|
||||
"$$$ -"
|
||||
};
|
||||
|
||||
private static final String[] MATCH = {
|
||||
"$$",
|
||||
" $$",
|
||||
" $$",
|
||||
" $$",
|
||||
"$$ ",
|
||||
" $$ ",
|
||||
" $$ ",
|
||||
" $$ ",
|
||||
"$$$",
|
||||
" $$$",
|
||||
" $$$",
|
||||
"$$$$",
|
||||
" $$$$",
|
||||
"$$$$$$$$$$$$$$$$$$$$$",
|
||||
" $$$$$$$$$$$$$$$$$$$$$",
|
||||
" $$$$$$$$$$$$$$$$$$$$$",
|
||||
" $$$$$$$$$$$$$$$$$$$$$"
|
||||
};
|
||||
|
||||
private JLatexMathBlockParser.Factory factory;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
factory = new JLatexMathBlockParser.Factory();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void factory_indentBlock() {
|
||||
// when state indent is greater than block -> nono
|
||||
|
||||
final ParserState state = mock(ParserState.class);
|
||||
when(state.getIndent()).thenReturn(Parsing.CODE_BLOCK_INDENT);
|
||||
|
||||
// hm, interesting, `BlockStart.none()` actually returns null
|
||||
final BlockStart start = factory.tryStart(state, null);
|
||||
assertNull(start);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void factory_noMatch() {
|
||||
|
||||
for (String line : NO_MATCH) {
|
||||
final ParserState state = createState(line);
|
||||
|
||||
assertNull(factory.tryStart(state, null));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void factory_match() {
|
||||
|
||||
for (String line : MATCH) {
|
||||
final ParserState state = createState(line);
|
||||
|
||||
final BlockStart start = factory.tryStart(state, null);
|
||||
assertNotNull(start);
|
||||
|
||||
// hm...
|
||||
final BlockStartImpl impl = (BlockStartImpl) start;
|
||||
assertEquals(quote(line), line.length() + 1, impl.getNewIndex());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finish() {
|
||||
|
||||
for (String line : MATCH) {
|
||||
final ParserState state = createState(line);
|
||||
|
||||
// we will have 2 checks here:
|
||||
// * must pass for correct length
|
||||
// * must fail for incorrect
|
||||
|
||||
final int count = countDollarSigns(line);
|
||||
|
||||
// pass
|
||||
{
|
||||
final JLatexMathBlockParser parser = new JLatexMathBlockParser(count);
|
||||
final BlockContinueImpl impl = (BlockContinueImpl) parser.tryContinue(state);
|
||||
assertTrue(quote(line), impl.isFinalize());
|
||||
}
|
||||
|
||||
// fail (in terms of closing, not failing test)
|
||||
{
|
||||
final JLatexMathBlockParser parser = new JLatexMathBlockParser(count + 1);
|
||||
final BlockContinueImpl impl = (BlockContinueImpl) parser.tryContinue(state);
|
||||
assertFalse(quote(line), impl.isFinalize());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finish_noMatch() {
|
||||
for (String line : NO_MATCH) {
|
||||
final ParserState state = createState(line);
|
||||
// doesn't matter
|
||||
final int count = 2;
|
||||
final JLatexMathBlockParser parser = new JLatexMathBlockParser(count);
|
||||
final BlockContinueImpl impl = (BlockContinueImpl) parser.tryContinue(state);
|
||||
assertFalse(quote(line), impl.isFinalize());
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static ParserState createState(@NonNull String line) {
|
||||
|
||||
final ParserState state = mock(ParserState.class);
|
||||
|
||||
int i = 0;
|
||||
for (int length = line.length(); i < length; i++) {
|
||||
if (' ' != line.charAt(i)) {
|
||||
// previous is the last space
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
when(state.getIndent()).thenReturn(i);
|
||||
when(state.getNextNonSpaceIndex()).thenReturn(i);
|
||||
when(state.getLine()).thenReturn(line);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private static int countDollarSigns(@NonNull String line) {
|
||||
int count = 0;
|
||||
for (int i = 0, length = line.length(); i < length; i++) {
|
||||
if ('$' == line.charAt(i)) count += 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String quote(@NonNull String s) {
|
||||
return '\'' + s + '\'';
|
||||
}
|
||||
}
|
@ -10,17 +10,24 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import io.noties.markwon.MarkwonConfiguration;
|
||||
import io.noties.markwon.MarkwonPlugin;
|
||||
import io.noties.markwon.MarkwonVisitor;
|
||||
import io.noties.markwon.SpannableBuilder;
|
||||
import io.noties.markwon.inlineparser.InlineProcessor;
|
||||
import io.noties.markwon.inlineparser.MarkwonInlineParser;
|
||||
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
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;
|
||||
@ -110,4 +117,106 @@ public class JLatexMathPluginTest {
|
||||
|
||||
verify(visitor, times(1)).setSpans(eq(0), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacy() {
|
||||
// if render mode is legacy:
|
||||
// - no inline plugin is required,
|
||||
// - parser has legacy block parser factory
|
||||
// - no inline node is registered (node)
|
||||
|
||||
final JLatexMathPlugin plugin = JLatexMathPlugin.create(1, new JLatexMathPlugin.BuilderConfigure() {
|
||||
@Override
|
||||
public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) {
|
||||
builder.renderMode(JLatexMathPlugin.RenderMode.LEGACY);
|
||||
}
|
||||
});
|
||||
|
||||
// registry
|
||||
{
|
||||
final MarkwonPlugin.Registry registry = mock(MarkwonPlugin.Registry.class);
|
||||
plugin.configure(registry);
|
||||
verify(registry, never()).require(any(Class.class));
|
||||
}
|
||||
|
||||
// parser
|
||||
{
|
||||
final Parser.Builder builder = mock(Parser.Builder.class);
|
||||
plugin.configureParser(builder);
|
||||
|
||||
final ArgumentCaptor<BlockParserFactory> captor =
|
||||
ArgumentCaptor.forClass(BlockParserFactory.class);
|
||||
verify(builder, times(1)).customBlockParserFactory(captor.capture());
|
||||
final BlockParserFactory factory = captor.getValue();
|
||||
assertTrue(factory.getClass().getName(), factory instanceof JLatexMathBlockParserLegacy.Factory);
|
||||
}
|
||||
|
||||
// visitor
|
||||
{
|
||||
final MarkwonVisitor.Builder builder = mock(MarkwonVisitor.Builder.class);
|
||||
plugin.configureVisitor(builder);
|
||||
|
||||
final ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
|
||||
verify(builder, times(1)).on(captor.capture(), any(MarkwonVisitor.NodeVisitor.class));
|
||||
|
||||
assertEquals(JLatexMathBlock.class, captor.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blocks_inlines_implicit() {
|
||||
final JLatexMathPlugin plugin = JLatexMathPlugin.create(1);
|
||||
final JLatexMathPlugin.Config config = plugin.config;
|
||||
assertEquals(JLatexMathPlugin.RenderMode.BLOCKS_AND_INLINES, config.renderMode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blocks_inlines() {
|
||||
final JLatexMathPlugin plugin = JLatexMathPlugin.create(12);
|
||||
|
||||
// registry
|
||||
{
|
||||
final MarkwonInlineParser.FactoryBuilder factoryBuilder = mock(MarkwonInlineParser.FactoryBuilder.class);
|
||||
final MarkwonInlineParserPlugin inlineParserPlugin = mock(MarkwonInlineParserPlugin.class);
|
||||
final MarkwonPlugin.Registry registry = mock(MarkwonPlugin.Registry.class);
|
||||
when(inlineParserPlugin.factoryBuilder()).thenReturn(factoryBuilder);
|
||||
when(registry.require(eq(MarkwonInlineParserPlugin.class))).thenReturn(inlineParserPlugin);
|
||||
plugin.configure(registry);
|
||||
|
||||
verify(registry, times(1)).require(eq(MarkwonInlineParserPlugin.class));
|
||||
verify(inlineParserPlugin, times(1)).factoryBuilder();
|
||||
|
||||
final ArgumentCaptor<InlineProcessor> captor = ArgumentCaptor.forClass(InlineProcessor.class);
|
||||
verify(factoryBuilder, times(1)).addInlineProcessor(captor.capture());
|
||||
|
||||
final InlineProcessor inlineProcessor = captor.getValue();
|
||||
assertTrue(inlineParserPlugin.getClass().getName(), inlineProcessor instanceof JLatexMathInlineProcessor);
|
||||
}
|
||||
|
||||
// parser
|
||||
{
|
||||
final Parser.Builder builder = mock(Parser.Builder.class);
|
||||
plugin.configureParser(builder);
|
||||
|
||||
final ArgumentCaptor<BlockParserFactory> captor =
|
||||
ArgumentCaptor.forClass(BlockParserFactory.class);
|
||||
verify(builder, times(1)).customBlockParserFactory(captor.capture());
|
||||
final BlockParserFactory factory = captor.getValue();
|
||||
assertTrue(factory.getClass().getName(), factory instanceof JLatexMathBlockParser.Factory);
|
||||
}
|
||||
|
||||
// visitor
|
||||
{
|
||||
final MarkwonVisitor.Builder builder = mock(MarkwonVisitor.Builder.class);
|
||||
plugin.configureVisitor(builder);
|
||||
|
||||
final ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
|
||||
verify(builder, times(2)).on(captor.capture(), any(MarkwonVisitor.NodeVisitor.class));
|
||||
|
||||
final List<Class> nodes = captor.getAllValues();
|
||||
assertEquals(2, nodes.size());
|
||||
assertTrue(nodes.toString(), nodes.contains(JLatexMathNode.class));
|
||||
assertTrue(nodes.toString(), nodes.contains(JLatexMathBlock.class));
|
||||
}
|
||||
}
|
||||
}
|
@ -21,8 +21,12 @@ import io.noties.markwon.Markwon;
|
||||
import io.noties.markwon.MarkwonConfiguration;
|
||||
import io.noties.markwon.MarkwonSpansFactory;
|
||||
import io.noties.markwon.MarkwonVisitor;
|
||||
import io.noties.markwon.RenderProps;
|
||||
import io.noties.markwon.SoftBreakAddsNewLinePlugin;
|
||||
import io.noties.markwon.SpanFactory;
|
||||
import io.noties.markwon.core.CoreProps;
|
||||
import io.noties.markwon.core.MarkwonTheme;
|
||||
import io.noties.markwon.core.spans.LastLineSpacingSpan;
|
||||
import io.noties.markwon.image.ImageItem;
|
||||
import io.noties.markwon.image.ImagesPlugin;
|
||||
import io.noties.markwon.image.SchemeHandler;
|
||||
@ -46,7 +50,9 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions {
|
||||
.add("linkWithMovementMethod", this::linkWithMovementMethod)
|
||||
.add("imagesPlugin", this::imagesPlugin)
|
||||
.add("softBreakAddsSpace", this::softBreakAddsSpace)
|
||||
.add("softBreakAddsNewLine", this::softBreakAddsNewLine);
|
||||
.add("softBreakAddsNewLine", this::softBreakAddsNewLine)
|
||||
.add("additionalSpacing", this::additionalSpacing)
|
||||
.add("headingNoSpace", this::headingNoSpace);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -242,6 +248,69 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions {
|
||||
markwon.setMarkdown(textView, md);
|
||||
}
|
||||
|
||||
private void additionalSpacing() {
|
||||
|
||||
// please note that bottom line (after 1 & 2 levels) will be drawn _AFTER_ padding
|
||||
final int spacing = (int) (128 * getResources().getDisplayMetrics().density + .5F);
|
||||
|
||||
final Markwon markwon = Markwon.builder(this)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
|
||||
builder.headingBreakHeight(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||
builder.appendFactory(
|
||||
Heading.class,
|
||||
(configuration, props) -> new LastLineSpacingSpan(spacing));
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
final String md = "" +
|
||||
"# Title title title title title title title title title title \n\ntext text text text";
|
||||
|
||||
markwon.setMarkdown(textView, md);
|
||||
}
|
||||
|
||||
private void headingNoSpace() {
|
||||
final Markwon markwon = Markwon.builder(this)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
|
||||
builder.headingBreakHeight(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||
builder.on(Heading.class, (visitor, heading) -> {
|
||||
|
||||
visitor.ensureNewLine();
|
||||
|
||||
final int length = visitor.length();
|
||||
visitor.visitChildren(heading);
|
||||
|
||||
CoreProps.HEADING_LEVEL.set(visitor.renderProps(), heading.getLevel());
|
||||
|
||||
visitor.setSpansForNodeOptional(heading, length);
|
||||
|
||||
if (visitor.hasNext(heading)) {
|
||||
visitor.ensureNewLine();
|
||||
// visitor.forceNewLine();
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
final String md = "" +
|
||||
"# Title title title title title title title title title title \n\ntext text text text";
|
||||
|
||||
markwon.setMarkdown(textView, md);
|
||||
}
|
||||
|
||||
// public void step_6() {
|
||||
//
|
||||
// final Markwon markwon = Markwon.builder(this)
|
||||
@ -270,5 +339,4 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions {
|
||||
// rendering lifecycle (before/after)
|
||||
// renderProps
|
||||
// process
|
||||
// priority
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user