xref: /dokuwiki/_test/tests/Parsing/ParserMode/GfmBacktickSingleTest.php (revision 47a02a102092be9e1e6f1ddaf158bdfffdb13d4f)
18ed75a23SAndreas Gohr<?php
28ed75a23SAndreas Gohr
38ed75a23SAndreas Gohrnamespace dokuwiki\test\Parsing\ParserMode;
48ed75a23SAndreas Gohr
58ed75a23SAndreas Gohruse dokuwiki\Parsing\ParserMode\GfmBacktickSingle;
68ed75a23SAndreas Gohruse dokuwiki\Parsing\ParserMode\GfmEmphasis;
78ed75a23SAndreas Gohr
88ed75a23SAndreas Gohr/**
98ed75a23SAndreas Gohr * Tests for the GFM inline code-span mode — single-backtick spans.
108ed75a23SAndreas Gohr */
118ed75a23SAndreas Gohrclass GfmBacktickSingleTest extends ParserTestBase
128ed75a23SAndreas Gohr{
138ed75a23SAndreas Gohr    public function setUp(): void
148ed75a23SAndreas Gohr    {
158ed75a23SAndreas Gohr        parent::setUp();
16*47a02a10SAndreas Gohr        $this->setSyntax('md');
178ed75a23SAndreas Gohr    }
188ed75a23SAndreas Gohr
198ed75a23SAndreas Gohr    function testBasicCodeSpan()
208ed75a23SAndreas Gohr    {
218ed75a23SAndreas Gohr        $this->P->addMode('gfm_backtick_single', new GfmBacktickSingle());
228ed75a23SAndreas Gohr        $this->P->parse('Foo `Bar` Baz');
238ed75a23SAndreas Gohr        $calls = [
248ed75a23SAndreas Gohr            ['document_start', []],
258ed75a23SAndreas Gohr            ['p_open', []],
268ed75a23SAndreas Gohr            ['cdata', ["\nFoo "]],
278ed75a23SAndreas Gohr            ['monospace_open', []],
288ed75a23SAndreas Gohr            ['unformatted', ['Bar']],
298ed75a23SAndreas Gohr            ['monospace_close', []],
308ed75a23SAndreas Gohr            ['cdata', [' Baz']],
318ed75a23SAndreas Gohr            ['p_close', []],
328ed75a23SAndreas Gohr            ['document_end', []],
338ed75a23SAndreas Gohr        ];
348ed75a23SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
358ed75a23SAndreas Gohr    }
368ed75a23SAndreas Gohr
378ed75a23SAndreas Gohr    function testSingleCharacterBody()
388ed75a23SAndreas Gohr    {
398ed75a23SAndreas Gohr        $this->P->addMode('gfm_backtick_single', new GfmBacktickSingle());
408ed75a23SAndreas Gohr        $this->P->parse('foo `b` bar');
418ed75a23SAndreas Gohr        $calls = [
428ed75a23SAndreas Gohr            ['document_start', []],
438ed75a23SAndreas Gohr            ['p_open', []],
448ed75a23SAndreas Gohr            ['cdata', ["\nfoo "]],
458ed75a23SAndreas Gohr            ['monospace_open', []],
468ed75a23SAndreas Gohr            ['unformatted', ['b']],
478ed75a23SAndreas Gohr            ['monospace_close', []],
488ed75a23SAndreas Gohr            ['cdata', [' bar']],
498ed75a23SAndreas Gohr            ['p_close', []],
508ed75a23SAndreas Gohr            ['document_end', []],
518ed75a23SAndreas Gohr        ];
528ed75a23SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
538ed75a23SAndreas Gohr    }
548ed75a23SAndreas Gohr
558ed75a23SAndreas Gohr    function testTwoSeparateSpansOnOneLine()
568ed75a23SAndreas Gohr    {
578ed75a23SAndreas Gohr        $this->P->addMode('gfm_backtick_single', new GfmBacktickSingle());
588ed75a23SAndreas Gohr        $this->P->parse('`one` and `two`');
598ed75a23SAndreas Gohr        $calls = [
608ed75a23SAndreas Gohr            ['document_start', []],
618ed75a23SAndreas Gohr            ['p_open', []],
628ed75a23SAndreas Gohr            ['cdata', ["\n"]],
638ed75a23SAndreas Gohr            ['monospace_open', []],
648ed75a23SAndreas Gohr            ['unformatted', ['one']],
658ed75a23SAndreas Gohr            ['monospace_close', []],
668ed75a23SAndreas Gohr            ['cdata', [' and ']],
678ed75a23SAndreas Gohr            ['monospace_open', []],
688ed75a23SAndreas Gohr            ['unformatted', ['two']],
698ed75a23SAndreas Gohr            ['monospace_close', []],
708ed75a23SAndreas Gohr            ['cdata', ['']],
718ed75a23SAndreas Gohr            ['p_close', []],
728ed75a23SAndreas Gohr            ['document_end', []],
738ed75a23SAndreas Gohr        ];
748ed75a23SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
758ed75a23SAndreas Gohr    }
768ed75a23SAndreas Gohr
778ed75a23SAndreas Gohr    function testUnmatchedOpenerStaysLiteral()
788ed75a23SAndreas Gohr    {
798ed75a23SAndreas Gohr        $this->P->addMode('gfm_backtick_single', new GfmBacktickSingle());
808ed75a23SAndreas Gohr        $this->P->parse('foo `bar with no closer');
818ed75a23SAndreas Gohr        $modes = array_column($this->H->calls, 0);
828ed75a23SAndreas Gohr        $this->assertNotContains('monospace_open', $modes,
838ed75a23SAndreas Gohr            'Unmatched opening backtick must stay literal');
848ed75a23SAndreas Gohr    }
858ed75a23SAndreas Gohr
868ed75a23SAndreas Gohr    function testAsymmetricEdgeSpaceIsPreserved()
878ed75a23SAndreas Gohr    {
888ed75a23SAndreas Gohr        // GFM example 342. Input ` a` — a leading space but no trailing
898ed75a23SAndreas Gohr        // space. Body stays as " a"; strip only fires when BOTH ends are
908ed75a23SAndreas Gohr        // whitespace.
918ed75a23SAndreas Gohr        $this->P->addMode('gfm_backtick_single', new GfmBacktickSingle());
928ed75a23SAndreas Gohr        $this->P->parse('` a`');
938ed75a23SAndreas Gohr        $calls = [
948ed75a23SAndreas Gohr            ['document_start', []],
958ed75a23SAndreas Gohr            ['p_open', []],
968ed75a23SAndreas Gohr            ['cdata', ["\n"]],
978ed75a23SAndreas Gohr            ['monospace_open', []],
988ed75a23SAndreas Gohr            ['unformatted', [' a']],
998ed75a23SAndreas Gohr            ['monospace_close', []],
1008ed75a23SAndreas Gohr            ['cdata', ['']],
1018ed75a23SAndreas Gohr            ['p_close', []],
1028ed75a23SAndreas Gohr            ['document_end', []],
1038ed75a23SAndreas Gohr        ];
1048ed75a23SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
1058ed75a23SAndreas Gohr    }
1068ed75a23SAndreas Gohr
1078ed75a23SAndreas Gohr    function testSymmetricEdgeSpaceIsStripped()
1088ed75a23SAndreas Gohr    {
1098ed75a23SAndreas Gohr        // Body with whitespace on both sides and non-whitespace content
1108ed75a23SAndreas Gohr        // in the middle gets one space stripped from each end. Input
1118ed75a23SAndreas Gohr        // body is " foo "; after strip it becomes "foo".
1128ed75a23SAndreas Gohr        $this->P->addMode('gfm_backtick_single', new GfmBacktickSingle());
1138ed75a23SAndreas Gohr        $this->P->parse('` foo `');
1148ed75a23SAndreas Gohr        $calls = [
1158ed75a23SAndreas Gohr            ['document_start', []],
1168ed75a23SAndreas Gohr            ['p_open', []],
1178ed75a23SAndreas Gohr            ['cdata', ["\n"]],
1188ed75a23SAndreas Gohr            ['monospace_open', []],
1198ed75a23SAndreas Gohr            ['unformatted', ['foo']],
1208ed75a23SAndreas Gohr            ['monospace_close', []],
1218ed75a23SAndreas Gohr            ['cdata', ['']],
1228ed75a23SAndreas Gohr            ['p_close', []],
1238ed75a23SAndreas Gohr            ['document_end', []],
1248ed75a23SAndreas Gohr        ];
1258ed75a23SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
1268ed75a23SAndreas Gohr    }
1278ed75a23SAndreas Gohr
1288ed75a23SAndreas Gohr    function testAllWhitespaceBodyIsPreserved()
1298ed75a23SAndreas Gohr    {
1308ed75a23SAndreas Gohr        // A body of pure whitespace is a valid code span and kept as is
1318ed75a23SAndreas Gohr        // (strip is skipped because trim of the body is empty).
1328ed75a23SAndreas Gohr        $this->P->addMode('gfm_backtick_single', new GfmBacktickSingle());
1338ed75a23SAndreas Gohr        $this->P->parse('a ` ` b');
1348ed75a23SAndreas Gohr        $calls = [
1358ed75a23SAndreas Gohr            ['document_start', []],
1368ed75a23SAndreas Gohr            ['p_open', []],
1378ed75a23SAndreas Gohr            ['cdata', ["\na "]],
1388ed75a23SAndreas Gohr            ['monospace_open', []],
1398ed75a23SAndreas Gohr            ['unformatted', [' ']],
1408ed75a23SAndreas Gohr            ['monospace_close', []],
1418ed75a23SAndreas Gohr            ['cdata', [' b']],
1428ed75a23SAndreas Gohr            ['p_close', []],
1438ed75a23SAndreas Gohr            ['document_end', []],
1448ed75a23SAndreas Gohr        ];
1458ed75a23SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
1468ed75a23SAndreas Gohr    }
1478ed75a23SAndreas Gohr
1488ed75a23SAndreas Gohr    function testEmptyDelimiterDoesNotMatch()
1498ed75a23SAndreas Gohr    {
1508ed75a23SAndreas Gohr        // Two adjacent backticks with no matching pair later in the
1518ed75a23SAndreas Gohr        // paragraph stay literal — the length-boundary guards reject them
1528ed75a23SAndreas Gohr        // as an n=1 opener followed immediately by an n=1 closer.
1538ed75a23SAndreas Gohr        $this->P->addMode('gfm_backtick_single', new GfmBacktickSingle());
1548ed75a23SAndreas Gohr        $this->P->parse('foo `` bar');
1558ed75a23SAndreas Gohr        $modes = array_column($this->H->calls, 0);
1568ed75a23SAndreas Gohr        $this->assertNotContains('monospace_open', $modes,
1578ed75a23SAndreas Gohr            'Bare adjacent backticks with no closer must stay literal');
1588ed75a23SAndreas Gohr    }
1598ed75a23SAndreas Gohr
1608ed75a23SAndreas Gohr    function testN1BodyCanContainDoubleBacktickRun()
1618ed75a23SAndreas Gohr    {
1628ed75a23SAndreas Gohr        // GFM example 340. Input backtick-space-2xbacktick-space-backtick.
1638ed75a23SAndreas Gohr        // The interior run of two is not a valid n=1 closer, so it lives
1648ed75a23SAndreas Gohr        // in the body; edge-space stripping then leaves just the two
1658ed75a23SAndreas Gohr        // backticks as the body content.
1668ed75a23SAndreas Gohr        $this->P->addMode('gfm_backtick_single', new GfmBacktickSingle());
1678ed75a23SAndreas Gohr        $this->P->parse('` `` `');
1688ed75a23SAndreas Gohr        $calls = [
1698ed75a23SAndreas Gohr            ['document_start', []],
1708ed75a23SAndreas Gohr            ['p_open', []],
1718ed75a23SAndreas Gohr            ['cdata', ["\n"]],
1728ed75a23SAndreas Gohr            ['monospace_open', []],
1738ed75a23SAndreas Gohr            ['unformatted', ['``']],
1748ed75a23SAndreas Gohr            ['monospace_close', []],
1758ed75a23SAndreas Gohr            ['cdata', ['']],
1768ed75a23SAndreas Gohr            ['p_close', []],
1778ed75a23SAndreas Gohr            ['document_end', []],
1788ed75a23SAndreas Gohr        ];
1798ed75a23SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
1808ed75a23SAndreas Gohr    }
1818ed75a23SAndreas Gohr
1828ed75a23SAndreas Gohr    function testRunOfThreeBackticksIsNotAnN1Span()
1838ed75a23SAndreas Gohr    {
1848ed75a23SAndreas Gohr        // The length-boundary guards on the opener reject a backtick that
1858ed75a23SAndreas Gohr        // is immediately followed by another one, so a run of three or
1868ed75a23SAndreas Gohr        // more never opens an n=1 span. Triple-backtick fenced blocks
1878ed75a23SAndreas Gohr        // are a separate mode's concern.
1888ed75a23SAndreas Gohr        $this->P->addMode('gfm_backtick_single', new GfmBacktickSingle());
1898ed75a23SAndreas Gohr        $this->P->parse('foo ```bar``` baz');
1908ed75a23SAndreas Gohr        $modes = array_column($this->H->calls, 0);
1918ed75a23SAndreas Gohr        $this->assertNotContains('monospace_open', $modes,
1928ed75a23SAndreas Gohr            'A run of 3 backticks must not trigger an n=1 span');
1938ed75a23SAndreas Gohr    }
1948ed75a23SAndreas Gohr
1958ed75a23SAndreas Gohr    function testDoesNotSpanParagraphBoundary()
1968ed75a23SAndreas Gohr    {
1978ed75a23SAndreas Gohr        $this->P->addMode('gfm_backtick_single', new GfmBacktickSingle());
1988ed75a23SAndreas Gohr        $this->P->parse("This `has a\n\nnew paragraph`.");
1998ed75a23SAndreas Gohr        $modes = array_column($this->H->calls, 0);
2008ed75a23SAndreas Gohr        $this->assertNotContains('monospace_open', $modes,
2018ed75a23SAndreas Gohr            'GfmBacktickSingle must not open when the closer is past a blank line');
2028ed75a23SAndreas Gohr    }
2038ed75a23SAndreas Gohr
2048ed75a23SAndreas Gohr    function testAllowsSingleNewline()
2058ed75a23SAndreas Gohr    {
2068ed75a23SAndreas Gohr        $this->P->addMode('gfm_backtick_single', new GfmBacktickSingle());
2078ed75a23SAndreas Gohr        $this->P->parse("`open\nclose`");
2088ed75a23SAndreas Gohr        $modes = array_column($this->H->calls, 0);
2098ed75a23SAndreas Gohr        $this->assertContains('monospace_open', $modes,
2108ed75a23SAndreas Gohr            'GfmBacktickSingle must still match across a single newline');
2118ed75a23SAndreas Gohr    }
2128ed75a23SAndreas Gohr
2138ed75a23SAndreas Gohr    function testContentIsLiteral()
2148ed75a23SAndreas Gohr    {
2158ed75a23SAndreas Gohr        // Other inline modes must not parse inside a code span.
2168ed75a23SAndreas Gohr        $this->P->addMode('gfm_emphasis', new GfmEmphasis());
2178ed75a23SAndreas Gohr        $this->P->addMode('gfm_backtick_single', new GfmBacktickSingle());
2188ed75a23SAndreas Gohr        $this->P->parse('`*foo*`');
2198ed75a23SAndreas Gohr        $modes = array_column($this->H->calls, 0);
2208ed75a23SAndreas Gohr        $this->assertNotContains('emphasis_open', $modes,
2218ed75a23SAndreas Gohr            'Emphasis must not parse inside a code span');
2228ed75a23SAndreas Gohr        $this->assertContains('monospace_open', $modes,
2238ed75a23SAndreas Gohr            'Backtick span must emit monospace_open');
2248ed75a23SAndreas Gohr
2258ed75a23SAndreas Gohr        // The emphasized text stays as an unformatted (verbatim) call
2268ed75a23SAndreas Gohr        // inside the span — same treatment as nowiki and %%.
2278ed75a23SAndreas Gohr        $unformatted = array_filter($this->H->calls, static fn($c) => $c[0] === 'unformatted');
2288ed75a23SAndreas Gohr        $bodies = array_map(static fn($c) => $c[1][0], $unformatted);
2298ed75a23SAndreas Gohr        $this->assertContains('*foo*', $bodies,
2308ed75a23SAndreas Gohr            'Raw *foo* must appear as verbatim unformatted content');
2318ed75a23SAndreas Gohr    }
2328ed75a23SAndreas Gohr
2338ed75a23SAndreas Gohr    function testSortValue()
2348ed75a23SAndreas Gohr    {
2358ed75a23SAndreas Gohr        $mode = new GfmBacktickSingle();
2368ed75a23SAndreas Gohr        $this->assertSame(165, $mode->getSort());
2378ed75a23SAndreas Gohr    }
2388ed75a23SAndreas Gohr}
239