1*8719732dSAndreas Gohr<?php 2*8719732dSAndreas Gohr 3*8719732dSAndreas Gohrnamespace dokuwiki\test\Parsing\ParserMode; 4*8719732dSAndreas Gohr 5*8719732dSAndreas Gohruse dokuwiki\Parsing\ModeRegistry; 6*8719732dSAndreas Gohruse dokuwiki\Parsing\ParserMode\Eol; 7*8719732dSAndreas Gohruse dokuwiki\Parsing\ParserMode\GfmHeader; 8*8719732dSAndreas Gohr 9*8719732dSAndreas Gohr/** 10*8719732dSAndreas Gohr * Tests for GFM ATX headings (`# text` through `###### text`). 11*8719732dSAndreas Gohr */ 12*8719732dSAndreas Gohrclass GfmHeaderTest extends ParserTestBase 13*8719732dSAndreas Gohr{ 14*8719732dSAndreas Gohr public function setUp(): void 15*8719732dSAndreas Gohr { 16*8719732dSAndreas Gohr parent::setUp(); 17*8719732dSAndreas Gohr global $conf; 18*8719732dSAndreas Gohr $conf['syntax'] = 'markdown'; 19*8719732dSAndreas Gohr ModeRegistry::reset(); 20*8719732dSAndreas Gohr } 21*8719732dSAndreas Gohr 22*8719732dSAndreas Gohr public function tearDown(): void 23*8719732dSAndreas Gohr { 24*8719732dSAndreas Gohr ModeRegistry::reset(); 25*8719732dSAndreas Gohr parent::tearDown(); 26*8719732dSAndreas Gohr } 27*8719732dSAndreas Gohr 28*8719732dSAndreas Gohr function testLevelOne() 29*8719732dSAndreas Gohr { 30*8719732dSAndreas Gohr $this->P->addMode('gfm_header', new GfmHeader()); 31*8719732dSAndreas Gohr $this->P->parse("abc\n# Header\ndef"); 32*8719732dSAndreas Gohr $calls = [ 33*8719732dSAndreas Gohr ['document_start', []], 34*8719732dSAndreas Gohr ['p_open', []], 35*8719732dSAndreas Gohr ['cdata', ["\nabc"]], 36*8719732dSAndreas Gohr ['p_close', []], 37*8719732dSAndreas Gohr ['header', ['Header', 1, 4]], 38*8719732dSAndreas Gohr ['section_open', [1]], 39*8719732dSAndreas Gohr ['p_open', []], 40*8719732dSAndreas Gohr ['cdata', ["\ndef"]], 41*8719732dSAndreas Gohr ['p_close', []], 42*8719732dSAndreas Gohr ['section_close', []], 43*8719732dSAndreas Gohr ['document_end', []], 44*8719732dSAndreas Gohr ]; 45*8719732dSAndreas Gohr $this->assertCalls($calls, $this->H->calls); 46*8719732dSAndreas Gohr } 47*8719732dSAndreas Gohr 48*8719732dSAndreas Gohr function testAllLevels() 49*8719732dSAndreas Gohr { 50*8719732dSAndreas Gohr foreach ([1, 2, 3, 4, 5, 6] as $level) { 51*8719732dSAndreas Gohr $this->setUp(); 52*8719732dSAndreas Gohr $this->P->addMode('gfm_header', new GfmHeader()); 53*8719732dSAndreas Gohr $marker = str_repeat('#', $level); 54*8719732dSAndreas Gohr $this->P->parse("$marker foo"); 55*8719732dSAndreas Gohr $calls = array_column($this->H->calls, 0); 56*8719732dSAndreas Gohr $this->assertContains('header', $calls, "level $level must emit header"); 57*8719732dSAndreas Gohr 58*8719732dSAndreas Gohr $headerCall = array_values(array_filter( 59*8719732dSAndreas Gohr $this->H->calls, 60*8719732dSAndreas Gohr static fn($c) => $c[0] === 'header' 61*8719732dSAndreas Gohr ))[0]; 62*8719732dSAndreas Gohr $this->assertSame('foo', $headerCall[1][0], "level $level title"); 63*8719732dSAndreas Gohr $this->assertSame($level, $headerCall[1][1], "level $level level"); 64*8719732dSAndreas Gohr } 65*8719732dSAndreas Gohr } 66*8719732dSAndreas Gohr 67*8719732dSAndreas Gohr function testSevenHashesIsNotAHeading() 68*8719732dSAndreas Gohr { 69*8719732dSAndreas Gohr $this->P->addMode('gfm_header', new GfmHeader()); 70*8719732dSAndreas Gohr $this->P->parse('####### foo'); 71*8719732dSAndreas Gohr $modes = array_column($this->H->calls, 0); 72*8719732dSAndreas Gohr $this->assertNotContains('header', $modes, 73*8719732dSAndreas Gohr 'A run of 7 `#` must not open an ATX heading'); 74*8719732dSAndreas Gohr } 75*8719732dSAndreas Gohr 76*8719732dSAndreas Gohr function testHashTouchingTextIsNotAHeading() 77*8719732dSAndreas Gohr { 78*8719732dSAndreas Gohr $this->P->addMode('gfm_header', new GfmHeader()); 79*8719732dSAndreas Gohr $this->P->parse("#5 bolt\n\n#hashtag"); 80*8719732dSAndreas Gohr $modes = array_column($this->H->calls, 0); 81*8719732dSAndreas Gohr $this->assertNotContains('header', $modes, 82*8719732dSAndreas Gohr 'A `#` directly followed by a non-space char must not open a heading'); 83*8719732dSAndreas Gohr } 84*8719732dSAndreas Gohr 85*8719732dSAndreas Gohr function testEmptyHeading() 86*8719732dSAndreas Gohr { 87*8719732dSAndreas Gohr $this->P->addMode('gfm_header', new GfmHeader()); 88*8719732dSAndreas Gohr $this->P->parse("#\n"); 89*8719732dSAndreas Gohr $headerCalls = array_filter($this->H->calls, static fn($c) => $c[0] === 'header'); 90*8719732dSAndreas Gohr $this->assertCount(1, $headerCalls, 'bare `#` must still emit a heading'); 91*8719732dSAndreas Gohr $call = array_values($headerCalls)[0]; 92*8719732dSAndreas Gohr $this->assertSame('', $call[1][0]); 93*8719732dSAndreas Gohr $this->assertSame(1, $call[1][1]); 94*8719732dSAndreas Gohr } 95*8719732dSAndreas Gohr 96*8719732dSAndreas Gohr function testEmptyHeadingWithTrailingSpace() 97*8719732dSAndreas Gohr { 98*8719732dSAndreas Gohr $this->P->addMode('gfm_header', new GfmHeader()); 99*8719732dSAndreas Gohr $this->P->parse("## \n"); 100*8719732dSAndreas Gohr $headerCalls = array_filter($this->H->calls, static fn($c) => $c[0] === 'header'); 101*8719732dSAndreas Gohr $call = array_values($headerCalls)[0]; 102*8719732dSAndreas Gohr $this->assertSame('', $call[1][0]); 103*8719732dSAndreas Gohr $this->assertSame(2, $call[1][1]); 104*8719732dSAndreas Gohr } 105*8719732dSAndreas Gohr 106*8719732dSAndreas Gohr function testEmptyHeadingWithClosingHashes() 107*8719732dSAndreas Gohr { 108*8719732dSAndreas Gohr $this->P->addMode('gfm_header', new GfmHeader()); 109*8719732dSAndreas Gohr $this->P->parse("### ###\n"); 110*8719732dSAndreas Gohr $headerCalls = array_filter($this->H->calls, static fn($c) => $c[0] === 'header'); 111*8719732dSAndreas Gohr $call = array_values($headerCalls)[0]; 112*8719732dSAndreas Gohr $this->assertSame('', $call[1][0]); 113*8719732dSAndreas Gohr $this->assertSame(3, $call[1][1]); 114*8719732dSAndreas Gohr } 115*8719732dSAndreas Gohr 116*8719732dSAndreas Gohr function testOptionalClosingHashesStripped() 117*8719732dSAndreas Gohr { 118*8719732dSAndreas Gohr $this->P->addMode('gfm_header', new GfmHeader()); 119*8719732dSAndreas Gohr $this->P->parse("## foo ##\n"); 120*8719732dSAndreas Gohr $headerCalls = array_filter($this->H->calls, static fn($c) => $c[0] === 'header'); 121*8719732dSAndreas Gohr $call = array_values($headerCalls)[0]; 122*8719732dSAndreas Gohr $this->assertSame('foo', $call[1][0]); 123*8719732dSAndreas Gohr $this->assertSame(2, $call[1][1]); 124*8719732dSAndreas Gohr } 125*8719732dSAndreas Gohr 126*8719732dSAndreas Gohr function testClosingNeedNotMatchOpeningLength() 127*8719732dSAndreas Gohr { 128*8719732dSAndreas Gohr $this->P->addMode('gfm_header', new GfmHeader()); 129*8719732dSAndreas Gohr $this->P->parse("# foo ##################################\n"); 130*8719732dSAndreas Gohr $headerCalls = array_filter($this->H->calls, static fn($c) => $c[0] === 'header'); 131*8719732dSAndreas Gohr $call = array_values($headerCalls)[0]; 132*8719732dSAndreas Gohr $this->assertSame('foo', $call[1][0]); 133*8719732dSAndreas Gohr $this->assertSame(1, $call[1][1]); 134*8719732dSAndreas Gohr } 135*8719732dSAndreas Gohr 136*8719732dSAndreas Gohr function testTrailingSpacesAfterClosing() 137*8719732dSAndreas Gohr { 138*8719732dSAndreas Gohr $this->P->addMode('gfm_header', new GfmHeader()); 139*8719732dSAndreas Gohr $this->P->parse("### foo ### \n"); 140*8719732dSAndreas Gohr $headerCalls = array_filter($this->H->calls, static fn($c) => $c[0] === 'header'); 141*8719732dSAndreas Gohr $call = array_values($headerCalls)[0]; 142*8719732dSAndreas Gohr $this->assertSame('foo', $call[1][0]); 143*8719732dSAndreas Gohr $this->assertSame(3, $call[1][1]); 144*8719732dSAndreas Gohr } 145*8719732dSAndreas Gohr 146*8719732dSAndreas Gohr function testClosingRunFollowedByTextIsNotClosing() 147*8719732dSAndreas Gohr { 148*8719732dSAndreas Gohr $this->P->addMode('gfm_header', new GfmHeader()); 149*8719732dSAndreas Gohr $this->P->parse("### foo ### b\n"); 150*8719732dSAndreas Gohr $headerCalls = array_filter($this->H->calls, static fn($c) => $c[0] === 'header'); 151*8719732dSAndreas Gohr $call = array_values($headerCalls)[0]; 152*8719732dSAndreas Gohr $this->assertSame('foo ### b', $call[1][0]); 153*8719732dSAndreas Gohr $this->assertSame(3, $call[1][1]); 154*8719732dSAndreas Gohr } 155*8719732dSAndreas Gohr 156*8719732dSAndreas Gohr function testClosingHashMustBePrecededBySpace() 157*8719732dSAndreas Gohr { 158*8719732dSAndreas Gohr $this->P->addMode('gfm_header', new GfmHeader()); 159*8719732dSAndreas Gohr $this->P->parse("# foo#\n"); 160*8719732dSAndreas Gohr $headerCalls = array_filter($this->H->calls, static fn($c) => $c[0] === 'header'); 161*8719732dSAndreas Gohr $call = array_values($headerCalls)[0]; 162*8719732dSAndreas Gohr $this->assertSame('foo#', $call[1][0]); 163*8719732dSAndreas Gohr $this->assertSame(1, $call[1][1]); 164*8719732dSAndreas Gohr } 165*8719732dSAndreas Gohr 166*8719732dSAndreas Gohr function testIndentedHashIsNotAHeading() 167*8719732dSAndreas Gohr { 168*8719732dSAndreas Gohr // GFM tolerates 0-3 spaces of indent; we do not. Any leading 169*8719732dSAndreas Gohr // whitespace makes the line a paragraph (or preformatted, if 170*8719732dSAndreas Gohr // it meets that mode's rules). 171*8719732dSAndreas Gohr foreach ([1, 2, 3] as $indent) { 172*8719732dSAndreas Gohr $this->setUp(); 173*8719732dSAndreas Gohr $this->P->addMode('gfm_header', new GfmHeader()); 174*8719732dSAndreas Gohr $this->P->parse(str_repeat(' ', $indent) . '### foo'); 175*8719732dSAndreas Gohr $modes = array_column($this->H->calls, 0); 176*8719732dSAndreas Gohr $this->assertNotContains('header', $modes, 177*8719732dSAndreas Gohr "indent=$indent must NOT open a heading"); 178*8719732dSAndreas Gohr } 179*8719732dSAndreas Gohr } 180*8719732dSAndreas Gohr 181*8719732dSAndreas Gohr function testContentInlineWhitespaceCollapsed() 182*8719732dSAndreas Gohr { 183*8719732dSAndreas Gohr $this->P->addMode('gfm_header', new GfmHeader()); 184*8719732dSAndreas Gohr $this->P->parse("# foo \n"); 185*8719732dSAndreas Gohr $headerCalls = array_filter($this->H->calls, static fn($c) => $c[0] === 'header'); 186*8719732dSAndreas Gohr $call = array_values($headerCalls)[0]; 187*8719732dSAndreas Gohr $this->assertSame('foo', $call[1][0]); 188*8719732dSAndreas Gohr } 189*8719732dSAndreas Gohr 190*8719732dSAndreas Gohr function testHeadingCanInterruptParagraph() 191*8719732dSAndreas Gohr { 192*8719732dSAndreas Gohr $this->P->addMode('gfm_header', new GfmHeader()); 193*8719732dSAndreas Gohr $this->P->addMode('eol', new Eol()); 194*8719732dSAndreas Gohr $this->P->parse("Foo bar\n# baz\nBar foo"); 195*8719732dSAndreas Gohr $modes = array_column($this->H->calls, 0); 196*8719732dSAndreas Gohr $this->assertContains('header', $modes, 197*8719732dSAndreas Gohr 'ATX headings must interrupt paragraphs without requiring a blank line'); 198*8719732dSAndreas Gohr } 199*8719732dSAndreas Gohr 200*8719732dSAndreas Gohr function testSortValue() 201*8719732dSAndreas Gohr { 202*8719732dSAndreas Gohr $mode = new GfmHeader(); 203*8719732dSAndreas Gohr $this->assertSame(50, $mode->getSort()); 204*8719732dSAndreas Gohr } 205*8719732dSAndreas Gohr} 206