1<?php 2 3namespace dokuwiki\test\Parsing\ParserMode; 4 5use dokuwiki\Parsing\ModeRegistry; 6use dokuwiki\Parsing\ParserMode\GfmLink; 7use dokuwiki\Parsing\ParserMode\GfmMedia; 8 9/** 10 * Tests for GFM inline image syntax `` dispatching to DokuWiki's 11 * internalmedia / externalmedia handler instructions. 12 */ 13class GfmMediaTest extends ParserTestBase 14{ 15 public function setUp(): void 16 { 17 parent::setUp(); 18 global $conf; 19 $conf['syntax'] = 'markdown'; 20 ModeRegistry::reset(); 21 } 22 23 public function tearDown(): void 24 { 25 ModeRegistry::reset(); 26 parent::tearDown(); 27 } 28 29 function testInternalMedia() 30 { 31 $this->P->addMode('gfm_media', new GfmMedia()); 32 $this->P->parse('Foo  Bar'); 33 $calls = [ 34 ['document_start', []], 35 ['p_open', []], 36 ['cdata', ["\nFoo "]], 37 ['internalmedia', ['wiki:image.png', 'alt', null, null, null, 'cache', 'details']], 38 ['cdata', [' Bar']], 39 ['p_close', []], 40 ['document_end', []], 41 ]; 42 $this->assertCalls($calls, $this->H->calls); 43 } 44 45 function testExternalMediaHttps() 46 { 47 $this->P->addMode('gfm_media', new GfmMedia()); 48 $this->P->parse('Foo  Bar'); 49 $calls = [ 50 ['document_start', []], 51 ['p_open', []], 52 ['cdata', ["\nFoo "]], 53 ['externalmedia', ['https://example.com/img.png', 'logo', null, null, null, 'cache', 'details']], 54 ['cdata', [' Bar']], 55 ['p_close', []], 56 ['document_end', []], 57 ]; 58 $this->assertCalls($calls, $this->H->calls); 59 } 60 61 function testExternalMediaInterwiki() 62 { 63 $this->P->addMode('gfm_media', new GfmMedia()); 64 $this->P->parse('Foo  Bar'); 65 $calls = [ 66 ['document_start', []], 67 ['p_open', []], 68 ['cdata', ["\nFoo "]], 69 ['externalmedia', ['wp>Example.png', 'foo', null, null, null, 'cache', 'details']], 70 ['cdata', [' Bar']], 71 ['p_close', []], 72 ['document_end', []], 73 ]; 74 $this->assertCalls($calls, $this->H->calls); 75 } 76 77 function testEmptyAlt() 78 { 79 // GFM allows `` with empty alt; we pass null in the caption 80 // slot to match how DW's Media mode emits no-caption media. 81 $this->P->addMode('gfm_media', new GfmMedia()); 82 $this->P->parse('Foo  Bar'); 83 $calls = [ 84 ['document_start', []], 85 ['p_open', []], 86 ['cdata', ["\nFoo "]], 87 ['internalmedia', ['image.png', null, null, null, null, 'cache', 'details']], 88 ['cdata', [' Bar']], 89 ['p_close', []], 90 ['document_end', []], 91 ]; 92 $this->assertCalls($calls, $this->H->calls); 93 } 94 95 function testTitleInDoubleQuotesIsDiscarded() 96 { 97 // GFM allows  but DokuWiki's media handler has 98 // no separate title slot (alt doubles as caption). The title parses 99 // cleanly but is dropped. 100 $this->P->addMode('gfm_media', new GfmMedia()); 101 $this->P->parse('Foo  Bar'); 102 $calls = [ 103 ['document_start', []], 104 ['p_open', []], 105 ['cdata', ["\nFoo "]], 106 ['internalmedia', ['wiki:image.png', 'alt', null, null, null, 'cache', 'details']], 107 ['cdata', [' Bar']], 108 ['p_close', []], 109 ['document_end', []], 110 ]; 111 $this->assertCalls($calls, $this->H->calls); 112 } 113 114 function testTitleInSingleQuotesIsDiscarded() 115 { 116 $this->P->addMode('gfm_media', new GfmMedia()); 117 $this->P->parse("Foo  Bar"); 118 $calls = [ 119 ['document_start', []], 120 ['p_open', []], 121 ['cdata', ["\nFoo "]], 122 ['internalmedia', ['wiki:image.png', 'alt', null, null, null, 'cache', 'details']], 123 ['cdata', [' Bar']], 124 ['p_close', []], 125 ['document_end', []], 126 ]; 127 $this->assertCalls($calls, $this->H->calls); 128 } 129 130 function testMultibyteAlt() 131 { 132 $this->P->addMode('gfm_media', new GfmMedia()); 133 $this->P->parse('Foo  Bar'); 134 $calls = [ 135 ['document_start', []], 136 ['p_open', []], 137 ['cdata', ["\nFoo "]], 138 ['internalmedia', ['pic.png', '日本語', null, null, null, 'cache', 'details']], 139 ['cdata', [' Bar']], 140 ['p_close', []], 141 ['document_end', []], 142 ]; 143 $this->assertCalls($calls, $this->H->calls); 144 } 145 146 function testPlainLinkNotImage() 147 { 148 // With both gfm_media and gfm_link loaded, `[text](url)` (no leading 149 // `!`) must dispatch through gfm_link, not gfm_media. 150 $this->P->addMode('gfm_media', new GfmMedia()); 151 $this->P->addMode('gfm_link', new GfmLink()); 152 $this->P->parse('Foo [text](page) Bar'); 153 $modes = array_column($this->H->calls, 0); 154 $this->assertContains('internallink', $modes); 155 $this->assertNotContains('internalmedia', $modes); 156 } 157 158 function testBangAloneIsNotImage() 159 { 160 $this->P->addMode('gfm_media', new GfmMedia()); 161 $this->P->parse('Foo ![alt] Bar'); 162 $modes = array_column($this->H->calls, 0); 163 $this->assertNotContains('internalmedia', $modes); 164 $this->assertNotContains('externalmedia', $modes); 165 } 166 167 function testSpaceBetweenBracketsAndParensIsNotAnImage() 168 { 169 $this->P->addMode('gfm_media', new GfmMedia()); 170 $this->P->parse('![foo] (bar)'); 171 $modes = array_column($this->H->calls, 0); 172 $this->assertNotContains('internalmedia', $modes); 173 $this->assertNotContains('externalmedia', $modes); 174 } 175 176 function testReferenceStyleImageNotMatched() 177 { 178 $this->P->addMode('gfm_media', new GfmMedia()); 179 $this->P->parse('![foo][bar]'); 180 $modes = array_column($this->H->calls, 0); 181 $this->assertNotContains('internalmedia', $modes); 182 $this->assertNotContains('externalmedia', $modes); 183 } 184 185 function testTwoImagesInOneLine() 186 { 187 $this->P->addMode('gfm_media', new GfmMedia()); 188 $this->P->parse('Foo  and  Bar'); 189 $calls = [ 190 ['document_start', []], 191 ['p_open', []], 192 ['cdata', ["\nFoo "]], 193 ['internalmedia', ['a.png', 'one', null, null, null, 'cache', 'details']], 194 ['cdata', [' and ']], 195 ['internalmedia', ['b.png', 'two', null, null, null, 'cache', 'details']], 196 ['cdata', [' Bar']], 197 ['p_close', []], 198 ['document_end', []], 199 ]; 200 $this->assertCalls($calls, $this->H->calls); 201 } 202 203 function testWidthOnlyParameter() 204 { 205 $this->P->addMode('gfm_media', new GfmMedia()); 206 $this->P->parse('Foo  Bar'); 207 $calls = [ 208 ['document_start', []], 209 ['p_open', []], 210 ['cdata', ["\nFoo "]], 211 ['internalmedia', ['wiki:image.png', 'alt', null, '200', null, 'cache', 'details']], 212 ['cdata', [' Bar']], 213 ['p_close', []], 214 ['document_end', []], 215 ]; 216 $this->assertCalls($calls, $this->H->calls); 217 } 218 219 function testWidthAndHeightParameter() 220 { 221 $this->P->addMode('gfm_media', new GfmMedia()); 222 $this->P->parse('Foo  Bar'); 223 $calls = [ 224 ['document_start', []], 225 ['p_open', []], 226 ['cdata', ["\nFoo "]], 227 ['internalmedia', ['wiki:image.png', 'alt', null, '200', '100', 'cache', 'details']], 228 ['cdata', [' Bar']], 229 ['p_close', []], 230 ['document_end', []], 231 ]; 232 $this->assertCalls($calls, $this->H->calls); 233 } 234 235 function testLinkingParameter() 236 { 237 $this->P->addMode('gfm_media', new GfmMedia()); 238 $this->P->parse('Foo  Bar'); 239 $calls = [ 240 ['document_start', []], 241 ['p_open', []], 242 ['cdata', ["\nFoo "]], 243 ['internalmedia', ['wiki:image.png', 'alt', null, null, null, 'cache', 'nolink']], 244 ['cdata', [' Bar']], 245 ['p_close', []], 246 ['document_end', []], 247 ]; 248 $this->assertCalls($calls, $this->H->calls); 249 } 250 251 function testCacheParameter() 252 { 253 $this->P->addMode('gfm_media', new GfmMedia()); 254 $this->P->parse('Foo  Bar'); 255 $calls = [ 256 ['document_start', []], 257 ['p_open', []], 258 ['cdata', ["\nFoo "]], 259 ['internalmedia', ['wiki:image.png', 'alt', null, null, null, 'recache', 'details']], 260 ['cdata', [' Bar']], 261 ['p_close', []], 262 ['document_end', []], 263 ]; 264 $this->assertCalls($calls, $this->H->calls); 265 } 266 267 function testCombinedParameters() 268 { 269 $this->P->addMode('gfm_media', new GfmMedia()); 270 $this->P->parse('Foo  Bar'); 271 $calls = [ 272 ['document_start', []], 273 ['p_open', []], 274 ['cdata', ["\nFoo "]], 275 ['internalmedia', ['wiki:image.png', 'alt', null, '200', '100', 'nocache', 'nolink']], 276 ['cdata', [' Bar']], 277 ['p_close', []], 278 ['document_end', []], 279 ]; 280 $this->assertCalls($calls, $this->H->calls); 281 } 282 283 function testLastQuestionMarkIsTheParameterDelimiter() 284 { 285 // URLs may carry their own query string; DW splits on the *last* `?` 286 // so the URL query survives as part of the src. 287 $this->P->addMode('gfm_media', new GfmMedia()); 288 $this->P->parse('Foo  Bar'); 289 $calls = [ 290 ['document_start', []], 291 ['p_open', []], 292 ['cdata', ["\nFoo "]], 293 ['externalmedia', ['https://example.com/img?v=2', 'alt', null, '200', '100', 'cache', 'details']], 294 ['cdata', [' Bar']], 295 ['p_close', []], 296 ['document_end', []], 297 ]; 298 $this->assertCalls($calls, $this->H->calls); 299 } 300 301 function testAlignRight() 302 { 303 // GFM has no native image-alignment syntax, so GfmMedia borrows 304 // DW's ?right/?left/?center keyword from the shared URL parameter 305 // block — the only way to align an inline GFM image. 306 $this->P->addMode('gfm_media', new GfmMedia()); 307 $this->P->parse('Foo  Bar'); 308 $calls = [ 309 ['document_start', []], 310 ['p_open', []], 311 ['cdata', ["\nFoo "]], 312 ['internalmedia', ['wiki:image.png', 'alt', 'right', null, null, 'cache', 'details']], 313 ['cdata', [' Bar']], 314 ['p_close', []], 315 ['document_end', []], 316 ]; 317 $this->assertCalls($calls, $this->H->calls); 318 } 319 320 function testAlignWithSizeAndLinking() 321 { 322 $this->P->addMode('gfm_media', new GfmMedia()); 323 $this->P->parse('Foo  Bar'); 324 $calls = [ 325 ['document_start', []], 326 ['p_open', []], 327 ['cdata', ["\nFoo "]], 328 ['internalmedia', ['wiki:image.png', 'alt', 'center', '200', '100', 'cache', 'nolink']], 329 ['cdata', [' Bar']], 330 ['p_close', []], 331 ['document_end', []], 332 ]; 333 $this->assertCalls($calls, $this->H->calls); 334 } 335 336 function testSortValue() 337 { 338 $this->assertSame(310, (new GfmMedia())->getSort()); 339 } 340} 341