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