xref: /dokuwiki/_test/tests/Parsing/ParserMode/GfmTableTest.php (revision 95f694202286c1add4c442936a5caa38db0dd603)
1<?php
2
3namespace dokuwiki\test\Parsing\ParserMode;
4
5use dokuwiki\Parsing\ParserMode\GfmTable;
6
7/**
8 * Tests for GFM table blocks.
9 *
10 * GfmTable uses an entry/exit lexer state with allowedModes-driven inline
11 * nesting, then runs a small post-pass rewriter (Handler\GfmTable) that
12 * derives column alignment from the delimiter row, drops it, pads/truncates
13 * body rows, and emits the canonical DokuWiki table call sequence. Tests
14 * here assert against the rewriter's output, not the raw `gfm_table_*`
15 * tokens it consumes.
16 */
17class GfmTableTest extends ParserTestBase
18{
19    public function testSort()
20    {
21        $this->assertSame(55, (new GfmTable())->getSort());
22    }
23
24    /** Spec example 198: basic table, no alignment, plain text. */
25    public function testBasicTable()
26    {
27        $this->P->addMode('gfm_table', new GfmTable());
28        $this->P->parse("| foo | bar |\n| --- | --- |\n| baz | bim |");
29
30        $expected = [
31            ['document_start', []],
32            ['table_open', [2, 2, 1]],
33            ['tablethead_open', []],
34            ['tablerow_open', []],
35            ['tableheader_open', [1, null, 1]],
36            ['cdata', ['foo']],
37            ['tableheader_close', []],
38            ['tableheader_open', [1, null, 1]],
39            ['cdata', ['bar']],
40            ['tableheader_close', []],
41            ['tablerow_close', []],
42            ['tablethead_close', []],
43            ['tabletbody_open', []],
44            ['tablerow_open', []],
45            ['tablecell_open', [1, null, 1]],
46            ['cdata', ['baz']],
47            ['tablecell_close', []],
48            ['tablecell_open', [1, null, 1]],
49            ['cdata', ['bim']],
50            ['tablecell_close', []],
51            ['tablerow_close', []],
52            ['tabletbody_close', []],
53            ['table_close', [42]],
54            ['document_end', []],
55        ];
56        $this->assertCalls($expected, $this->H->calls);
57    }
58
59    /** Spec example 199: alignment via `:-:` and `---:`, no outer pipes. */
60    public function testAlignment()
61    {
62        $this->P->addMode('gfm_table', new GfmTable());
63        $this->P->parse("| abc | defghi |\n:-: | -----------:\nbar | baz");
64
65        $expected = [
66            ['document_start', []],
67            ['table_open', [2, 2, 1]],
68            ['tablethead_open', []],
69            ['tablerow_open', []],
70            ['tableheader_open', [1, 'center', 1]],
71            ['cdata', ['abc']],
72            ['tableheader_close', []],
73            ['tableheader_open', [1, 'right', 1]],
74            ['cdata', ['defghi']],
75            ['tableheader_close', []],
76            ['tablerow_close', []],
77            ['tablethead_close', []],
78            ['tabletbody_open', []],
79            ['tablerow_open', []],
80            ['tablecell_open', [1, 'center', 1]],
81            ['cdata', ['bar']],
82            ['tablecell_close', []],
83            ['tablecell_open', [1, 'right', 1]],
84            ['cdata', ['baz']],
85            ['tablecell_close', []],
86            ['tablerow_close', []],
87            ['tabletbody_close', []],
88            ['table_close', [46]],
89            ['document_end', []],
90        ];
91        $this->assertCalls($expected, $this->H->calls);
92    }
93
94    /** Spec example 200 (partial): a backslash-escaped pipe must not split
95     *  the cell, and per the GFM tables extension `\|` unescapes to `|`
96     *  in the rendered cell — even when no general escape mode is active.
97     *  This test exercises GfmTable in isolation (no gfm_escape registered)
98     *  so the rewriter's own per-cell `\|`→`|` pass is what produces the
99     *  unescape. */
100    public function testEscapedPipeDoesNotSplitCell()
101    {
102        $this->P->addMode('gfm_table', new GfmTable());
103        $this->P->parse("| f\\|oo |\n| ---- |");
104
105        // One cell, content `f|oo`: the `\|` is unescaped by GfmTable's
106        // tables-extension pipe rewrite.
107        $expected = [
108            ['document_start', []],
109            ['table_open', [1, 1, 1]],
110            ['tablethead_open', []],
111            ['tablerow_open', []],
112            ['tableheader_open', [1, null, 1]],
113            ['cdata', ['f|oo']],
114            ['tableheader_close', []],
115            ['tablerow_close', []],
116            ['tablethead_close', []],
117            ['table_close', [19]],
118            ['document_end', []],
119        ];
120        $this->assertCalls($expected, $this->H->calls);
121    }
122
123    /** Spec example 201: a blockquote line terminates the table. */
124    public function testTerminatedByBlockquote()
125    {
126        $this->P->addMode('gfm_table', new GfmTable());
127        $this->P->parse("| abc | def |\n| --- | --- |\n| bar | baz |\n> bar");
128
129        // Expect the table to end at the start of `> bar`; the trailing
130        // `> bar` is left as cdata (no quote mode added in this test).
131        $expected = [
132            ['document_start', []],
133            ['table_open', [2, 2, 1]],
134            ['tablethead_open', []],
135            ['tablerow_open', []],
136            ['tableheader_open', [1, null, 1]],
137            ['cdata', ['abc']],
138            ['tableheader_close', []],
139            ['tableheader_open', [1, null, 1]],
140            ['cdata', ['def']],
141            ['tableheader_close', []],
142            ['tablerow_close', []],
143            ['tablethead_close', []],
144            ['tabletbody_open', []],
145            ['tablerow_open', []],
146            ['tablecell_open', [1, null, 1]],
147            ['cdata', ['bar']],
148            ['tablecell_close', []],
149            ['tablecell_open', [1, null, 1]],
150            ['cdata', ['baz']],
151            ['tablecell_close', []],
152            ['tablerow_close', []],
153            ['tabletbody_close', []],
154            ['table_close', [42]],
155            ['p_open', []],
156            ['cdata', ['> bar']],
157            ['p_close', []],
158            ['document_end', []],
159        ];
160        $this->assertCalls($expected, $this->H->calls);
161    }
162
163    /** Spec example 202: short body row gets padded to header column count. */
164    public function testShortBodyRowPadded()
165    {
166        $this->P->addMode('gfm_table', new GfmTable());
167        $this->P->parse("| abc | def |\n| --- | --- |\n| bar | baz |\nbar");
168
169        $expected = [
170            ['document_start', []],
171            ['table_open', [2, 3, 1]],
172            ['tablethead_open', []],
173            ['tablerow_open', []],
174            ['tableheader_open', [1, null, 1]],
175            ['cdata', ['abc']],
176            ['tableheader_close', []],
177            ['tableheader_open', [1, null, 1]],
178            ['cdata', ['def']],
179            ['tableheader_close', []],
180            ['tablerow_close', []],
181            ['tablethead_close', []],
182            ['tabletbody_open', []],
183            ['tablerow_open', []],
184            ['tablecell_open', [1, null, 1]],
185            ['cdata', ['bar']],
186            ['tablecell_close', []],
187            ['tablecell_open', [1, null, 1]],
188            ['cdata', ['baz']],
189            ['tablecell_close', []],
190            ['tablerow_close', []],
191            ['tablerow_open', []],
192            ['tablecell_open', [1, null, 1]],
193            ['cdata', ['bar']],
194            ['tablecell_close', []],
195            ['tablecell_open', [1, null, 1]],
196            ['tablecell_close', []],
197            ['tablerow_close', []],
198            ['tabletbody_close', []],
199            ['table_close', [46]],
200            ['document_end', []],
201        ];
202        $this->assertCalls($expected, $this->H->calls);
203    }
204
205    /** Spec example 204: long body row truncated to header column count. */
206    public function testLongBodyRowTruncated()
207    {
208        $this->P->addMode('gfm_table', new GfmTable());
209        $this->P->parse("| abc | def |\n| --- | --- |\n| bar |\n| bar | baz | boo |");
210
211        $expected = [
212            ['document_start', []],
213            ['table_open', [2, 3, 1]],
214            ['tablethead_open', []],
215            ['tablerow_open', []],
216            ['tableheader_open', [1, null, 1]],
217            ['cdata', ['abc']],
218            ['tableheader_close', []],
219            ['tableheader_open', [1, null, 1]],
220            ['cdata', ['def']],
221            ['tableheader_close', []],
222            ['tablerow_close', []],
223            ['tablethead_close', []],
224            ['tabletbody_open', []],
225            ['tablerow_open', []],
226            ['tablecell_open', [1, null, 1]],
227            ['cdata', ['bar']],
228            ['tablecell_close', []],
229            ['tablecell_open', [1, null, 1]],
230            ['tablecell_close', []],
231            ['tablerow_close', []],
232            ['tablerow_open', []],
233            ['tablecell_open', [1, null, 1]],
234            ['cdata', ['bar']],
235            ['tablecell_close', []],
236            ['tablecell_open', [1, null, 1]],
237            ['cdata', ['baz']],
238            ['tablecell_close', []],
239            ['tablerow_close', []],
240            ['tabletbody_close', []],
241            ['table_close', [56]],
242            ['document_end', []],
243        ];
244        $this->assertCalls($expected, $this->H->calls);
245    }
246
247    /** Spec example 203: header has 2 cells, delimiter has 1 - the regex
248     *  matches but the rewriter detects the mismatch and emits cdata, which
249     *  the Block rewriter wraps in a paragraph. */
250    public function testColumnCountMismatchFallback()
251    {
252        $this->P->addMode('gfm_table', new GfmTable());
253        $this->P->parse("| abc | def |\n| --- |\n| bar |");
254
255        $expected = [
256            ['document_start', []],
257            ['p_open', []],
258            ['cdata', ["| abc | def |\n| --- |\n| bar |"]],
259            ['p_close', []],
260            ['document_end', []],
261        ];
262        $this->assertCalls($expected, $this->H->calls);
263    }
264
265    /** Spec example 205: header + delimiter only, no body rows. */
266    public function testEmptyBody()
267    {
268        $this->P->addMode('gfm_table', new GfmTable());
269        $this->P->parse("| abc | def |\n| --- | --- |");
270
271        $expected = [
272            ['document_start', []],
273            ['table_open', [2, 1, 1]],
274            ['tablethead_open', []],
275            ['tablerow_open', []],
276            ['tableheader_open', [1, null, 1]],
277            ['cdata', ['abc']],
278            ['tableheader_close', []],
279            ['tableheader_open', [1, null, 1]],
280            ['cdata', ['def']],
281            ['tableheader_close', []],
282            ['tablerow_close', []],
283            ['tablethead_close', []],
284            ['table_close', [28]],
285            ['document_end', []],
286        ];
287        $this->assertCalls($expected, $this->H->calls);
288    }
289}
290