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