1<?php 2 3namespace dokuwiki\test\Parsing\ParserMode; 4 5use dokuwiki\Parsing\Handler\Lists; 6use dokuwiki\Parsing\ParserMode\Code; 7use dokuwiki\Parsing\ParserMode\Eol; 8use dokuwiki\Parsing\ParserMode\Footnote; 9use dokuwiki\Parsing\ParserMode\Strong; 10use dokuwiki\Parsing\ParserMode\GfmHr; 11use dokuwiki\Parsing\ParserMode\Listblock; 12use dokuwiki\Parsing\ParserMode\GfmQuote; 13use dokuwiki\Parsing\ParserMode\Preformatted; 14use dokuwiki\Parsing\ParserMode\Table; 15use dokuwiki\Parsing\ParserMode\Unformatted; 16 17class FootnoteTest extends ParserTestBase 18{ 19 20 function setUp() : void { 21 parent::setUp(); 22 $this->P->addMode('footnote',new Footnote()); 23 } 24 25 function testFootnote() { 26 $this->P->parse('Foo (( testing )) Bar'); 27 $calls = [ 28 ['document_start',[]], 29 ['p_open',[]], 30 ['cdata',["\n".'Foo ']], 31 ['nest', [ [ 32 ['footnote_open',[]], 33 ['cdata',[' testing ']], 34 ['footnote_close',[]], 35 ]]], 36 ['cdata',[' Bar']], 37 ['p_close',[]], 38 ['document_end',[]], 39 ]; 40 $this->assertCalls($calls, $this->H->calls); 41 } 42 43 function testNotAFootnote() { 44 $this->P->parse("Foo (( testing\n Bar"); 45 $calls = [ 46 ['document_start',[]], 47 ['p_open',[]], 48 ['cdata',["\nFoo (( testing\n Bar"]], 49 ['p_close',[]], 50 ['document_end',[]], 51 ]; 52 $this->assertCalls($calls, $this->H->calls); 53 } 54 55 function testFootnoteLinefeed() { 56 $this->P->addMode('eol',new Eol()); 57 $this->P->parse("Foo (( testing\ntesting )) Bar"); 58 $calls = [ 59 ['document_start',[]], 60 ['p_open',[]], 61 ['cdata',['Foo ']], 62 ['nest', [ [ 63 ['footnote_open',[]], 64 ['cdata',[" testing\ntesting "]], 65 ['footnote_close',[]], 66 ]]], 67 ['cdata',[' Bar']], 68 ['p_close',[]], 69 ['document_end',[]], 70 ]; 71 $this->assertCalls($calls, $this->H->calls); 72 } 73 74 function testFootnoteNested() { 75 $this->P->parse('Foo (( x((y))z )) Bar'); 76 $calls = [ 77 ['document_start',[]], 78 ['p_open',[]], 79 ['cdata',["\n".'Foo ']], 80 ['nest', [ [ 81 ['footnote_open',[]], 82 ['cdata',[' x((y']], 83 ['footnote_close',[]], 84 ]]], 85 ['cdata',['z )) Bar']], 86 ['p_close',[]], 87 ['document_end',[]], 88 ]; 89 $this->assertCalls($calls, $this->H->calls); 90 } 91 92 function testFootnoteEol() { 93 $this->P->addMode('eol',new Eol()); 94 $this->P->parse("Foo \nX(( test\ning ))Y\n Bar"); 95 $calls = [ 96 ['document_start',[]], 97 ['p_open',[]], 98 ['cdata',['Foo'."\n".'X']], 99 ['nest', [ [ 100 ['footnote_open',[]], 101 ['cdata',[" test\ning "]], 102 ['footnote_close',[]], 103 ]]], 104 ['cdata',['Y'."\n".'Bar']], 105 ['p_close',[]], 106 ['document_end',[]], 107 ]; 108 $this->assertCalls($calls, $this->H->calls); 109 } 110 111 function testFootnoteStrong() { 112 $this->P->addMode('strong',new Strong()); 113 $this->P->parse('Foo (( **testing** )) Bar'); 114 $calls = [ 115 ['document_start',[]], 116 ['p_open',[]], 117 ['cdata',["\n".'Foo ']], 118 ['nest', [ [ 119 ['footnote_open',[]], 120 ['cdata',[' ']], 121 ['strong_open',[]], 122 ['cdata',['testing']], 123 ['strong_close',[]], 124 ['cdata',[' ']], 125 ['footnote_close',[]], 126 ]]], 127 ['cdata',[' Bar']], 128 ['p_close',[]], 129 ['document_end',[]], 130 ]; 131 $this->assertCalls($calls, $this->H->calls); 132 } 133 134 function testFootnoteHr() { 135 $this->P->addMode('gfm_hr',new GfmHr()); 136 $this->P->parse("Foo ((\n----\n)) Bar"); 137 $calls = [ 138 ['document_start',[]], 139 ['p_open',[]], 140 ['cdata',["\n".'Foo ']], 141 ['nest', [ [ 142 ['footnote_open',[]], 143 ['hr',[]], 144 ['cdata',["\n"]], 145 ['footnote_close',[]], 146 ]]], 147 ['cdata',[' Bar']], 148 ['p_close',[]], 149 ['document_end',[]], 150 ]; 151 $this->assertCalls($calls, $this->H->calls); 152 } 153 154 function testFootnoteCode() { 155 $this->P->addMode('code',new Code()); 156 $this->P->parse("Foo (( <code>Test</code> )) Bar"); 157 $calls = [ 158 ['document_start',[]], 159 ['p_open',[]], 160 ['cdata',["\n".'Foo ']], 161 ['nest', [ [ 162 ['footnote_open',[]], 163 ['cdata',[' ']], 164 ['code',['Test',null,null]], 165 ['cdata',[' ']], 166 ['footnote_close',[]], 167 ]]], 168 ['cdata',[' Bar']], 169 ['p_close',[]], 170 ['document_end',[]], 171 ]; 172 $this->assertCalls($calls, $this->H->calls); 173 } 174 175 function testFootnotePreformatted() { 176 $this->P->addMode('preformatted',new Preformatted()); 177 $this->P->parse("Foo (( \n Test\n )) Bar"); 178 $calls = [ 179 ['document_start',[]], 180 ['p_open',[]], 181 ['cdata',["\n".'Foo ']], 182 ['nest', [ [ 183 ['footnote_open',[]], 184 ['cdata',[' ']], 185 ['preformatted',['Test']], 186 ['cdata',[' ']], 187 ['footnote_close',[]], 188 ]]], 189 ['cdata',[' Bar']], 190 ['p_close',[]], 191 ['document_end',[]], 192 ]; 193 $this->assertCalls($calls, $this->H->calls); 194 } 195 196 function testFootnotePreformattedEol() { 197 $this->P->addMode('preformatted',new Preformatted()); 198 $this->P->addMode('eol',new Eol()); 199 $this->P->parse("Foo (( \n Test\n )) Bar"); 200 $calls = [ 201 ['document_start',[]], 202 ['p_open',[]], 203 ['cdata',['Foo ']], 204 ['nest', [ [ 205 ['footnote_open',[]], 206 ['cdata',[' ']], 207 ['preformatted',['Test']], 208 ['cdata',[' ']], 209 ['footnote_close',[]], 210 ]]], 211 ['cdata',[' Bar']], 212 ['p_close',[]], 213 ['document_end',[]], 214 ]; 215 216 $this->assertCalls($calls, $this->H->calls); 217 } 218 219 function testFootnoteUnformatted() { 220 $this->P->addMode('unformatted',new Unformatted()); 221 $this->P->parse("Foo (( <nowiki>Test</nowiki> )) Bar"); 222 $calls = [ 223 ['document_start',[]], 224 ['p_open',[]], 225 ['cdata',["\n".'Foo ']], 226 ['nest', [ [ 227 ['footnote_open',[]], 228 ['cdata',[' ']], 229 ['unformatted',['Test']], 230 ['cdata',[' ']], 231 ['footnote_close',[]], 232 ]]], 233 ['cdata',[' Bar']], 234 ['p_close',[]], 235 ['document_end',[]], 236 ]; 237 $this->assertCalls($calls, $this->H->calls); 238 } 239 240 function testFootnoteNotHeader() { 241 $this->P->addMode('unformatted',new Unformatted()); 242 $this->P->parse("Foo (( \n====Test====\n )) Bar"); 243 $calls = [ 244 ['document_start',[]], 245 ['p_open',[]], 246 ['cdata',["\n".'Foo ']], 247 ['nest', [ [ 248 ['footnote_open',[]], 249 ['cdata',[" \n====Test====\n "]], 250 ['footnote_close',[]], 251 ]]], 252 ['cdata',[' Bar']], 253 ['p_close',[]], 254 ['document_end',[]], 255 ]; 256 $this->assertCalls($calls, $this->H->calls); 257 } 258 259 function testFootnoteTable() { 260 $this->P->addMode('table',new Table()); 261 $this->P->parse("Foo (( 262| Row 0 Col 1 | Row 0 Col 2 | Row 0 Col 3 | 263| Row 1 Col 1 | Row 1 Col 2 | Row 1 Col 3 | 264 )) Bar"); 265 $calls = [ 266 ['document_start',[]], 267 ['p_open',[]], 268 ['cdata',["\n".'Foo ']], 269 ['nest', [ [ 270 ['footnote_open',[]], 271 ['table_open',[3, 2, 8]], 272 ['tablerow_open',[]], 273 ['tablecell_open',[1,'left',1]], 274 ['cdata',[' Row 0 Col 1 ']], 275 ['tablecell_close',[]], 276 ['tablecell_open',[1,'left',1]], 277 ['cdata',[' Row 0 Col 2 ']], 278 ['tablecell_close',[]], 279 ['tablecell_open',[1,'left',1]], 280 ['cdata',[' Row 0 Col 3 ']], 281 ['tablecell_close',[]], 282 ['tablerow_close',[]], 283 ['tablerow_open',[]], 284 ['tablecell_open',[1,'left',1]], 285 ['cdata',[' Row 1 Col 1 ']], 286 ['tablecell_close',[]], 287 ['tablecell_open',[1,'left',1]], 288 ['cdata',[' Row 1 Col 2 ']], 289 ['tablecell_close',[]], 290 ['tablecell_open',[1,'left',1]], 291 ['cdata',[' Row 1 Col 3 ']], 292 ['tablecell_close',[]], 293 ['tablerow_close',[]], 294 ['table_close',[123]], 295 ['cdata',[' ']], 296 ['footnote_close',[]], 297 ]]], 298 ['cdata',[' Bar']], 299 ['p_close',[]], 300 ['document_end',[]], 301 ]; 302 $this->assertCalls($calls, $this->H->calls); 303 } 304 305 function testFootnoteList() { 306 $this->P->addMode('listblock',new ListBlock()); 307 $this->P->parse("Foo (( 308 *A 309 * B 310 * C 311 )) Bar"); 312 $calls = [ 313 ['document_start',[]], 314 ['p_open',[]], 315 ['cdata',["\n".'Foo ']], 316 ['nest', [ [ 317 ['footnote_open',[]], 318 ['listu_open',[]], 319 ['listitem_open',[1,Lists::NODE]], 320 ['listcontent_open',[]], 321 ['cdata',["A"]], 322 ['listcontent_close',[]], 323 ['listu_open',[]], 324 ['listitem_open',[2]], 325 ['listcontent_open',[]], 326 ['cdata',[' B']], 327 ['listcontent_close',[]], 328 ['listitem_close',[]], 329 ['listu_close',[]], 330 ['listitem_close',[]], 331 ['listitem_open',[1]], 332 ['listcontent_open',[]], 333 ['cdata',[' C']], 334 ['listcontent_close',[]], 335 ['listitem_close',[]], 336 ['listu_close',[]], 337 ['cdata',[' ']], 338 ['footnote_close',[]], 339 ]]], 340 ['cdata',[' Bar']], 341 ['p_close',[]], 342 ['document_end',[]], 343 ]; 344 $this->assertCalls($calls, $this->H->calls); 345 } 346 347 function testFootnoteQuote() { 348 // GfmQuote is the unified quote mode (replaces DW Quote). Under 349 // the test's default DW-preferred syntax the post-pass flattens 350 // sub-parsed paragraph wrapping into linebreak-separated cdata, 351 // and nested `>>` produces a nested `quote_open` pair. The body 352 // sub-parsed call list is wrapped in a `nest` instruction. 353 $this->P->addMode('gfm_quote', new GfmQuote()); 354 $this->P->parse("Foo (( 355> def 356>>ghi 357 )) Bar"); 358 $calls = [ 359 ['document_start',[]], 360 ['p_open',[]], 361 ['cdata',["\n".'Foo ']], 362 ['nest', [ [ 363 ['footnote_open',[]], 364 ['quote_open',[]], 365 ['nest', [ [ ['cdata', ['def']] ] ]], 366 ['quote_open',[]], 367 ['nest', [ [ ['cdata', ['ghi']] ] ]], 368 ['quote_close',[]], 369 ['quote_close',[]], 370 ['cdata',["\n "]], 371 ['footnote_close',[]], 372 ]]], 373 ['cdata',[' Bar']], 374 ['p_close',[]], 375 ['document_end',[]], 376 ]; 377 378 $this->assertCalls($calls, $this->H->calls); 379 } 380 381 /** 382 * Footnotes are block-level containers (they can hold tables, lists, 383 * blockquotes, etc.), so unlike inline formatting they are allowed to 384 * span paragraph breaks. Pins this behavior down so future refactors 385 * of the inline-formatting paragraph guard don't accidentally restrict 386 * footnotes. 387 */ 388 function testFootnoteSpansParagraphBoundary() { 389 $this->P->addMode('eol', new Eol()); 390 $this->P->parse("Foo (( para one\n\npara two )) Bar"); 391 $calls = [ 392 ['document_start', []], 393 ['p_open', []], 394 ['cdata', ['Foo ']], 395 ['nest', [[ 396 ['footnote_open', []], 397 ['cdata', [" para one\n\npara two "]], 398 ['footnote_close', []], 399 ]]], 400 ['cdata', [' Bar']], 401 ['p_close', []], 402 ['document_end', []], 403 ]; 404 $this->assertCalls($calls, $this->H->calls); 405 } 406 407 function testFootnoteNesting() { 408 // Strong no longer opens where its inner `**` is adjacent to spaces 409 // (flanking rule). So `** (( b ` inside the footnote stays literal, 410 // and the footnote closes at the first `))`. The trailing `** c ))` 411 // outside has `**` adjacent to space too — also literal. 412 $this->P->addMode('strong',new Strong()); 413 $this->P->parse("(( a ** (( b )) ** c ))"); 414 415 $calls = [ 416 ['document_start',[]], 417 ['p_open',[]], 418 ['cdata',["\n"]], 419 ['nest', [ [ 420 ['footnote_open',[]], 421 ['cdata',[' a ** (( b ']], 422 ['footnote_close',[]], 423 ]]], 424 ['cdata',[" ** c ))"]], 425 ['p_close',[]], 426 ['document_end',[]], 427 ]; 428 429 $this->assertCalls($calls, $this->H->calls); 430 } 431} 432