public void Undo_ReplacesProxyTableBlockWithAParagraphBlock() { // Arrange const string dummyText = "dummyText"; var dummyProxyTableBlock = new ProxyTableBlock(null); dummyProxyTableBlock.Lines = new StringLineGroup(dummyText); BlockParser dummyBlockParser = _mockRepository.Create <BlockParser>().Object; ContainerBlock dummyParent = _mockRepository.Create <ContainerBlock>(dummyBlockParser).Object; // Must specify block parser since we're calling ProcessLine later dummyParent.Add(dummyProxyTableBlock); // Assigns dummyParent to dummyProxyTableBlock.Parent BlockProcessor dummyBlockProcessor = MarkdigTypesFactory.CreateBlockProcessor(); dummyBlockProcessor.Open(dummyParent); dummyBlockProcessor.Open(dummyProxyTableBlock); ExposedFlexiTableBlockParser testSubject = CreateExposedFlexiTableBlockParser(); // Act testSubject.ExposedUndo(dummyBlockProcessor, dummyProxyTableBlock); // Assert Assert.Single(dummyParent); var resultParagraphBlock = dummyParent[0] as ParagraphBlock; Assert.NotNull(resultParagraphBlock); Assert.Equal(dummyText, resultParagraphBlock.Lines.ToString()); // Verify that ParagraphBlock remains open dummyBlockProcessor.ProcessLine(new StringSlice(dummyText)); Assert.Equal($"{dummyText}\n{dummyText}", resultParagraphBlock.Lines.ToString()); }
public void UpdateOpenFlexiSectionBlocks_DiscardsStacksForClosedTreesAndCreatesANewStackForANewTree() { // Arrange var dummyCurrentBranch = new Stack <FlexiSectionBlock>(); dummyCurrentBranch.Push(CreateFlexiSectionBlock()); FlexiSectionBlock dummyClosedFlexiSectionBlock = CreateFlexiSectionBlock(); dummyClosedFlexiSectionBlock.IsOpen = false; var dummyClosedBranchStack = new Stack <FlexiSectionBlock>(); dummyClosedBranchStack.Push(dummyClosedFlexiSectionBlock); var dummyOpenSectionBlocks = new Stack <Stack <FlexiSectionBlock> >(); dummyOpenSectionBlocks.Push(dummyCurrentBranch); dummyOpenSectionBlocks.Push(dummyClosedBranchStack); BlockProcessor dummyBlockProcessor = MarkdigTypesFactory.CreateBlockProcessor(); dummyBlockProcessor.ProcessLine(new StringSlice("")); // This line sets BlockProcessor.CurrentContainer to a MarkdownDocument FlexiSectionBlock dummyNewFlexiSectionBlock = CreateFlexiSectionBlock(); Mock <FlexiSectionBlockFactory> mockTestSubject = CreateMockFlexiSectionBlockFactory(); mockTestSubject.CallBase = true; mockTestSubject.Setup(t => t.GetOrCreateOpenFlexiSectionBlocks(dummyBlockProcessor.Document)).Returns(dummyOpenSectionBlocks); // Act mockTestSubject.Object.UpdateOpenFlexiSectionBlocks(dummyBlockProcessor, dummyNewFlexiSectionBlock); // Assert _mockRepository.VerifyAll(); Assert.Equal(2, dummyOpenSectionBlocks.Count); // Closed tree removed, open tree remains, new tree added Assert.Same(dummyNewFlexiSectionBlock, dummyOpenSectionBlocks.Pop().Peek()); Assert.Same(dummyCurrentBranch, dummyOpenSectionBlocks.Peek()); }
//Process block elements in lines private void ProcessLines() { while (true) { var textLine = lineReader.ReadLine(); if (textLine == null) { break; } blockProcessor.ProcessLine(textLine); } }
public void Create_CreatesFlexiCardBlock() { // Arrange const int dummyColumn = 2; var dummyLine = new StringSlice("dummyText", 3, 8); const string dummyUrl = "dummyUrl"; const string dummyBackgroundIcon = "dummyBackgroundIcon"; IFlexiCardBlockOptions dummyDefaultCardOptions = _mockRepository.Create <IFlexiCardBlockOptions>().Object; Mock <IFlexiCardsBlockOptions> mockFlexiCardsBlockOptions = _mockRepository.Create <IFlexiCardsBlockOptions>(); mockFlexiCardsBlockOptions.Setup(f => f.DefaultCardOptions).Returns(dummyDefaultCardOptions); Mock <BlockParser> dummyBlockParser = _mockRepository.Create <BlockParser>(); ProxyFlexiCardsBlock dummyProxyFlexiCardsBlock = CreateProxyFlexiCardsBlock(mockFlexiCardsBlockOptions.Object, blockParser: dummyBlockParser.Object); BlockProcessor dummyBlockProcessor = MarkdigTypesFactory.CreateBlockProcessor(); // Following 3 lines set dummyBlockProcessor.CurrentContainer dummyBlockParser.Setup(b => b.TryContinue(dummyBlockProcessor, dummyProxyFlexiCardsBlock)).Returns(BlockState.ContinueDiscard); dummyBlockProcessor.Open(dummyProxyFlexiCardsBlock); dummyBlockProcessor.ProcessLine(new StringSlice(string.Empty)); dummyBlockProcessor.Column = dummyColumn; dummyBlockProcessor.Line = dummyLine; var dummyAttributes = new ReadOnlyDictionary <string, string>(new Dictionary <string, string>()); Mock <IFlexiCardBlockOptions> mockFlexiCardBlockOptions = _mockRepository.Create <IFlexiCardBlockOptions>(); mockFlexiCardBlockOptions.Setup(f => f.Url).Returns(dummyUrl); mockFlexiCardBlockOptions.Setup(f => f.BackgroundIcon).Returns(dummyBackgroundIcon); mockFlexiCardBlockOptions.Setup(f => f.Attributes).Returns(dummyAttributes); Mock <IBlockOptionsFactory <IFlexiCardBlockOptions> > mockFlexiCardBlockOptionsFactory = _mockRepository. Create <IBlockOptionsFactory <IFlexiCardBlockOptions> >(); mockFlexiCardBlockOptionsFactory.Setup(f => f.Create(dummyDefaultCardOptions, dummyBlockProcessor)).Returns(mockFlexiCardBlockOptions.Object); FlexiCardBlockFactory testSubject = CreateFlexiCardBlockFactory(mockFlexiCardBlockOptionsFactory.Object); // Act FlexiCardBlock result = testSubject.Create(dummyBlockProcessor, dummyBlockParser.Object); // Assert _mockRepository.VerifyAll(); Assert.Equal(dummyUrl, result.Url); Assert.Equal(dummyBackgroundIcon, result.BackgroundIcon); Assert.Same(dummyAttributes, result.Attributes); Assert.Same(dummyBlockParser.Object, result.Parser); Assert.Equal(dummyColumn, result.Column); Assert.Equal(dummyLine.Start, result.Span.Start); Assert.Equal(0, result.Span.End); }
public void UpdateOpenFlexiSectionBlocks_IgnoresAncestorContainerBlocksThatAreClosed() { // Arrange BlockProcessor dummyBlockProcessor = MarkdigTypesFactory.CreateBlockProcessor(); // Create abitrary closed blocks (ListItemBlock and ListBlock) and set BlockProcessor.CurrentContainer to dummyListItemBlock. // Closed blocks should be ignored, so UpdateOpenFlexiSectionBlocks should not create a new stack. Mock <BlockParser> mockBlockParser = _mockRepository.Create <BlockParser>(); mockBlockParser.Setup(b => b.TryContinue(dummyBlockProcessor, It.IsAny <Block>())).Returns(BlockState.Continue); var dummyListItemBlock = new ListItemBlock(mockBlockParser.Object); var dummyListBlock = new ListBlock(null) { dummyListItemBlock }; FlexiSectionBlock dummyFlexiSectionBlock = CreateFlexiSectionBlock(); dummyFlexiSectionBlock.Add(dummyListBlock); dummyBlockProcessor.Open(dummyListItemBlock); dummyBlockProcessor.ProcessLine(new StringSlice("")); // This line sets BlockProcessor.CurrentContainer to dummyListItemBlock dummyListItemBlock.IsOpen = false; dummyListBlock.IsOpen = false; FlexiSectionBlock dummyNewFlexiSectionBlock = CreateFlexiSectionBlock(); var dummyCurrentBranch = new Stack <FlexiSectionBlock>(); dummyCurrentBranch.Push(dummyFlexiSectionBlock); var dummyOpenSectionBlocks = new Stack <Stack <FlexiSectionBlock> >(); dummyOpenSectionBlocks.Push(dummyCurrentBranch); Mock <FlexiSectionBlockFactory> mockTestSubject = CreateMockFlexiSectionBlockFactory(); mockTestSubject.CallBase = true; mockTestSubject.Setup(t => t.GetOrCreateOpenFlexiSectionBlocks(dummyBlockProcessor.Document)).Returns(dummyOpenSectionBlocks); // Act mockTestSubject.Object.UpdateOpenFlexiSectionBlocks(dummyBlockProcessor, dummyNewFlexiSectionBlock); // Assert _mockRepository.VerifyAll(); Assert.Single(dummyCurrentBranch); Assert.Equal(dummyNewFlexiSectionBlock, dummyCurrentBranch.Peek()); }
public void UpdateOpenFlexiSectionBlocks_ClosesFlexiSectionBlocksInCurrentBranchWithTheSameOrHigherLevels() { // Arrange BlockProcessor dummyBlockProcessor = MarkdigTypesFactory.CreateBlockProcessor(); Mock <BlockParser> mockBlockParser = _mockRepository.Create <BlockParser>(); mockBlockParser.Setup(b => b.TryContinue(dummyBlockProcessor, It.IsAny <Block>())).Returns(BlockState.Continue); FlexiSectionBlock dummyLevel1FlexiSectionBlock = CreateFlexiSectionBlock(level: 1, blockParser: mockBlockParser.Object); FlexiSectionBlock dummyLevel2FlexiSectionBlock = CreateFlexiSectionBlock(level: 2, blockParser: mockBlockParser.Object); FlexiSectionBlock dummyLevel3FlexiSectionBlock = CreateFlexiSectionBlock(level: 3, blockParser: mockBlockParser.Object); dummyBlockProcessor.Open(dummyLevel1FlexiSectionBlock); dummyBlockProcessor.Open(dummyLevel2FlexiSectionBlock); dummyBlockProcessor.Open(dummyLevel3FlexiSectionBlock); dummyBlockProcessor.ProcessLine(new StringSlice("")); // Sets BlockProcessor.CurrentContainer to a FlexiSectionBlock var dummyCurrentBranch = new Stack <FlexiSectionBlock>(); dummyCurrentBranch.Push(dummyLevel1FlexiSectionBlock); dummyCurrentBranch.Push(dummyLevel2FlexiSectionBlock); dummyCurrentBranch.Push(dummyLevel3FlexiSectionBlock); var dummyOpenSectionBlocks = new Stack <Stack <FlexiSectionBlock> >(); dummyOpenSectionBlocks.Push(dummyCurrentBranch); FlexiSectionBlock dummyNewFlexiSectionBlock = CreateFlexiSectionBlock(level: 2); Mock <FlexiSectionBlockFactory> mockTestSubject = CreateMockFlexiSectionBlockFactory(); mockTestSubject.CallBase = true; mockTestSubject.Setup(t => t.GetOrCreateOpenFlexiSectionBlocks(dummyBlockProcessor.Document)).Returns(dummyOpenSectionBlocks); // Act mockTestSubject.Object.UpdateOpenFlexiSectionBlocks(dummyBlockProcessor, dummyNewFlexiSectionBlock); // Assert _mockRepository.VerifyAll(); Assert.Equal(2, dummyCurrentBranch.Count); // Level 2 and level 3 blocks get removed Assert.Same(dummyNewFlexiSectionBlock, dummyCurrentBranch.Pop()); Assert.Same(dummyLevel1FlexiSectionBlock, dummyCurrentBranch.Peek()); mockBlockParser.Verify(b => b.Close(dummyBlockProcessor, dummyLevel2FlexiSectionBlock)); // Gets closed mockBlockParser.Verify(b => b.Close(dummyBlockProcessor, dummyLevel3FlexiSectionBlock)); // Gets closed }
internal virtual void ProcessContent(BlockProcessor blockProcessor, FlexiIncludeBlock flexiIncludeBlock, ContainerBlock parentOfNewBlocks, ReadOnlyCollection <string> content) { // The method used here is also used for GridTables. Using a child processor avoids conflicts with existing // open blocks in the root processor. BlockProcessor childProcessor = blockProcessor.CreateChild(); childProcessor.Open(parentOfNewBlocks); // If content is code, start with ``` bool isCode = flexiIncludeBlock.Type == FlexiIncludeType.Code; if (isCode) { childProcessor.ProcessLine(_codeBlockFence); } // TODO this has the potential to be really slow if content has lots of lines and we've got lots of clippings using start/end strings // Clippings - need not be sequential, they can also overlap int contentNumLines = content.Count; foreach (Clipping clipping in flexiIncludeBlock.Clippings ?? _defaultClippings) { string before = clipping.Before; if (isCode && before != null) { childProcessor.ProcessLine(new StringSlice(before)); // No issue even if Before is multiline since we're in a code block } int startLineNumber = -1; (string startString, string endString) = clipping.GetNormalizedStartAndEndStrings(); bool startStringSpecified = startString != null; bool endStringSpecified = endString != null; (int normalizedStartLine, int normalizedEndLine)normalizedStartAndEndLines = default; if (!startStringSpecified || !endStringSpecified) { normalizedStartAndEndLines = clipping.GetNormalizedStartAndEndLines(contentNumLines); } if (startStringSpecified) { int lastIndex = contentNumLines - 2; // Since demarcation lines are not included in the clipping, the last line cannot be a start demarcation line. for (int i = 0; i <= lastIndex; i++) { if (content[i].Contains(startString)) { startLineNumber = i + 2; break; } } if (startLineNumber == -1) { throw new OptionsException(nameof(Clipping.StartString), string.Format(Strings.OptionsException_FlexiIncludeBlockFactory_NoLineContainsStartString, startString)); } } else { startLineNumber = normalizedStartAndEndLines.normalizedStartLine; } // If we encounter an invalid block in the included content, this ensures the BlockException thrown has the right line number in the included content's source. childProcessor.LineIndex = startLineNumber - 1; for (int lineNumber = startLineNumber; lineNumber <= contentNumLines; lineNumber++) { var stringSlice = new StringSlice(content[lineNumber - 1]); if (!stringSlice.IsEmpty) { if (clipping.Indent > 0) { stringSlice = _leadingWhitespaceEditorService.Indent(stringSlice, clipping.Indent); } _leadingWhitespaceEditorService.Dedent(ref stringSlice, clipping.Dedent); _leadingWhitespaceEditorService.Collapse(ref stringSlice, clipping.Collapse); } childProcessor.ProcessLine(stringSlice); // Check whether we've reached the end of the clipping if (endStringSpecified) { if (lineNumber == contentNumLines) { throw new OptionsException(nameof(Clipping.EndString), string.Format(Strings.OptionsException_FlexiIncludeBlockFactory_NoLineContainsEndString, endString)); } // Check if next line contains the end line substring if (content[lineNumber].Contains(endString)) { break; } } else if (lineNumber == normalizedStartAndEndLines.normalizedEndLine) { break; } } string after = clipping.After; if (isCode && after != null) { childProcessor.ProcessLine(new StringSlice(after)); // No issue even if Before is multiline since we're in a code block } } if (isCode) // If content is code, end with ``` { childProcessor.ProcessLine(_codeBlockFence); } // Ensure that the last replacement block has been closed. While the block never makes it to the OpenedBlocks collection in the root processor, // calling Close for it ensures that it and its children's Close methods and events get called. childProcessor.Close(parentOfNewBlocks.LastChild); // BlockProcessors are pooled. Once we're done with innerProcessor, we must release it. This also removes all references to // tempContainerBlock, which should allow it to be collected quickly. childProcessor.ReleaseChild(); }