xref: /dokuwiki/_test/tests/Parsing/ParserMode/FormattingTest.php (revision 10fb3d6558c6f13ffaca18fede92dbc37fe3ede0)
1<?php
2
3namespace dokuwiki\test\Parsing\ParserMode;
4
5use dokuwiki\Parsing\ParserMode\Deleted;
6use dokuwiki\Parsing\ParserMode\Emphasis;
7use dokuwiki\Parsing\ParserMode\Monospace;
8use dokuwiki\Parsing\ParserMode\Strong;
9use dokuwiki\Parsing\ParserMode\Subscript;
10use dokuwiki\Parsing\ParserMode\Superscript;
11use dokuwiki\Parsing\ParserMode\Underline;
12
13/**
14 * Tests for the individual formatting modes (bold, italic, underline, etc.)
15 */
16class FormattingTest extends ParserTestBase
17{
18    function testStrong()
19    {
20        $this->P->addMode('strong', new Strong());
21        $this->P->parse('Foo **Bar** Baz');
22        $calls = [
23            ['document_start', []],
24            ['p_open', []],
25            ['cdata', ["\nFoo "]],
26            ['strong_open', []],
27            ['cdata', ['Bar']],
28            ['strong_close', []],
29            ['cdata', [' Baz']],
30            ['p_close', []],
31            ['document_end', []],
32        ];
33        $this->assertCalls($calls, $this->H->calls);
34    }
35
36    function testEmphasis()
37    {
38        $this->P->addMode('emphasis', new Emphasis());
39        $this->P->parse('Foo //Bar// Baz');
40        $calls = [
41            ['document_start', []],
42            ['p_open', []],
43            ['cdata', ["\nFoo "]],
44            ['emphasis_open', []],
45            ['cdata', ['Bar']],
46            ['emphasis_close', []],
47            ['cdata', [' Baz']],
48            ['p_close', []],
49            ['document_end', []],
50        ];
51        $this->assertCalls($calls, $this->H->calls);
52    }
53
54    function testUnderline()
55    {
56        $this->P->addMode('underline', new Underline());
57        $this->P->parse('Foo __Bar__ Baz');
58        $calls = [
59            ['document_start', []],
60            ['p_open', []],
61            ['cdata', ["\nFoo "]],
62            ['underline_open', []],
63            ['cdata', ['Bar']],
64            ['underline_close', []],
65            ['cdata', [' Baz']],
66            ['p_close', []],
67            ['document_end', []],
68        ];
69        $this->assertCalls($calls, $this->H->calls);
70    }
71
72    function testMonospace()
73    {
74        $this->P->addMode('monospace', new Monospace());
75        $this->P->parse("Foo ''Bar'' Baz");
76        $calls = [
77            ['document_start', []],
78            ['p_open', []],
79            ['cdata', ["\nFoo "]],
80            ['monospace_open', []],
81            ['cdata', ['Bar']],
82            ['monospace_close', []],
83            ['cdata', [' Baz']],
84            ['p_close', []],
85            ['document_end', []],
86        ];
87        $this->assertCalls($calls, $this->H->calls);
88    }
89
90    function testSubscript()
91    {
92        $this->P->addMode('subscript', new Subscript());
93        $this->P->parse('Foo <sub>Bar</sub> Baz');
94        $calls = [
95            ['document_start', []],
96            ['p_open', []],
97            ['cdata', ["\nFoo "]],
98            ['subscript_open', []],
99            ['cdata', ['Bar']],
100            ['subscript_close', []],
101            ['cdata', [' Baz']],
102            ['p_close', []],
103            ['document_end', []],
104        ];
105        $this->assertCalls($calls, $this->H->calls);
106    }
107
108    function testSuperscript()
109    {
110        $this->P->addMode('superscript', new Superscript());
111        $this->P->parse('Foo <sup>Bar</sup> Baz');
112        $calls = [
113            ['document_start', []],
114            ['p_open', []],
115            ['cdata', ["\nFoo "]],
116            ['superscript_open', []],
117            ['cdata', ['Bar']],
118            ['superscript_close', []],
119            ['cdata', [' Baz']],
120            ['p_close', []],
121            ['document_end', []],
122        ];
123        $this->assertCalls($calls, $this->H->calls);
124    }
125
126    function testDeleted()
127    {
128        $this->P->addMode('deleted', new Deleted());
129        $this->P->parse('Foo <del>Bar</del> Baz');
130        $calls = [
131            ['document_start', []],
132            ['p_open', []],
133            ['cdata', ["\nFoo "]],
134            ['deleted_open', []],
135            ['cdata', ['Bar']],
136            ['deleted_close', []],
137            ['cdata', [' Baz']],
138            ['p_close', []],
139            ['document_end', []],
140        ];
141        $this->assertCalls($calls, $this->H->calls);
142    }
143
144    function testNesting()
145    {
146        $this->P->addMode('strong', new Strong());
147        $this->P->addMode('emphasis', new Emphasis());
148        $this->P->parse('Foo **bold //and italic// text** Bar');
149        $calls = [
150            ['document_start', []],
151            ['p_open', []],
152            ['cdata', ["\nFoo "]],
153            ['strong_open', []],
154            ['cdata', ['bold ']],
155            ['emphasis_open', []],
156            ['cdata', ['and italic']],
157            ['emphasis_close', []],
158            ['cdata', [' text']],
159            ['strong_close', []],
160            ['cdata', [' Bar']],
161            ['p_close', []],
162            ['document_end', []],
163        ];
164        $this->assertCalls($calls, $this->H->calls);
165    }
166
167    function testNoSelfNesting()
168    {
169        $this->P->addMode('strong', new Strong());
170        $this->P->parse('Foo **bold **not nested** end** Bar');
171        $calls = [
172            ['document_start', []],
173            ['p_open', []],
174            ['cdata', ["\nFoo "]],
175            ['strong_open', []],
176            ['cdata', ['bold ']],
177            ['strong_close', []],
178            ['cdata', ['not nested']],
179            ['strong_open', []],
180            ['cdata', [' end']],
181            ['strong_close', []],
182            ['cdata', [' Bar']],
183            ['p_close', []],
184            ['document_end', []],
185        ];
186        $this->assertCalls($calls, $this->H->calls);
187    }
188
189    /**
190     * @dataProvider provideParagraphBoundaryModes
191     *
192     * Formatting delimiters must not match across a blank line. An unclosed
193     * delimiter followed by a blank line and then an unrelated delimiter
194     * further down must stay literal — otherwise the lexer greedily swallows
195     * the paragraph break.
196     */
197    function testDelimitersDoNotSpanParagraphBoundary(
198        string $modeName,
199        $mode,
200        string $input
201    ) {
202        $this->P->addMode($modeName, $mode);
203        $this->P->parse($input);
204        foreach ($this->H->calls as $call) {
205            $this->assertNotSame(
206                $modeName . '_open',
207                $call[0],
208                "Mode '$modeName' must not open across a blank line in: " . json_encode($input)
209            );
210        }
211    }
212
213    public static function provideParagraphBoundaryModes(): array
214    {
215        return [
216            'strong'      => ['strong',      new Strong(),      "**open\n\nclose**"],
217            'emphasis'    => ['emphasis',    new Emphasis(),    "//open\n\nclose//"],
218            'underline'   => ['underline',   new Underline(),   "__open\n\nclose__"],
219            'monospace'   => ['monospace',   new Monospace(),   "''open\n\nclose''"],
220            'subscript'   => ['subscript',   new Subscript(),   "<sub>open\n\nclose</sub>"],
221            'superscript' => ['superscript', new Superscript(), "<sup>open\n\nclose</sup>"],
222            'deleted'     => ['deleted',     new Deleted(),     "<del>open\n\nclose</del>"],
223        ];
224    }
225
226    /**
227     * A single newline inside a delimiter pair is still valid (multi-line
228     * formatting), only blank lines end it.
229     */
230    function testStrongAllowsSingleNewline()
231    {
232        $this->P->addMode('strong', new Strong());
233        $this->P->parse("**open\nclose**");
234        $this->assertContains(
235            'strong_open',
236            array_column($this->H->calls, 0),
237            'Strong must still match across a single newline'
238        );
239    }
240}
241