1<?php 2 3namespace dokuwiki\test\Parsing\ParserMode; 4 5use dokuwiki\Parsing\ParserMode\Code; 6use dokuwiki\Parsing\ParserMode\Eol; 7use dokuwiki\Parsing\ParserMode\File; 8use dokuwiki\Parsing\ParserMode\Header; 9use dokuwiki\Parsing\ParserMode\Listblock; 10use dokuwiki\Parsing\ParserMode\Preformatted; 11 12class PreformattedTest extends ParserTestBase 13{ 14 15 function testFile() { 16 $this->P->addMode('file',new File()); 17 $this->P->parse('Foo <file>testing</file> Bar'); 18 $calls = [ 19 ['document_start',[]], 20 ['p_open',[]], 21 ['cdata',["\n".'Foo ']], 22 ['p_close',[]], 23 ['file',['testing',null,null]], 24 ['p_open',[]], 25 ['cdata',['Bar']], 26 ['p_close',[]], 27 ['document_end',[]], 28 ]; 29 30 $this->assertCalls($calls, $this->H->calls); 31 } 32 33 function testCode() { 34 $this->P->addMode('code',new Code()); 35 $this->P->parse('Foo <code>testing</code> Bar'); 36 $calls = [ 37 ['document_start',[]], 38 ['p_open',[]], 39 ['cdata',["\n".'Foo ']], 40 ['p_close',[]], 41 ['code',['testing', null, null]], 42 ['p_open',[]], 43 ['cdata',['Bar']], 44 ['p_close',[]], 45 ['document_end',[]], 46 ]; 47 $this->assertCalls($calls, $this->H->calls); 48 } 49 50 function testCodeWhitespace() { 51 $this->P->addMode('code',new Code()); 52 $this->P->parse("Foo <code \n>testing</code> Bar"); 53 $calls = [ 54 ['document_start',[]], 55 ['p_open',[]], 56 ['cdata',["\n".'Foo ']], 57 ['p_close',[]], 58 ['code',['testing', null, null]], 59 ['p_open',[]], 60 ['cdata',['Bar']], 61 ['p_close',[]], 62 ['document_end',[]], 63 ]; 64 $this->assertCalls($calls, $this->H->calls); 65 } 66 67 function testCodeLang() { 68 $this->P->addMode('code',new Code()); 69 $this->P->parse("Foo <code php>testing</code> Bar"); 70 $calls = [ 71 ['document_start',[]], 72 ['p_open',[]], 73 ['cdata',["\n".'Foo ']], 74 ['p_close',[]], 75 ['code',['testing', 'php', null]], 76 ['p_open',[]], 77 ['cdata',['Bar']], 78 ['p_close',[]], 79 ['document_end',[]], 80 ]; 81 $this->assertCalls($calls, $this->H->calls); 82 } 83 84 function testPreformatted() { 85 $this->P->addMode('preformatted',new Preformatted()); 86 $this->P->parse("F oo\n x \n y \nBar\n"); 87 $calls = [ 88 ['document_start',[]], 89 ['p_open',[]], 90 ['cdata',["\nF oo"]], 91 ['p_close',[]], 92 ['preformatted',["x \n y "]], 93 ['p_open',[]], 94 ['cdata',["\nBar"]], 95 ['p_close',[]], 96 ['document_end',[]], 97 ]; 98 $this->assertCalls($calls, $this->H->calls); 99 } 100 101 function testPreformattedWinEOL() { 102 $this->P->addMode('preformatted',new Preformatted()); 103 $this->P->parse("F oo\r\n x \r\n y \r\nBar\r\n"); 104 $calls = [ 105 ['document_start',[]], 106 ['p_open',[]], 107 ['cdata',["\nF oo"]], 108 ['p_close',[]], 109 ['preformatted',["x \n y "]], 110 ['p_open',[]], 111 ['cdata',["\nBar"]], 112 ['p_close',[]], 113 ['document_end',[]], 114 ]; 115 $this->assertCalls($calls, $this->H->calls); 116 } 117 118 function testPreformattedTab() { 119 $this->P->addMode('preformatted',new Preformatted()); 120 $this->P->parse("F oo\n\tx\t\n\t\ty\t\nBar\n"); 121 $calls = [ 122 ['document_start',[]], 123 ['p_open',[]], 124 ['cdata',["\nF oo"]], 125 ['p_close',[]], 126 ['preformatted',["x\t\n\ty\t"]], 127 ['p_open',[]], 128 ['cdata',["\nBar"]], 129 ['p_close',[]], 130 ['document_end',[]], 131 ]; 132 $this->assertCalls($calls, $this->H->calls); 133 } 134 135 function testPreformattedTabWinEOL() { 136 $this->P->addMode('preformatted',new Preformatted()); 137 $this->P->parse("F oo\r\n\tx\t\r\n\t\ty\t\r\nBar\r\n"); 138 $calls = [ 139 ['document_start',[]], 140 ['p_open',[]], 141 ['cdata',["\nF oo"]], 142 ['p_close',[]], 143 ['preformatted',["x\t\n\ty\t"]], 144 ['p_open',[]], 145 ['cdata',["\nBar"]], 146 ['p_close',[]], 147 ['document_end',[]], 148 ]; 149 $this->assertCalls($calls, $this->H->calls); 150 } 151 152 function testPreformattedList() { 153 // Listblock (sort 10) must be added before Preformatted (sort 20) so 154 // the resulting PCRE alternation matches the canonical mode order. 155 // PCRE picks the first alternative that matches at a given position, 156 // and an indented bullet line like " - x" matches both modes at the 157 // same offset; Listblock has to come first to win the tie. 158 $this->P->addMode('listblock',new Listblock()); 159 $this->P->addMode('preformatted',new Preformatted()); 160 $this->P->parse(" - x \n * y \nF oo\n x \n y \n -X\n *Y\nBar\n"); 161 $calls = [ 162 ['document_start',[]], 163 ['listo_open',[]], 164 ['listitem_open',[1]], 165 ['listcontent_open',[]], 166 ['cdata',[" x "]], 167 ['listcontent_close',[]], 168 ['listitem_close',[]], 169 ['listo_close',[]], 170 ['listu_open',[]], 171 ['listitem_open',[1]], 172 ['listcontent_open',[]], 173 ['cdata',[" y "]], 174 ['listcontent_close',[]], 175 ['listitem_close',[]], 176 ['listu_close',[]], 177 ['p_open',[]], 178 ['cdata',["F oo"]], 179 ['p_close',[]], 180 ['preformatted',["x \n y \n-X\n*Y"]], 181 ['p_open',[]], 182 ['cdata',["\nBar"]], 183 ['p_close',[]], 184 ['document_end',[]], 185 ]; 186 $this->assertCalls($calls, $this->H->calls); 187 } 188 189 190 function testMarkdownPreferredUsesFourSpaces() { 191 // In `md` and `md+dw` settings the indent threshold is 4, 192 // matching GFM's indented code block rule. Lines with only 2-3 193 // leading spaces stay as paragraph text. 194 $this->setSyntax('md'); 195 $this->P->addMode('preformatted', new Preformatted()); 196 $this->P->parse("F oo\n x \n y \nBar\n"); 197 $calls = [ 198 ['document_start', []], 199 ['p_open', []], 200 ['cdata', ["\nF oo"]], 201 ['p_close', []], 202 ['preformatted', ["x \n y "]], 203 ['p_open', []], 204 ['cdata', ["\nBar"]], 205 ['p_close', []], 206 ['document_end', []], 207 ]; 208 $this->assertCalls($calls, $this->H->calls); 209 } 210 211 function testMarkdownPreferredRejectsTwoSpaces() { 212 // 2-space indent in MD-preferred mode does NOT trigger preformatted. 213 $this->setSyntax('md'); 214 $this->P->addMode('preformatted', new Preformatted()); 215 $this->P->parse("F oo\n x\nBar\n"); 216 $modes = array_column($this->H->calls, 0); 217 $this->assertNotContains('preformatted', $modes, 218 '2-space indent must not trigger preformatted when Markdown is preferred'); 219 } 220 221 function testMarkdownPreferredTabStillTriggers() { 222 // Tab is a trigger regardless of the space threshold. 223 $this->setSyntax('md'); 224 $this->P->addMode('preformatted', new Preformatted()); 225 $this->P->parse("F oo\n\tx\nBar\n"); 226 $modes = array_column($this->H->calls, 0); 227 $this->assertContains('preformatted', $modes, 228 'A single tab must still trigger preformatted in MD-preferred mode'); 229 } 230 231 function testStripsLeadingAndTrailingBlankIndentedLines() { 232 // GFM example #87: leading and trailing blank-but-indented lines 233 // should not appear in the preformatted body. The lexer's 234 // continuation pattern eats their indents, leaving padding `\n` 235 // runs in the rewriter buffer; the rewriter trims them so the 236 // emitted text starts and ends on a non-blank line. 237 $this->setSyntax('md'); 238 $this->P->addMode('preformatted', new Preformatted()); 239 $this->P->parse("\n \n foo\n \n\n"); 240 $calls = [ 241 ['document_start', []], 242 ['preformatted', ['foo']], 243 ['document_end', []], 244 ]; 245 $this->assertCalls($calls, $this->H->calls); 246 } 247 248 function testWhitespaceOnlyBlockIsSkipped() { 249 // A run of only blank-but-indented lines must not emit a 250 // preformatted call at all - the body would be pure whitespace 251 // and visually meaningless. 252 $this->setSyntax('md'); 253 $this->P->addMode('preformatted', new Preformatted()); 254 $this->P->parse("\n \n \n\n"); 255 $modes = array_column($this->H->calls, 0); 256 $this->assertNotContains('preformatted', $modes); 257 } 258 259 function testPreformattedPlusHeaderAndEol() { 260 // Note that EOL must come after preformatted! 261 $this->P->addMode('preformatted',new Preformatted()); 262 $this->P->addMode('header',new Header()); 263 $this->P->addMode('eol',new Eol()); 264 $this->P->parse("F oo\n ==Test==\n y \nBar\n"); 265 $calls = [ 266 ['document_start',[]], 267 ['p_open',[]], 268 ['cdata',["F oo"]], 269 ['p_close',[]], 270 ['preformatted',["==Test==\n y "]], 271 ['p_open',[]], 272 ['cdata',['Bar']], 273 ['p_close',[]], 274 ['document_end',[]], 275 ]; 276 $this->assertCalls($calls, $this->H->calls); 277 } 278} 279