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