1*c4bcbc2eSAndreas Gohr<?php 2*c4bcbc2eSAndreas Gohr 3*c4bcbc2eSAndreas Gohrnamespace dokuwiki\test\Parsing\ParserMode; 4*c4bcbc2eSAndreas Gohr 5*c4bcbc2eSAndreas Gohruse dokuwiki\Parsing\ModeRegistry; 6*c4bcbc2eSAndreas Gohruse dokuwiki\Parsing\ParserMode\GfmEmphasis; 7*c4bcbc2eSAndreas Gohruse dokuwiki\Parsing\ParserMode\GfmLinebreak; 8*c4bcbc2eSAndreas Gohr 9*c4bcbc2eSAndreas Gohr/** 10*c4bcbc2eSAndreas Gohr * Tests for the GFM hard-line-break mode. 11*c4bcbc2eSAndreas Gohr */ 12*c4bcbc2eSAndreas Gohrclass GfmLinebreakTest extends ParserTestBase 13*c4bcbc2eSAndreas Gohr{ 14*c4bcbc2eSAndreas Gohr public function setUp(): void 15*c4bcbc2eSAndreas Gohr { 16*c4bcbc2eSAndreas Gohr parent::setUp(); 17*c4bcbc2eSAndreas Gohr global $conf; 18*c4bcbc2eSAndreas Gohr $conf['syntax'] = 'markdown'; 19*c4bcbc2eSAndreas Gohr ModeRegistry::reset(); 20*c4bcbc2eSAndreas Gohr } 21*c4bcbc2eSAndreas Gohr 22*c4bcbc2eSAndreas Gohr public function tearDown(): void 23*c4bcbc2eSAndreas Gohr { 24*c4bcbc2eSAndreas Gohr ModeRegistry::reset(); 25*c4bcbc2eSAndreas Gohr parent::tearDown(); 26*c4bcbc2eSAndreas Gohr } 27*c4bcbc2eSAndreas Gohr 28*c4bcbc2eSAndreas Gohr function testTwoTrailingSpacesProduceLinebreak() 29*c4bcbc2eSAndreas Gohr { 30*c4bcbc2eSAndreas Gohr $this->P->addMode('gfm_linebreak', new GfmLinebreak()); 31*c4bcbc2eSAndreas Gohr $this->P->parse("foo \nbar"); 32*c4bcbc2eSAndreas Gohr 33*c4bcbc2eSAndreas Gohr $names = array_column($this->H->calls, 0); 34*c4bcbc2eSAndreas Gohr $this->assertContains('linebreak', $names, 35*c4bcbc2eSAndreas Gohr 'Two trailing spaces before a newline must produce a linebreak call'); 36*c4bcbc2eSAndreas Gohr } 37*c4bcbc2eSAndreas Gohr 38*c4bcbc2eSAndreas Gohr function testManyTrailingSpacesProduceLinebreak() 39*c4bcbc2eSAndreas Gohr { 40*c4bcbc2eSAndreas Gohr $this->P->addMode('gfm_linebreak', new GfmLinebreak()); 41*c4bcbc2eSAndreas Gohr $this->P->parse("foo \nbar"); 42*c4bcbc2eSAndreas Gohr 43*c4bcbc2eSAndreas Gohr $names = array_column($this->H->calls, 0); 44*c4bcbc2eSAndreas Gohr $this->assertContains('linebreak', $names, 45*c4bcbc2eSAndreas Gohr '7+ trailing spaces before a newline must produce a linebreak call'); 46*c4bcbc2eSAndreas Gohr } 47*c4bcbc2eSAndreas Gohr 48*c4bcbc2eSAndreas Gohr function testBackslashNewlineProducesLinebreak() 49*c4bcbc2eSAndreas Gohr { 50*c4bcbc2eSAndreas Gohr $this->P->addMode('gfm_linebreak', new GfmLinebreak()); 51*c4bcbc2eSAndreas Gohr $this->P->parse("foo\\\nbar"); 52*c4bcbc2eSAndreas Gohr 53*c4bcbc2eSAndreas Gohr $names = array_column($this->H->calls, 0); 54*c4bcbc2eSAndreas Gohr $this->assertContains('linebreak', $names, 55*c4bcbc2eSAndreas Gohr 'A single backslash before a newline must produce a linebreak call'); 56*c4bcbc2eSAndreas Gohr } 57*c4bcbc2eSAndreas Gohr 58*c4bcbc2eSAndreas Gohr function testLeadingWhitespaceOnNextLineConsumed() 59*c4bcbc2eSAndreas Gohr { 60*c4bcbc2eSAndreas Gohr // Spec example 656 / 657: leading spaces at the beginning of the 61*c4bcbc2eSAndreas Gohr // next line are dropped — the rendered HTML must not carry them. 62*c4bcbc2eSAndreas Gohr $this->P->addMode('gfm_linebreak', new GfmLinebreak()); 63*c4bcbc2eSAndreas Gohr $this->P->parse("foo \n bar"); 64*c4bcbc2eSAndreas Gohr 65*c4bcbc2eSAndreas Gohr $cdata = array_filter($this->H->calls, static fn($c) => $c[0] === 'cdata'); 66*c4bcbc2eSAndreas Gohr $joined = implode('', array_map(static fn($c) => $c[1][0], $cdata)); 67*c4bcbc2eSAndreas Gohr 68*c4bcbc2eSAndreas Gohr $this->assertSame("\nfoobar", $joined, 69*c4bcbc2eSAndreas Gohr 'Leading whitespace on the line after a hard break must be consumed'); 70*c4bcbc2eSAndreas Gohr } 71*c4bcbc2eSAndreas Gohr 72*c4bcbc2eSAndreas Gohr function testNoLinebreakAtParagraphBreak() 73*c4bcbc2eSAndreas Gohr { 74*c4bcbc2eSAndreas Gohr // Spec example 665 (analogue): trailing spaces immediately before 75*c4bcbc2eSAndreas Gohr // a paragraph break are not a hard break — the lookahead rejects. 76*c4bcbc2eSAndreas Gohr $this->P->addMode('gfm_linebreak', new GfmLinebreak()); 77*c4bcbc2eSAndreas Gohr $this->P->parse("foo \n\nbar"); 78*c4bcbc2eSAndreas Gohr 79*c4bcbc2eSAndreas Gohr $names = array_column($this->H->calls, 0); 80*c4bcbc2eSAndreas Gohr $this->assertNotContains('linebreak', $names, 81*c4bcbc2eSAndreas Gohr 'Trailing spaces before a blank line must not produce a hard break'); 82*c4bcbc2eSAndreas Gohr } 83*c4bcbc2eSAndreas Gohr 84*c4bcbc2eSAndreas Gohr function testNoLinebreakAtEof() 85*c4bcbc2eSAndreas Gohr { 86*c4bcbc2eSAndreas Gohr // Spec example 665: trailing spaces at end of document are not a 87*c4bcbc2eSAndreas Gohr // hard break. The parser appends `\n`, so the lookahead's `\z` arm 88*c4bcbc2eSAndreas Gohr // catches this case. 89*c4bcbc2eSAndreas Gohr $this->P->addMode('gfm_linebreak', new GfmLinebreak()); 90*c4bcbc2eSAndreas Gohr $this->P->parse('foo '); 91*c4bcbc2eSAndreas Gohr 92*c4bcbc2eSAndreas Gohr $names = array_column($this->H->calls, 0); 93*c4bcbc2eSAndreas Gohr $this->assertNotContains('linebreak', $names, 94*c4bcbc2eSAndreas Gohr 'Trailing spaces at EOF must not produce a hard break'); 95*c4bcbc2eSAndreas Gohr } 96*c4bcbc2eSAndreas Gohr 97*c4bcbc2eSAndreas Gohr function testBackslashAtEofStaysLiteral() 98*c4bcbc2eSAndreas Gohr { 99*c4bcbc2eSAndreas Gohr // Spec example 664: a single trailing backslash at end of document 100*c4bcbc2eSAndreas Gohr // is not a hard break — same paragraph-end rule. 101*c4bcbc2eSAndreas Gohr $this->P->addMode('gfm_linebreak', new GfmLinebreak()); 102*c4bcbc2eSAndreas Gohr $this->P->parse('foo\\'); 103*c4bcbc2eSAndreas Gohr 104*c4bcbc2eSAndreas Gohr $names = array_column($this->H->calls, 0); 105*c4bcbc2eSAndreas Gohr $this->assertNotContains('linebreak', $names, 106*c4bcbc2eSAndreas Gohr 'A trailing backslash at EOF must stay literal, not produce a break'); 107*c4bcbc2eSAndreas Gohr 108*c4bcbc2eSAndreas Gohr $cdata = array_filter($this->H->calls, static fn($c) => $c[0] === 'cdata'); 109*c4bcbc2eSAndreas Gohr $joined = implode('', array_map(static fn($c) => $c[1][0], $cdata)); 110*c4bcbc2eSAndreas Gohr $this->assertStringContainsString('\\', $joined, 111*c4bcbc2eSAndreas Gohr 'The literal backslash must survive in cdata when no break fires'); 112*c4bcbc2eSAndreas Gohr } 113*c4bcbc2eSAndreas Gohr 114*c4bcbc2eSAndreas Gohr function testWorksInsideEmphasis() 115*c4bcbc2eSAndreas Gohr { 116*c4bcbc2eSAndreas Gohr // Spec example 658: hard breaks fire inside inline containers. 117*c4bcbc2eSAndreas Gohr // GfmLinebreak is SUBSTITION, GfmEmphasis allows SUBSTITION via 118*c4bcbc2eSAndreas Gohr // its allowedModes — so the break appears between the open and 119*c4bcbc2eSAndreas Gohr // close emphasis calls. 120*c4bcbc2eSAndreas Gohr $this->P->addMode('gfm_emphasis', new GfmEmphasis()); 121*c4bcbc2eSAndreas Gohr $this->P->addMode('gfm_linebreak', new GfmLinebreak()); 122*c4bcbc2eSAndreas Gohr $this->P->parse("*foo \nbar*"); 123*c4bcbc2eSAndreas Gohr 124*c4bcbc2eSAndreas Gohr $names = array_column($this->H->calls, 0); 125*c4bcbc2eSAndreas Gohr $emOpen = array_search('emphasis_open', $names, true); 126*c4bcbc2eSAndreas Gohr $break = array_search('linebreak', $names, true); 127*c4bcbc2eSAndreas Gohr $emClose = array_search('emphasis_close', $names, true); 128*c4bcbc2eSAndreas Gohr 129*c4bcbc2eSAndreas Gohr $this->assertNotFalse($emOpen, 'emphasis_open must fire'); 130*c4bcbc2eSAndreas Gohr $this->assertNotFalse($break, 'linebreak must fire inside emphasis'); 131*c4bcbc2eSAndreas Gohr $this->assertNotFalse($emClose, 'emphasis_close must fire'); 132*c4bcbc2eSAndreas Gohr $this->assertLessThan($break, $emOpen, 133*c4bcbc2eSAndreas Gohr 'linebreak must come after the emphasis opener'); 134*c4bcbc2eSAndreas Gohr $this->assertLessThan($emClose, $break, 135*c4bcbc2eSAndreas Gohr 'linebreak must come before the emphasis closer'); 136*c4bcbc2eSAndreas Gohr } 137*c4bcbc2eSAndreas Gohr 138*c4bcbc2eSAndreas Gohr function testGetSortValue() 139*c4bcbc2eSAndreas Gohr { 140*c4bcbc2eSAndreas Gohr $mode = new GfmLinebreak(); 141*c4bcbc2eSAndreas Gohr $this->assertSame(140, $mode->getSort()); 142*c4bcbc2eSAndreas Gohr } 143*c4bcbc2eSAndreas Gohr} 144