using System.Text.RegularExpressions; using Markdig.Extensions.AutoLinks; using Markdig.Extensions.Tables; using Markdig.Syntax; using NUnit.Framework; namespace Markdig.Tests; public class MiscTests { [Test] public void LinkWithInvalidNonAsciiDomainNameIsIgnored() { // Url from https://github.com/lunet-io/markdig/issues/438 _ = Markdown.ToHtml("[minulém díle](http://V%20minulém%20díle%20jsme%20nainstalovali%20SQL%20Server,%20který%20je%20nutný%20pro%20běh%20Configuration%20Manageru.%20Dnes%20nás%20čeká%20instalace%20WSUS,%20což%20je%20produkt,%20jež%20je%20možné%20používat%20i%20jako%20samostatnou%20funkci%20ve%20Windows%20Serveru,%20který%20se%20stará%20o%20stažení%20a%20instalaci%20aktualizací%20z%20Microsoft%20Update%20na%20klientské%20počítače.%20Stejně%20jako%20v%20předchozích%20dílech,%20tak%20i%20v%20tomto%20si%20ukážeme%20obě%20varianty%20instalace%20–%20a%20to%20jak%20instalaci%20z%20PowerShellu,%20tak%20instalaci%20pomocí%20GUI.) "); // Valid IDN TestParser.TestSpec("[foo](http://ünicode.com)", "

foo

"); TestParser.TestSpec("[foo](http://ünicode.ünicode.com)", "

foo

"); // Invalid IDN TestParser.TestSpec("[foo](http://ünicode..com)", "

foo

"); } [TestCase("link [foo [bar]]")] // https://spec.commonmark.org/0.29/#example-508 [TestCase("link [foo][bar]")] [TestCase("link [][foo][bar][]")] [TestCase("link [][foo][bar][[]]")] [TestCase("link [foo] [bar]")] [TestCase("link [[foo] [] [bar] [[abc]def]]")] [TestCase("[]")] [TestCase("[ ]")] [TestCase("[bar][]")] [TestCase("[bar][ foo]")] [TestCase("[bar][foo ][]")] [TestCase("[bar][fo[ ]o ][][]")] [TestCase("[a]b[c[d[e]f]g]h")] [TestCase("a[b[c[d]e]f[g]h]i foo [j]k[l[m]n]o")] [TestCase("a[b[c[d]e]f[g]h]i[] [][foo][bar][] foo [j]k[l[m]n]o")] [TestCase("a[b[c[d]e]f[g]h]i foo [j]k[l[m]n]o[][]")] public void LinkTextMayContainBalancedBrackets(string linkText) { string markdown = $"[{linkText}](/uri)"; string expected = $@"

{linkText}

"; TestParser.TestSpec(markdown, expected); // Make the link text unbalanced foreach (var bracketIndex in linkText .Select((c, i) => new Tuple(c, i)) .Where(t => t.Item1 == '[' || t.Item1 == ']') .Select(t => t.Item2)) { string brokenLinkText = linkText.Remove(bracketIndex, 1); markdown = $"[{brokenLinkText}](/uri)"; expected = $@"

{brokenLinkText}

"; string actual = Markdown.ToHtml(markdown); Assert.AreNotEqual(expected, actual); } } [Theory] [TestCase('[', 9 * 1024, true, false)] [TestCase('[', 11 * 1024, true, true)] [TestCase('[', 100, false, false)] [TestCase('[', 150, false, true)] [TestCase('>', 100, true, false)] [TestCase('>', 150, true, true)] public void GuardsAgainstHighlyNestedNodes(char c, int count, bool parseOnly, bool shouldThrow) { var markdown = new string(c, count); Action test = parseOnly ? () => Markdown.Parse(markdown) : () => Markdown.ToHtml(markdown); if (shouldThrow) { Exception e = Assert.Throws(test); Assert.True(e.Message.Contains("depth limit")); } else { test(); } } [Test] public void MaximumNestingDepthCanBeRaisedForDeepListExtras() { var markdown = "Krankenhaus\nD. " + string.Join(" ", Enumerable.Repeat("M.", 160)); var pipeline = new MarkdownPipelineBuilder { MaximumNestingDepth = 512, }.UseListExtras().Build(); Assert.DoesNotThrow(() => Markdown.ToHtml(markdown, pipeline)); } [Test] public void MaximumNestingDepthCanBeLowered() { var pipeline = new MarkdownPipelineBuilder { MaximumNestingDepth = 16, }.Build(); Exception e = Assert.Throws(() => Markdown.ToHtml(new string('>', 20), pipeline)); Assert.True(e.Message.Contains("depth limit")); } [Test] public void IsIssue356Corrected() { string input = @"https://foo.bar/path/\#m4mv5W0GYKZpGvfA.97"; string expected = @"

https://foo.bar/path/\#m4mv5W0GYKZpGvfA.97

"; TestParser.TestSpec($"<{input}>", expected); TestParser.TestSpec(input, expected, "autolinks|advanced"); } [Test] public void IsIssue365Corrected() { // The scheme must be escaped too... string input = "![image](\"onclick=\"alert&#40;'click'&#41;\"://)"; string expected = "

\"image\"

"; TestParser.TestSpec(input, expected); } [Test] public void TestAltTextIsCorrectlyEscaped() { TestParser.TestSpec( @"![This is image alt text with quotation ' and double quotation ""hello"" world](girl.png)", @"

"); } [Test] public void TestFixHang() { var input = File.ReadAllText(Path.Combine(TestParser.TestsDirectory, "hang.md")); _ = Markdown.ToHtml(input); } [Test] public void TestInvalidHtmlEntity() { var input = "9&ddr;&*&ddr;&de��__"; TestParser.TestSpec(input, "

9&ddr;&*&ddr;&de��__

"); } [Test] public void TestInvalidCharacterHandling() { var input = File.ReadAllText(Path.Combine(TestParser.TestsDirectory, "ArgumentOutOfRangeException.md")); _ = Markdown.ToHtml(input); } [Test] public void TestInvalidCodeEscape() { var input = "```**Header** "; _ = Markdown.ToHtml(input); } [Test] public void TestEmphasisAndHtmlEntity() { var markdownText = "*Unlimited-Fun®*®"; TestParser.TestSpec(markdownText, "

Unlimited-Fun®®

"); } [Test] public void TestThematicInsideCodeBlockInsideList() { var input = @"1. In the : ``` Id DisplayName Description -- ----------- ----------- 62375ab9-6b52-47ed-826b-58e47e0e304b Group.Unified ... ```"; TestParser.TestSpec(input, @"
  1. In the :

    Id                                   DisplayName         Description
    --                                   -----------         -----------
    62375ab9-6b52-47ed-826b-58e47e0e304b Group.Unified       ...
    
"); } [Test] public void VisualizeMathExpressions() { string math = @"Math expressions $\frac{n!}{k!(n-k)!} = \binom{n}{k}$ $$\frac{n!}{k!(n-k)!} = \binom{n}{k}$$ $$ \frac{n!}{k!(n-k)!} = \binom{n}{k} $$
\begin{align} \sqrt{37} & = \sqrt{\frac{73^2-1}{12^2}} \\ & = \sqrt{\frac{73^2}{12^2}\cdot\frac{73^2-1}{73^2}} \\ & = \sqrt{\frac{73^2}{12^2}}\sqrt{\frac{73^2-1}{73^2}} \\ & = \frac{73}{12}\sqrt{1 - \frac{1}{73^2}} \\ & \approx \frac{73}{12}\left(1 - \frac{1}{2\cdot73^2}\right) \end{align}
"; //Console.WriteLine("Math Expressions:\n"); var pl = new MarkdownPipelineBuilder().UseMathematics().Build(); // UseEmphasisExtras(EmphasisExtraOptions.Subscript).Build() var html = Markdown.ToHtml(math, pl); //Console.WriteLine(html); } [Test] public void InlineMathExpression() { string math = @"Math expressions $\frac{n!}{k!(n-k)!} = \binom{n}{k}$ "; var pl = new MarkdownPipelineBuilder().UseMathematics().Build(); // UseEmphasisExtras(EmphasisExtraOptions.Subscript).Build() var html = Markdown.ToHtml(math, pl); var test1 = html.Contains("

\\("); var test2 = html.Contains("\\)

"); if (!test1 || !test2) { Console.WriteLine(html); } Assert.IsTrue(test1, "Leading bracket missing"); Assert.IsTrue(test2, "Trailing bracket missing"); } [Test] public void BlockMathExpression() { string math = @"Math expressions $$ \frac{n!}{k!(n-k)!} = \binom{n}{k} $$ "; var pl = new MarkdownPipelineBuilder().UseMathematics().Build(); // UseEmphasisExtras(EmphasisExtraOptions.Subscript).Build() var html = Markdown.ToHtml(math, pl); var test1 = html.Contains("
\n\\["); var test2 = html.Contains("\\]
"); if (!test1 || !test2) { Console.WriteLine(html); } Assert.IsTrue(test1, "Leading bracket missing"); Assert.IsTrue(test2, "Trailing bracket missing"); } [Test] public void CanDisableParsingHeadings() { var noHeadingsPipeline = new MarkdownPipelineBuilder().DisableHeadings().Build(); TestParser.TestSpec("Foo\n===", "

Foo

"); TestParser.TestSpec("Foo\n===", "

Foo\n===

", noHeadingsPipeline); TestParser.TestSpec("# Heading 1", "

Heading 1

"); TestParser.TestSpec("# Heading 1", "

# Heading 1

", noHeadingsPipeline); // Does not also disable link reference definitions TestParser.TestSpec("[Foo]\n\n[Foo]: bar", "

Foo

"); TestParser.TestSpec("[Foo]\n\n[Foo]: bar", "

Foo

", noHeadingsPipeline); } [Test] public void CanOpenAutoLinksInNewWindow() { var pipeline = new MarkdownPipelineBuilder().UseAutoLinks().Build(); var newWindowPipeline = new MarkdownPipelineBuilder().UseAutoLinks(new AutoLinkOptions() { OpenInNewWindow = true }).Build(); TestParser.TestSpec("www.foo.bar", "

www.foo.bar

", pipeline); TestParser.TestSpec("www.foo.bar", "

www.foo.bar

", newWindowPipeline); } [Test] public void CanUseHttpsPrefixForWWWAutoLinks() { var pipeline = new MarkdownPipelineBuilder().UseAutoLinks().Build(); var httpsPipeline = new MarkdownPipelineBuilder().UseAutoLinks(new AutoLinkOptions() { UseHttpsForWWWLinks = true }).Build(); TestParser.TestSpec("www.foo.bar", "

www.foo.bar

", pipeline); TestParser.TestSpec("www.foo.bar", "

www.foo.bar

", httpsPipeline); } [Test] public void RootInlineHasCorrectSourceSpan() { var pipeline = new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build(); pipeline.TrackTrivia = true; var document = Markdown.Parse("0123456789\n", pipeline); var expectedSourceSpan = new SourceSpan(0, 10); Assert.That(((LeafBlock)document.LastChild).Inline.Span == expectedSourceSpan); } [Test] public void RootInlineInTableCellHasCorrectSourceSpan() { var pipeline = new MarkdownPipelineBuilder().UsePreciseSourceLocation().UseAdvancedExtensions().Build(); pipeline.TrackTrivia = true; var document = Markdown.Parse("| a | b |\n| --- | --- |\n| *dest*
| \\[in\\] The address of the result of the operation.
|", pipeline); var paragraph = (ParagraphBlock)((TableCell)((TableRow)((Table)document.LastChild).LastChild).First()).LastChild; Assert.That(paragraph.Inline.Span.Start == paragraph.Inline.FirstChild.Span.Start); Assert.That(paragraph.Inline.Span.End == paragraph.Inline.LastChild.Span.End); } [Test] public void TestGridTableShortLine() { var input = @" +--+ | | +-"; var expected = @"
"; TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseGridTables().Build()); } [Test] public void TestGridTableLastCellWithoutClosingPipe() { // The last cell is open and shorter than the column, and the next line has // a `|` where this one would end: https://github.com/xoofx/markdig/issues/494 var input = @" +-----+----------+ | 6 | 1 | | +-----+----------+"; var expected = @"
6 1
"; TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseGridTables().Build()); } [Test] public void TestDefinitionListInListItemWithBlankLine() { var input = @" - term : definition "; var expected = @"
  • term
    definition
"; TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseDefinitionLists().Build()); } [Test] public void TestAlertWithinAlertOrNestedBlock() { var input = @" >[!NOTE] [!NOTE] The second one is not a note. >>[!NOTE] Also not a note. "; var expected = @"

Note

[!NOTE] The second one is not a note.

[!NOTE] Also not a note.

"; TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks().Build()); } [Test] public void TestNestedAlertInsideBlockquote() { // >>[!NOTE] should become a blockquote wrapping an alert when AllowNestedAlerts is enabled var input = @">>[!NOTE] Also a note. "; var expected = @"

Note

Also a note.

"; TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks(allowNestedAlerts: true).Build()); } [Test] public void TestNestedAlertInsideAlert() { // An alert inside another alert is never recognized, even with AllowNestedAlerts var input = @">[!NOTE] > >[!TIP] > > A tip inside a note "; var expected = @"

Note

[!TIP] A tip inside a note

"; TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks(allowNestedAlerts: true).Build()); } [Test] public void TestAlertInsideListItem() { // Alerts inside list items require AllowNestedAlerts var input = @"- > [!NOTE] > A note inside a list item "; var expected = @"
  • Note

    A note inside a list item

"; TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks(allowNestedAlerts: true).Build()); } [Test] public void TestAlertInsideNestedListItem() { // Alert inside a nested list item (list item indented under another list item) requires AllowNestedAlerts var input = @"- list item 1 - list item 2 - > [!NOTE] > A note inside a nested list item "; var expected = @"
  • list item 1
  • list item 2
    • Note

      A note inside a nested list item

"; TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks(allowNestedAlerts: true).Build()); } [Test] public void TestIssue887ListAfterNestedBlockQuote() { var input = @"> >* _Unordered 1_ QL2 level 1 no space sep > > Continued 1 > > * Unordered 2 level 1 > > 1. **Ordered 1** QL2 level 1 > > Continued 1 > > > Not continued QL3 > > 2. Ordered 2 QL2 level 1 > > Continued 2a > > > > Continued 2b > > > > Continued 2c > > > > 3. Ordered 3 QL2 level 2 > > Continued 3a > > > > Continued 3b "; var expected = @"
  • Unordered 1 QL2 level 1 no space sep Continued 1
  • Unordered 2 level 1
  1. Ordered 1 QL2 level 1 Continued 1

Not continued QL3

  1. Ordered 2 QL2 level 1 Continued 2a

    Continued 2b

    Continued 2c

    1. Ordered 3 QL2 level 2 Continued 3a

      Continued 3b

"; TestParser.TestSpec(input, expected); } [Test] public void TestIssue845ListItemBlankLine() { TestParser.TestSpec("-\n\n foo",@"

foo

"); TestParser.TestSpec("-\n-\n\n foo",@"

foo

"); TestParser.TestSpec("-\n\n-\n\n foo",@"

foo

"); } }