xref: /dokuwiki/_test/tests/Parsing/ParserMode/GfmTableTest.php (revision 3dabe4e0a0d70b79a7aced8ac8a36d4b37a61024)
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. The unescape itself — turning `\|` into a literal `|` in
96     *  the cell content — is GfmEscape's job and is not yet implemented;
97     *  here we only assert the cell-splitting contract that GfmTable owns. */
98    public function testEscapedPipeDoesNotSplitCell()
99    {
100        $this->P->addMode('gfm_table', new GfmTable());
101        $this->P->parse("| f\\|oo |\n| ---- |");
102
103        // One cell, content `f\|oo` literal (escape preserved). When
104        // GfmEscape lands the same input will collapse to `f|oo` without
105        // any change here.
106        $expected = [
107            ['document_start', []],
108            ['table_open', [1, 1, 1]],
109            ['tablethead_open', []],
110            ['tablerow_open', []],
111            ['tableheader_open', [1, null, 1]],
112            ['cdata', ['f\\|oo']],
113            ['tableheader_close', []],
114            ['tablerow_close', []],
115            ['tablethead_close', []],
116            ['table_close', [19]],
117            ['document_end', []],
118        ];
119        $this->assertCalls($expected, $this->H->calls);
120    }
121
122    /** Spec example 201: a blockquote line terminates the table. */
123    public function testTerminatedByBlockquote()
124    {
125        $this->P->addMode('gfm_table', new GfmTable());
126        $this->P->parse("| abc | def |\n| --- | --- |\n| bar | baz |\n> bar");
127
128        // Expect the table to end at the start of `> bar`; the trailing
129        // `> bar` is left as cdata (no quote mode added in this test).
130        $expected = [
131            ['document_start', []],
132            ['table_open', [2, 2, 1]],
133            ['tablethead_open', []],
134            ['tablerow_open', []],
135            ['tableheader_open', [1, null, 1]],
136            ['cdata', ['abc']],
137            ['tableheader_close', []],
138            ['tableheader_open', [1, null, 1]],
139            ['cdata', ['def']],
140            ['tableheader_close', []],
141            ['tablerow_close', []],
142            ['tablethead_close', []],
143            ['tabletbody_open', []],
144            ['tablerow_open', []],
145            ['tablecell_open', [1, null, 1]],
146            ['cdata', ['bar']],
147            ['tablecell_close', []],
148            ['tablecell_open', [1, null, 1]],
149            ['cdata', ['baz']],
150            ['tablecell_close', []],
151            ['tablerow_close', []],
152            ['tabletbody_close', []],
153            ['table_close', [42]],
154            ['p_open', []],
155            ['cdata', ['> bar']],
156            ['p_close', []],
157            ['document_end', []],
158        ];
159        $this->assertCalls($expected, $this->H->calls);
160    }
161
162    /** Spec example 202: short body row gets padded to header column count. */
163    public function testShortBodyRowPadded()
164    {
165        $this->P->addMode('gfm_table', new GfmTable());
166        $this->P->parse("| abc | def |\n| --- | --- |\n| bar | baz |\nbar");
167
168        $expected = [
169            ['document_start', []],
170            ['table_open', [2, 3, 1]],
171            ['tablethead_open', []],
172            ['tablerow_open', []],
173            ['tableheader_open', [1, null, 1]],
174            ['cdata', ['abc']],
175            ['tableheader_close', []],
176            ['tableheader_open', [1, null, 1]],
177            ['cdata', ['def']],
178            ['tableheader_close', []],
179            ['tablerow_close', []],
180            ['tablethead_close', []],
181            ['tabletbody_open', []],
182            ['tablerow_open', []],
183            ['tablecell_open', [1, null, 1]],
184            ['cdata', ['bar']],
185            ['tablecell_close', []],
186            ['tablecell_open', [1, null, 1]],
187            ['cdata', ['baz']],
188            ['tablecell_close', []],
189            ['tablerow_close', []],
190            ['tablerow_open', []],
191            ['tablecell_open', [1, null, 1]],
192            ['cdata', ['bar']],
193            ['tablecell_close', []],
194            ['tablecell_open', [1, null, 1]],
195            ['tablecell_close', []],
196            ['tablerow_close', []],
197            ['tabletbody_close', []],
198            ['table_close', [46]],
199            ['document_end', []],
200        ];
201        $this->assertCalls($expected, $this->H->calls);
202    }
203
204    /** Spec example 204: long body row truncated to header column count. */
205    public function testLongBodyRowTruncated()
206    {
207        $this->P->addMode('gfm_table', new GfmTable());
208        $this->P->parse("| abc | def |\n| --- | --- |\n| bar |\n| bar | baz | boo |");
209
210        $expected = [
211            ['document_start', []],
212            ['table_open', [2, 3, 1]],
213            ['tablethead_open', []],
214            ['tablerow_open', []],
215            ['tableheader_open', [1, null, 1]],
216            ['cdata', ['abc']],
217            ['tableheader_close', []],
218            ['tableheader_open', [1, null, 1]],
219            ['cdata', ['def']],
220            ['tableheader_close', []],
221            ['tablerow_close', []],
222            ['tablethead_close', []],
223            ['tabletbody_open', []],
224            ['tablerow_open', []],
225            ['tablecell_open', [1, null, 1]],
226            ['cdata', ['bar']],
227            ['tablecell_close', []],
228            ['tablecell_open', [1, null, 1]],
229            ['tablecell_close', []],
230            ['tablerow_close', []],
231            ['tablerow_open', []],
232            ['tablecell_open', [1, null, 1]],
233            ['cdata', ['bar']],
234            ['tablecell_close', []],
235            ['tablecell_open', [1, null, 1]],
236            ['cdata', ['baz']],
237            ['tablecell_close', []],
238            ['tablerow_close', []],
239            ['tabletbody_close', []],
240            ['table_close', [56]],
241            ['document_end', []],
242        ];
243        $this->assertCalls($expected, $this->H->calls);
244    }
245
246    /** Spec example 203: header has 2 cells, delimiter has 1 - the regex
247     *  matches but the rewriter detects the mismatch and emits cdata, which
248     *  the Block rewriter wraps in a paragraph. */
249    public function testColumnCountMismatchFallback()
250    {
251        $this->P->addMode('gfm_table', new GfmTable());
252        $this->P->parse("| abc | def |\n| --- |\n| bar |");
253
254        $expected = [
255            ['document_start', []],
256            ['p_open', []],
257            ['cdata', ["| abc | def |\n| --- |\n| bar |"]],
258            ['p_close', []],
259            ['document_end', []],
260        ];
261        $this->assertCalls($expected, $this->H->calls);
262    }
263
264    /** Spec example 205: header + delimiter only, no body rows. */
265    public function testEmptyBody()
266    {
267        $this->P->addMode('gfm_table', new GfmTable());
268        $this->P->parse("| abc | def |\n| --- | --- |");
269
270        $expected = [
271            ['document_start', []],
272            ['table_open', [2, 1, 1]],
273            ['tablethead_open', []],
274            ['tablerow_open', []],
275            ['tableheader_open', [1, null, 1]],
276            ['cdata', ['abc']],
277            ['tableheader_close', []],
278            ['tableheader_open', [1, null, 1]],
279            ['cdata', ['def']],
280            ['tableheader_close', []],
281            ['tablerow_close', []],
282            ['tablethead_close', []],
283            ['table_close', [28]],
284            ['document_end', []],
285        ];
286        $this->assertCalls($expected, $this->H->calls);
287    }
288}
289