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