xref: /dokuwiki/_test/tests/Parsing/ParserMode/GfmDeletedTest.php (revision 057ab5a057a76df652cfe4176512a550ca9937f6)
1<?php
2
3namespace dokuwiki\test\Parsing\ParserMode;
4
5use dokuwiki\Parsing\ParserMode\GfmDeleted;
6
7/**
8 * Tests for the GFM strikethrough mode (`~~text~~`).
9 */
10class GfmDeletedTest extends ParserTestBase
11{
12    public function setUp(): void
13    {
14        parent::setUp();
15        $this->setSyntax('md');
16    }
17
18    public function testBasicStrikethrough()
19    {
20        $this->P->addMode('gfm_deleted', new GfmDeleted());
21        $this->P->parse('Foo ~~Bar~~ Baz');
22
23        $calls = [
24            ['document_start', []],
25            ['p_open', []],
26            ['cdata', ["\nFoo "]],
27            ['deleted_open', []],
28            ['cdata', ['Bar']],
29            ['deleted_close', []],
30            ['cdata', [' Baz']],
31            ['p_close', []],
32            ['document_end', []],
33        ];
34        $this->assertCalls($calls, $this->H->calls);
35    }
36
37    public function testSingleCharacterBody()
38    {
39        $this->P->addMode('gfm_deleted', new GfmDeleted());
40        $this->P->parse('foo ~~b~~ bar');
41
42        $calls = [
43            ['document_start', []],
44            ['p_open', []],
45            ['cdata', ["\nfoo "]],
46            ['deleted_open', []],
47            ['cdata', ['b']],
48            ['deleted_close', []],
49            ['cdata', [' bar']],
50            ['p_close', []],
51            ['document_end', []],
52        ];
53        $this->assertCalls($calls, $this->H->calls);
54    }
55
56    public function testMultipleWords()
57    {
58        $this->P->addMode('gfm_deleted', new GfmDeleted());
59        $this->P->parse('~~three four five~~');
60
61        $calls = [
62            ['document_start', []],
63            ['p_open', []],
64            ['cdata', ["\n"]],
65            ['deleted_open', []],
66            ['cdata', ['three four five']],
67            ['deleted_close', []],
68            ['cdata', ['']],
69            ['p_close', []],
70            ['document_end', []],
71        ];
72        $this->assertCalls($calls, $this->H->calls);
73    }
74
75    public function testTwoSeparateStrikethroughsOnOneLine()
76    {
77        $this->P->addMode('gfm_deleted', new GfmDeleted());
78        $this->P->parse('~~one~~ and ~~two~~');
79
80        $calls = [
81            ['document_start', []],
82            ['p_open', []],
83            ['cdata', ["\n"]],
84            ['deleted_open', []],
85            ['cdata', ['one']],
86            ['deleted_close', []],
87            ['cdata', [' and ']],
88            ['deleted_open', []],
89            ['cdata', ['two']],
90            ['deleted_close', []],
91            ['cdata', ['']],
92            ['p_close', []],
93            ['document_end', []],
94        ];
95        $this->assertCalls($calls, $this->H->calls);
96    }
97
98    public function testUnmatchedOpenerDoesNotStrike()
99    {
100        $this->P->addMode('gfm_deleted', new GfmDeleted());
101        $this->P->parse('foo ~~bar with no closer');
102
103        $calls = [
104            ['document_start', []],
105            ['p_open', []],
106            ['cdata', ["\nfoo ~~bar with no closer"]],
107            ['p_close', []],
108            ['document_end', []],
109        ];
110        $this->assertCalls($calls, $this->H->calls);
111    }
112
113    public function testOpenerFollowedBySpaceDoesNotStrike()
114    {
115        $this->P->addMode('gfm_deleted', new GfmDeleted());
116        $this->P->parse('foo ~~ bar~~ baz');
117
118        $calls = [
119            ['document_start', []],
120            ['p_open', []],
121            ['cdata', ["\nfoo ~~ bar~~ baz"]],
122            ['p_close', []],
123            ['document_end', []],
124        ];
125        $this->assertCalls($calls, $this->H->calls);
126    }
127
128    public function testEmptyDelimiterDoesNotStrike()
129    {
130        $this->P->addMode('gfm_deleted', new GfmDeleted());
131        $this->P->parse('foo ~~~~ bar');
132
133        $modes = array_column($this->H->calls, 0);
134        $this->assertNotContains('deleted_open', $modes,
135            'Empty `~~~~` must stay literal');
136    }
137
138    public function testTripleTildeDoesNotStrike()
139    {
140        // `~~~` is the GFM fenced-code-block marker; strikethrough must not
141        // consume a run of three or more tildes.
142        $this->P->addMode('gfm_deleted', new GfmDeleted());
143        $this->P->parse('foo ~~~bar~~~ baz');
144
145        $modes = array_column($this->H->calls, 0);
146        $this->assertNotContains('deleted_open', $modes,
147            'Run of 3+ tildes must not trigger strikethrough');
148    }
149
150    public function testMultilineStrikethrough()
151    {
152        $this->P->addMode('gfm_deleted', new GfmDeleted());
153        $this->P->parse("~~line\nline\nline~~");
154
155        $calls = [
156            ['document_start', []],
157            ['p_open', []],
158            ['cdata', ["\n"]],
159            ['deleted_open', []],
160            ['cdata', ["line\nline\nline"]],
161            ['deleted_close', []],
162            ['cdata', ['']],
163            ['p_close', []],
164            ['document_end', []],
165        ];
166        $this->assertCalls($calls, $this->H->calls);
167    }
168
169    public function testDoesNotSpanParagraphBoundary()
170    {
171        // An unclosed `~~` followed by a blank line must stay literal.
172        // Mirrors GFM spec example 492.
173        $this->P->addMode('gfm_deleted', new GfmDeleted());
174        $this->P->parse("This ~~has a\n\nnew paragraph~~.");
175
176        $modes = array_column($this->H->calls, 0);
177        $this->assertNotContains('deleted_open', $modes,
178            'GfmDeleted must not open when the closing `~~` is past a blank line');
179    }
180
181    public function testAllowsSingleNewline()
182    {
183        $this->P->addMode('gfm_deleted', new GfmDeleted());
184        $this->P->parse("~~open\nclose~~");
185
186        $modes = array_column($this->H->calls, 0);
187        $this->assertContains('deleted_open', $modes,
188            'GfmDeleted must still match across a single newline');
189    }
190
191    public function testTrailingWhitespaceBeforeCloserDoesNotStrike()
192    {
193        $this->P->addMode('gfm_deleted', new GfmDeleted());
194        $this->P->parse('~~foo bar ~~');
195
196        $modes = array_column($this->H->calls, 0);
197        $this->assertNotContains('deleted_open', $modes,
198            'Closer preceded by whitespace must not match');
199    }
200
201    public function testSortValue()
202    {
203        $mode = new GfmDeleted();
204        $this->assertSame(130, $mode->getSort());
205    }
206
207    public function testRejectedOpenerBeforeValidSpanStaysLiteral()
208    {
209        $this->P->addMode('gfm_deleted', new GfmDeleted());
210        $this->P->parse('~~ foo ~~bar~~');
211        $this->assertContains('deleted_open', array_column($this->H->calls, 0));
212        $this->assertStringContainsString('~~ foo ', $this->H->calls[2][1][0]);
213    }
214
215    public function testVeryLongSpanIsRecognized()
216    {
217        $body = trim(str_repeat('some words ', 7000)); // ~77KB
218        $this->P->addMode('gfm_deleted', new GfmDeleted());
219        $this->P->parse('~~' . $body . '~~');
220
221        $calls = array_map(fn($call) => [$call[0], $call[1]], $this->H->calls);
222        $this->assertContains(['deleted_open', []], $calls);
223        $this->assertContains(['cdata', [$body]], $calls);
224    }
225}
226