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
	 Dimitry Ivanov
						Dimitry Ivanov