xref: /dokuwiki/_test/tests/Parsing/ParserMode/GfmLinebreakTest.php (revision c4bcbc2e0c397783de26c1e3c211d82d1ac21bb4)
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'] = 'markdown';
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