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