1e89aeebdSAndreas Gohr<?php 2e89aeebdSAndreas Gohr 3e89aeebdSAndreas Gohrnamespace dokuwiki\test\Parsing\ParserMode; 4e89aeebdSAndreas Gohr 5e89aeebdSAndreas Gohruse dokuwiki\Parsing\ModeRegistry; 6e89aeebdSAndreas Gohruse dokuwiki\Parsing\ParserMode\GfmLink; 7e89aeebdSAndreas Gohruse dokuwiki\Parsing\ParserMode\Internallink; 8e89aeebdSAndreas Gohr 9e89aeebdSAndreas Gohr/** 10e89aeebdSAndreas Gohr * Tests for GFM inline links `[text](url)` dispatching to DokuWiki's 11e89aeebdSAndreas Gohr * internal / external / interwiki / email / windowsshare / local link 12e89aeebdSAndreas Gohr * handler instructions. 13e89aeebdSAndreas Gohr */ 14e89aeebdSAndreas Gohrclass GfmLinkTest extends ParserTestBase 15e89aeebdSAndreas Gohr{ 16e89aeebdSAndreas Gohr public function setUp(): void 17e89aeebdSAndreas Gohr { 18e89aeebdSAndreas Gohr parent::setUp(); 19e89aeebdSAndreas Gohr global $conf; 2013a62f81SAndreas Gohr $conf['syntax'] = 'md'; 21e89aeebdSAndreas Gohr ModeRegistry::reset(); 22e89aeebdSAndreas Gohr } 23e89aeebdSAndreas Gohr 24e89aeebdSAndreas Gohr public function tearDown(): void 25e89aeebdSAndreas Gohr { 26e89aeebdSAndreas Gohr ModeRegistry::reset(); 27e89aeebdSAndreas Gohr parent::tearDown(); 28e89aeebdSAndreas Gohr } 29e89aeebdSAndreas Gohr 30e89aeebdSAndreas Gohr function testInternalPage() 31e89aeebdSAndreas Gohr { 32e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 33e89aeebdSAndreas Gohr $this->P->parse('Foo [text](page) Bar'); 34e89aeebdSAndreas Gohr $calls = [ 35e89aeebdSAndreas Gohr ['document_start', []], 36e89aeebdSAndreas Gohr ['p_open', []], 37e89aeebdSAndreas Gohr ['cdata', ["\nFoo "]], 38e89aeebdSAndreas Gohr ['internallink', ['page', 'text']], 39e89aeebdSAndreas Gohr ['cdata', [' Bar']], 40e89aeebdSAndreas Gohr ['p_close', []], 41e89aeebdSAndreas Gohr ['document_end', []], 42e89aeebdSAndreas Gohr ]; 43e89aeebdSAndreas Gohr $this->assertCalls($calls, $this->H->calls); 44e89aeebdSAndreas Gohr } 45e89aeebdSAndreas Gohr 46e89aeebdSAndreas Gohr function testInternalPageWithNamespace() 47e89aeebdSAndreas Gohr { 48e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 49e89aeebdSAndreas Gohr $this->P->parse('Foo [Syntax](wiki:syntax#internal) Bar'); 50e89aeebdSAndreas Gohr $calls = [ 51e89aeebdSAndreas Gohr ['document_start', []], 52e89aeebdSAndreas Gohr ['p_open', []], 53e89aeebdSAndreas Gohr ['cdata', ["\nFoo "]], 54e89aeebdSAndreas Gohr ['internallink', ['wiki:syntax#internal', 'Syntax']], 55e89aeebdSAndreas Gohr ['cdata', [' Bar']], 56e89aeebdSAndreas Gohr ['p_close', []], 57e89aeebdSAndreas Gohr ['document_end', []], 58e89aeebdSAndreas Gohr ]; 59e89aeebdSAndreas Gohr $this->assertCalls($calls, $this->H->calls); 60e89aeebdSAndreas Gohr } 61e89aeebdSAndreas Gohr 62e89aeebdSAndreas Gohr function testExternalLink() 63e89aeebdSAndreas Gohr { 64e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 65e89aeebdSAndreas Gohr $this->P->parse('Foo [Google](http://google.com) Bar'); 66e89aeebdSAndreas Gohr $calls = [ 67e89aeebdSAndreas Gohr ['document_start', []], 68e89aeebdSAndreas Gohr ['p_open', []], 69e89aeebdSAndreas Gohr ['cdata', ["\nFoo "]], 70e89aeebdSAndreas Gohr ['externallink', ['http://google.com', 'Google']], 71e89aeebdSAndreas Gohr ['cdata', [' Bar']], 72e89aeebdSAndreas Gohr ['p_close', []], 73e89aeebdSAndreas Gohr ['document_end', []], 74e89aeebdSAndreas Gohr ]; 75e89aeebdSAndreas Gohr $this->assertCalls($calls, $this->H->calls); 76e89aeebdSAndreas Gohr } 77e89aeebdSAndreas Gohr 78e89aeebdSAndreas Gohr function testInterwikiLink() 79e89aeebdSAndreas Gohr { 80e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 81e89aeebdSAndreas Gohr $this->P->parse('Foo [callbacks](wp>Callback) Bar'); 82e89aeebdSAndreas Gohr $calls = [ 83e89aeebdSAndreas Gohr ['document_start', []], 84e89aeebdSAndreas Gohr ['p_open', []], 85e89aeebdSAndreas Gohr ['cdata', ["\nFoo "]], 86e89aeebdSAndreas Gohr ['interwikilink', ['wp>Callback', 'callbacks', 'wp', 'Callback']], 87e89aeebdSAndreas Gohr ['cdata', [' Bar']], 88e89aeebdSAndreas Gohr ['p_close', []], 89e89aeebdSAndreas Gohr ['document_end', []], 90e89aeebdSAndreas Gohr ]; 91e89aeebdSAndreas Gohr $this->assertCalls($calls, $this->H->calls); 92e89aeebdSAndreas Gohr } 93e89aeebdSAndreas Gohr 94e89aeebdSAndreas Gohr function testInterwikiLinkCaseNormalized() 95e89aeebdSAndreas Gohr { 96e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 97e89aeebdSAndreas Gohr $this->P->parse('Foo [Page](IW>somepage) Bar'); 98e89aeebdSAndreas Gohr $calls = [ 99e89aeebdSAndreas Gohr ['document_start', []], 100e89aeebdSAndreas Gohr ['p_open', []], 101e89aeebdSAndreas Gohr ['cdata', ["\nFoo "]], 102e89aeebdSAndreas Gohr ['interwikilink', ['IW>somepage', 'Page', 'iw', 'somepage']], 103e89aeebdSAndreas Gohr ['cdata', [' Bar']], 104e89aeebdSAndreas Gohr ['p_close', []], 105e89aeebdSAndreas Gohr ['document_end', []], 106e89aeebdSAndreas Gohr ]; 107e89aeebdSAndreas Gohr $this->assertCalls($calls, $this->H->calls); 108e89aeebdSAndreas Gohr } 109e89aeebdSAndreas Gohr 110e89aeebdSAndreas Gohr function testEmailLink() 111e89aeebdSAndreas Gohr { 112e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 113e89aeebdSAndreas Gohr $this->P->parse('Foo [mail](user@example.com) Bar'); 114e89aeebdSAndreas Gohr $calls = [ 115e89aeebdSAndreas Gohr ['document_start', []], 116e89aeebdSAndreas Gohr ['p_open', []], 117e89aeebdSAndreas Gohr ['cdata', ["\nFoo "]], 118e89aeebdSAndreas Gohr ['emaillink', ['user@example.com', 'mail']], 119e89aeebdSAndreas Gohr ['cdata', [' Bar']], 120e89aeebdSAndreas Gohr ['p_close', []], 121e89aeebdSAndreas Gohr ['document_end', []], 122e89aeebdSAndreas Gohr ]; 123e89aeebdSAndreas Gohr $this->assertCalls($calls, $this->H->calls); 124e89aeebdSAndreas Gohr } 125e89aeebdSAndreas Gohr 126e89aeebdSAndreas Gohr function testLocalAnchor() 127e89aeebdSAndreas Gohr { 128e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 129e89aeebdSAndreas Gohr $this->P->parse('Foo [section](#anchor) Bar'); 130e89aeebdSAndreas Gohr $calls = [ 131e89aeebdSAndreas Gohr ['document_start', []], 132e89aeebdSAndreas Gohr ['p_open', []], 133e89aeebdSAndreas Gohr ['cdata', ["\nFoo "]], 134e89aeebdSAndreas Gohr ['locallink', ['anchor', 'section']], 135e89aeebdSAndreas Gohr ['cdata', [' Bar']], 136e89aeebdSAndreas Gohr ['p_close', []], 137e89aeebdSAndreas Gohr ['document_end', []], 138e89aeebdSAndreas Gohr ]; 139e89aeebdSAndreas Gohr $this->assertCalls($calls, $this->H->calls); 140e89aeebdSAndreas Gohr } 141e89aeebdSAndreas Gohr 142e89aeebdSAndreas Gohr function testWindowsShare() 143e89aeebdSAndreas Gohr { 144e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 145e89aeebdSAndreas Gohr $this->P->parse('Foo [share](\\\\server\\share) Bar'); 146e89aeebdSAndreas Gohr $calls = [ 147e89aeebdSAndreas Gohr ['document_start', []], 148e89aeebdSAndreas Gohr ['p_open', []], 149e89aeebdSAndreas Gohr ['cdata', ["\nFoo "]], 150e89aeebdSAndreas Gohr ['windowssharelink', ['\\\\server\\share', 'share']], 151e89aeebdSAndreas Gohr ['cdata', [' Bar']], 152e89aeebdSAndreas Gohr ['p_close', []], 153e89aeebdSAndreas Gohr ['document_end', []], 154e89aeebdSAndreas Gohr ]; 155e89aeebdSAndreas Gohr $this->assertCalls($calls, $this->H->calls); 156e89aeebdSAndreas Gohr } 157e89aeebdSAndreas Gohr 158e89aeebdSAndreas Gohr function testTitleInDoubleQuotesIsDiscarded() 159e89aeebdSAndreas Gohr { 160e89aeebdSAndreas Gohr // GFM allows [text](url "title") but DokuWiki's link handler 161e89aeebdSAndreas Gohr // instructions have no title-attribute slot. The title parses 162e89aeebdSAndreas Gohr // cleanly but is dropped; the resulting handler call is identical 163e89aeebdSAndreas Gohr // to the no-title case. 164e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 165e89aeebdSAndreas Gohr $this->P->parse('Foo [Google](http://google.com "Search engine") Bar'); 166e89aeebdSAndreas Gohr $calls = [ 167e89aeebdSAndreas Gohr ['document_start', []], 168e89aeebdSAndreas Gohr ['p_open', []], 169e89aeebdSAndreas Gohr ['cdata', ["\nFoo "]], 170e89aeebdSAndreas Gohr ['externallink', ['http://google.com', 'Google']], 171e89aeebdSAndreas Gohr ['cdata', [' Bar']], 172e89aeebdSAndreas Gohr ['p_close', []], 173e89aeebdSAndreas Gohr ['document_end', []], 174e89aeebdSAndreas Gohr ]; 175e89aeebdSAndreas Gohr $this->assertCalls($calls, $this->H->calls); 176e89aeebdSAndreas Gohr } 177e89aeebdSAndreas Gohr 178e89aeebdSAndreas Gohr function testTitleInSingleQuotesIsDiscarded() 179e89aeebdSAndreas Gohr { 180e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 181e89aeebdSAndreas Gohr $this->P->parse("Foo [page](target 'a title') Bar"); 182e89aeebdSAndreas Gohr $calls = [ 183e89aeebdSAndreas Gohr ['document_start', []], 184e89aeebdSAndreas Gohr ['p_open', []], 185e89aeebdSAndreas Gohr ['cdata', ["\nFoo "]], 186e89aeebdSAndreas Gohr ['internallink', ['target', 'page']], 187e89aeebdSAndreas Gohr ['cdata', [' Bar']], 188e89aeebdSAndreas Gohr ['p_close', []], 189e89aeebdSAndreas Gohr ['document_end', []], 190e89aeebdSAndreas Gohr ]; 191e89aeebdSAndreas Gohr $this->assertCalls($calls, $this->H->calls); 192e89aeebdSAndreas Gohr } 193e89aeebdSAndreas Gohr 194e89aeebdSAndreas Gohr function testSpaceBetweenBracketsAndParensIsNotALink() 195e89aeebdSAndreas Gohr { 196e89aeebdSAndreas Gohr // GFM explicitly forbids whitespace between `]` and `(`. 197e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 198e89aeebdSAndreas Gohr $this->P->parse('[foo] (bar)'); 199e89aeebdSAndreas Gohr $modes = array_column($this->H->calls, 0); 200e89aeebdSAndreas Gohr $this->assertNotContains('internallink', $modes); 201e89aeebdSAndreas Gohr $this->assertNotContains('externallink', $modes); 202e89aeebdSAndreas Gohr } 203e89aeebdSAndreas Gohr 204e89aeebdSAndreas Gohr function testDwDoubleBracketNotConsumedByGfmLink() 205e89aeebdSAndreas Gohr { 206e89aeebdSAndreas Gohr // With both gfm_link and DW internallink loaded (mixed syntax), 207e89aeebdSAndreas Gohr // `[[foo]]` must go to Internallink. GfmLink's `\[(?!\[)` guard 208e89aeebdSAndreas Gohr // refuses single-bracket matches that are actually part of `[[`. 209e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 210e89aeebdSAndreas Gohr $this->P->addMode('internallink', new Internallink()); 211e89aeebdSAndreas Gohr $this->P->parse('Foo [[bar]] Baz'); 212e89aeebdSAndreas Gohr $calls = [ 213e89aeebdSAndreas Gohr ['document_start', []], 214e89aeebdSAndreas Gohr ['p_open', []], 215e89aeebdSAndreas Gohr ['cdata', ["\nFoo "]], 216e89aeebdSAndreas Gohr ['internallink', ['bar', null]], 217e89aeebdSAndreas Gohr ['cdata', [' Baz']], 218e89aeebdSAndreas Gohr ['p_close', []], 219e89aeebdSAndreas Gohr ['document_end', []], 220e89aeebdSAndreas Gohr ]; 221e89aeebdSAndreas Gohr $this->assertCalls($calls, $this->H->calls); 222e89aeebdSAndreas Gohr } 223e89aeebdSAndreas Gohr 224e89aeebdSAndreas Gohr function testMultibyteLinkText() 225e89aeebdSAndreas Gohr { 226e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 227e89aeebdSAndreas Gohr $this->P->parse('Foo [日本語](page) Bar'); 228e89aeebdSAndreas Gohr $calls = [ 229e89aeebdSAndreas Gohr ['document_start', []], 230e89aeebdSAndreas Gohr ['p_open', []], 231e89aeebdSAndreas Gohr ['cdata', ["\nFoo "]], 232e89aeebdSAndreas Gohr ['internallink', ['page', '日本語']], 233e89aeebdSAndreas Gohr ['cdata', [' Bar']], 234e89aeebdSAndreas Gohr ['p_close', []], 235e89aeebdSAndreas Gohr ['document_end', []], 236e89aeebdSAndreas Gohr ]; 237e89aeebdSAndreas Gohr $this->assertCalls($calls, $this->H->calls); 238e89aeebdSAndreas Gohr } 239e89aeebdSAndreas Gohr 240e89aeebdSAndreas Gohr function testReferenceStyleLinkNotMatched() 241e89aeebdSAndreas Gohr { 242e89aeebdSAndreas Gohr // `[foo][bar]` (reference-style) requires a reference definition 243e89aeebdSAndreas Gohr // we do not support; each `[...]` should stay literal text. 244e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 245e89aeebdSAndreas Gohr $this->P->parse('[foo][bar]'); 246e89aeebdSAndreas Gohr $modes = array_column($this->H->calls, 0); 247e89aeebdSAndreas Gohr $this->assertNotContains('internallink', $modes); 248e89aeebdSAndreas Gohr $this->assertNotContains('externallink', $modes); 249e89aeebdSAndreas Gohr } 250e89aeebdSAndreas Gohr 251e89aeebdSAndreas Gohr function testTwoLinksInOneLine() 252e89aeebdSAndreas Gohr { 253e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 254e89aeebdSAndreas Gohr $this->P->parse('Foo [one](a) and [two](b) Bar'); 255e89aeebdSAndreas Gohr $calls = [ 256e89aeebdSAndreas Gohr ['document_start', []], 257e89aeebdSAndreas Gohr ['p_open', []], 258e89aeebdSAndreas Gohr ['cdata', ["\nFoo "]], 259e89aeebdSAndreas Gohr ['internallink', ['a', 'one']], 260e89aeebdSAndreas Gohr ['cdata', [' and ']], 261e89aeebdSAndreas Gohr ['internallink', ['b', 'two']], 262e89aeebdSAndreas Gohr ['cdata', [' Bar']], 263e89aeebdSAndreas Gohr ['p_close', []], 264e89aeebdSAndreas Gohr ['document_end', []], 265e89aeebdSAndreas Gohr ]; 266e89aeebdSAndreas Gohr $this->assertCalls($calls, $this->H->calls); 267e89aeebdSAndreas Gohr } 268e89aeebdSAndreas Gohr 269e89aeebdSAndreas Gohr function testFragmentInExternalUrl() 270e89aeebdSAndreas Gohr { 271e89aeebdSAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 272e89aeebdSAndreas Gohr $this->P->parse('Foo [x](http://example.com#fragment) Bar'); 273e89aeebdSAndreas Gohr $calls = [ 274e89aeebdSAndreas Gohr ['document_start', []], 275e89aeebdSAndreas Gohr ['p_open', []], 276e89aeebdSAndreas Gohr ['cdata', ["\nFoo "]], 277e89aeebdSAndreas Gohr ['externallink', ['http://example.com#fragment', 'x']], 278e89aeebdSAndreas Gohr ['cdata', [' Bar']], 279e89aeebdSAndreas Gohr ['p_close', []], 280e89aeebdSAndreas Gohr ['document_end', []], 281e89aeebdSAndreas Gohr ]; 282e89aeebdSAndreas Gohr $this->assertCalls($calls, $this->H->calls); 283e89aeebdSAndreas Gohr } 284e89aeebdSAndreas Gohr 2853440a8c0SAndreas Gohr // ----- image-as-label (`[](target)`) ----- 2863440a8c0SAndreas Gohr 2873440a8c0SAndreas Gohr /** 2883440a8c0SAndreas Gohr * Media descriptor shape GfmLink emits for image-as-label, matching 2893440a8c0SAndreas Gohr * what Media::parseMedia() returns. 2903440a8c0SAndreas Gohr */ 2913440a8c0SAndreas Gohr private function mediaArray(array $overrides): array 2923440a8c0SAndreas Gohr { 2933440a8c0SAndreas Gohr return array_merge([ 2943440a8c0SAndreas Gohr 'type' => 'internalmedia', 2953440a8c0SAndreas Gohr 'src' => 'wiki:image.png', 2963440a8c0SAndreas Gohr 'title' => 'alt', 2973440a8c0SAndreas Gohr 'align' => null, 2983440a8c0SAndreas Gohr 'width' => null, 2993440a8c0SAndreas Gohr 'height' => null, 3003440a8c0SAndreas Gohr 'cache' => 'cache', 3013440a8c0SAndreas Gohr 'linking' => 'details', 3023440a8c0SAndreas Gohr ], $overrides); 3033440a8c0SAndreas Gohr } 3043440a8c0SAndreas Gohr 3053440a8c0SAndreas Gohr function testImageAsLabelInternalPageLink() 3063440a8c0SAndreas Gohr { 3073440a8c0SAndreas Gohr // The canonical case: image that links to a wiki page. 3083440a8c0SAndreas Gohr // Markdown equivalent of DW's `[[test:link|{{wiki:image.png}}]]`. 3093440a8c0SAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 3103440a8c0SAndreas Gohr $this->P->parse('Foo [](test:link) Bar'); 3113440a8c0SAndreas Gohr $calls = [ 3123440a8c0SAndreas Gohr ['document_start', []], 3133440a8c0SAndreas Gohr ['p_open', []], 3143440a8c0SAndreas Gohr ['cdata', ["\nFoo "]], 3153440a8c0SAndreas Gohr ['internallink', ['test:link', $this->mediaArray([])]], 3163440a8c0SAndreas Gohr ['cdata', [' Bar']], 3173440a8c0SAndreas Gohr ['p_close', []], 3183440a8c0SAndreas Gohr ['document_end', []], 3193440a8c0SAndreas Gohr ]; 3203440a8c0SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 3213440a8c0SAndreas Gohr } 3223440a8c0SAndreas Gohr 3233440a8c0SAndreas Gohr function testImageAsLabelExternalLink() 3243440a8c0SAndreas Gohr { 3253440a8c0SAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 3263440a8c0SAndreas Gohr $this->P->parse('Foo [](http://example.com) Bar'); 3273440a8c0SAndreas Gohr $calls = [ 3283440a8c0SAndreas Gohr ['document_start', []], 3293440a8c0SAndreas Gohr ['p_open', []], 3303440a8c0SAndreas Gohr ['cdata', ["\nFoo "]], 3313440a8c0SAndreas Gohr ['externallink', ['http://example.com', $this->mediaArray([])]], 3323440a8c0SAndreas Gohr ['cdata', [' Bar']], 3333440a8c0SAndreas Gohr ['p_close', []], 3343440a8c0SAndreas Gohr ['document_end', []], 3353440a8c0SAndreas Gohr ]; 3363440a8c0SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 3373440a8c0SAndreas Gohr } 3383440a8c0SAndreas Gohr 3393440a8c0SAndreas Gohr function testImageAsLabelWithExternalMedia() 3403440a8c0SAndreas Gohr { 3413440a8c0SAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 3423440a8c0SAndreas Gohr $this->P->parse('Foo [](test:link) Bar'); 3433440a8c0SAndreas Gohr $calls = [ 3443440a8c0SAndreas Gohr ['document_start', []], 3453440a8c0SAndreas Gohr ['p_open', []], 3463440a8c0SAndreas Gohr ['cdata', ["\nFoo "]], 3473440a8c0SAndreas Gohr ['internallink', ['test:link', $this->mediaArray([ 3483440a8c0SAndreas Gohr 'type' => 'externalmedia', 3493440a8c0SAndreas Gohr 'src' => 'https://example.com/logo.png', 3503440a8c0SAndreas Gohr 'title' => 'logo', 3513440a8c0SAndreas Gohr ])]], 3523440a8c0SAndreas Gohr ['cdata', [' Bar']], 3533440a8c0SAndreas Gohr ['p_close', []], 3543440a8c0SAndreas Gohr ['document_end', []], 3553440a8c0SAndreas Gohr ]; 3563440a8c0SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 3573440a8c0SAndreas Gohr } 3583440a8c0SAndreas Gohr 3593440a8c0SAndreas Gohr function testImageAsLabelInterwikiLink() 3603440a8c0SAndreas Gohr { 3613440a8c0SAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 3623440a8c0SAndreas Gohr $this->P->parse('Foo [](wp>Example) Bar'); 3633440a8c0SAndreas Gohr $calls = [ 3643440a8c0SAndreas Gohr ['document_start', []], 3653440a8c0SAndreas Gohr ['p_open', []], 3663440a8c0SAndreas Gohr ['cdata', ["\nFoo "]], 3673440a8c0SAndreas Gohr ['interwikilink', ['wp>Example', $this->mediaArray([]), 'wp', 'Example']], 3683440a8c0SAndreas Gohr ['cdata', [' Bar']], 3693440a8c0SAndreas Gohr ['p_close', []], 3703440a8c0SAndreas Gohr ['document_end', []], 3713440a8c0SAndreas Gohr ]; 3723440a8c0SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 3733440a8c0SAndreas Gohr } 3743440a8c0SAndreas Gohr 3753440a8c0SAndreas Gohr function testImageAsLabelEmailLink() 3763440a8c0SAndreas Gohr { 3773440a8c0SAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 3783440a8c0SAndreas Gohr $this->P->parse('Foo [](user@example.com) Bar'); 3793440a8c0SAndreas Gohr $calls = [ 3803440a8c0SAndreas Gohr ['document_start', []], 3813440a8c0SAndreas Gohr ['p_open', []], 3823440a8c0SAndreas Gohr ['cdata', ["\nFoo "]], 3833440a8c0SAndreas Gohr ['emaillink', ['user@example.com', $this->mediaArray([])]], 3843440a8c0SAndreas Gohr ['cdata', [' Bar']], 3853440a8c0SAndreas Gohr ['p_close', []], 3863440a8c0SAndreas Gohr ['document_end', []], 3873440a8c0SAndreas Gohr ]; 3883440a8c0SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 3893440a8c0SAndreas Gohr } 3903440a8c0SAndreas Gohr 3913440a8c0SAndreas Gohr function testImageAsLabelMediaParameters() 3923440a8c0SAndreas Gohr { 3933440a8c0SAndreas Gohr // Full DW parameter vocabulary works in the nested image slot. 3943440a8c0SAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 3953440a8c0SAndreas Gohr $this->P->parse('Foo [](test:link) Bar'); 3963440a8c0SAndreas Gohr $calls = [ 3973440a8c0SAndreas Gohr ['document_start', []], 3983440a8c0SAndreas Gohr ['p_open', []], 3993440a8c0SAndreas Gohr ['cdata', ["\nFoo "]], 4003440a8c0SAndreas Gohr ['internallink', ['test:link', $this->mediaArray([ 4013440a8c0SAndreas Gohr 'align' => 'right', 4023440a8c0SAndreas Gohr 'width' => '200', 4033440a8c0SAndreas Gohr 'height' => '100', 4043440a8c0SAndreas Gohr 'linking' => 'nolink', 4053440a8c0SAndreas Gohr ])]], 4063440a8c0SAndreas Gohr ['cdata', [' Bar']], 4073440a8c0SAndreas Gohr ['p_close', []], 4083440a8c0SAndreas Gohr ['document_end', []], 4093440a8c0SAndreas Gohr ]; 4103440a8c0SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 4113440a8c0SAndreas Gohr } 4123440a8c0SAndreas Gohr 4133440a8c0SAndreas Gohr function testImageAsLabelEmptyAlt() 4143440a8c0SAndreas Gohr { 4153440a8c0SAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 4163440a8c0SAndreas Gohr $this->P->parse('Foo [](test:link) Bar'); 4173440a8c0SAndreas Gohr $calls = [ 4183440a8c0SAndreas Gohr ['document_start', []], 4193440a8c0SAndreas Gohr ['p_open', []], 4203440a8c0SAndreas Gohr ['cdata', ["\nFoo "]], 4213440a8c0SAndreas Gohr ['internallink', ['test:link', $this->mediaArray(['title' => null])]], 4223440a8c0SAndreas Gohr ['cdata', [' Bar']], 4233440a8c0SAndreas Gohr ['p_close', []], 4243440a8c0SAndreas Gohr ['document_end', []], 4253440a8c0SAndreas Gohr ]; 4263440a8c0SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 4273440a8c0SAndreas Gohr } 4283440a8c0SAndreas Gohr 4293440a8c0SAndreas Gohr function testImageAsLabelBothTitlesDiscarded() 4303440a8c0SAndreas Gohr { 4313440a8c0SAndreas Gohr // Titles on both URLs parse cleanly but are dropped — neither 4323440a8c0SAndreas Gohr // DW's media nor link instructions have a title-attribute slot. 4333440a8c0SAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 4343440a8c0SAndreas Gohr $this->P->parse('Foo [](test:link "link title") Bar'); 4353440a8c0SAndreas Gohr $calls = [ 4363440a8c0SAndreas Gohr ['document_start', []], 4373440a8c0SAndreas Gohr ['p_open', []], 4383440a8c0SAndreas Gohr ['cdata', ["\nFoo "]], 4393440a8c0SAndreas Gohr ['internallink', ['test:link', $this->mediaArray([])]], 4403440a8c0SAndreas Gohr ['cdata', [' Bar']], 4413440a8c0SAndreas Gohr ['p_close', []], 4423440a8c0SAndreas Gohr ['document_end', []], 4433440a8c0SAndreas Gohr ]; 4443440a8c0SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 4453440a8c0SAndreas Gohr } 4463440a8c0SAndreas Gohr 44774031e46SAndreas Gohr // ----- backslash-escape interaction (GFM §6.1) ----- 44874031e46SAndreas Gohr 44974031e46SAndreas Gohr function testBackslashEscapesInLabel() 45074031e46SAndreas Gohr { 45174031e46SAndreas Gohr // Plain-text label gets §6.1 unescape applied before it reaches 45274031e46SAndreas Gohr // the link handler — `\*` collapses to a literal `*`. 45374031e46SAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 45474031e46SAndreas Gohr $this->P->parse('Foo [te\\*xt](page) Bar'); 45574031e46SAndreas Gohr $calls = [ 45674031e46SAndreas Gohr ['document_start', []], 45774031e46SAndreas Gohr ['p_open', []], 45874031e46SAndreas Gohr ['cdata', ["\nFoo "]], 45974031e46SAndreas Gohr ['internallink', ['page', 'te*xt']], 46074031e46SAndreas Gohr ['cdata', [' Bar']], 46174031e46SAndreas Gohr ['p_close', []], 46274031e46SAndreas Gohr ['document_end', []], 46374031e46SAndreas Gohr ]; 46474031e46SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 46574031e46SAndreas Gohr } 46674031e46SAndreas Gohr 467*0f694376SAndreas Gohr function testBackslashEscapedBracketInLabel() 468*0f694376SAndreas Gohr { 469*0f694376SAndreas Gohr // Spec example #523: an escaped `[` inside the label is allowed 470*0f694376SAndreas Gohr // and unescapes to a literal bracket. The label class accepts 471*0f694376SAndreas Gohr // `\[` / `\]` so the outer match still finds its `]` close. 472*0f694376SAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 473*0f694376SAndreas Gohr $this->P->parse('Foo [link \\[bar](page) Baz'); 474*0f694376SAndreas Gohr $calls = [ 475*0f694376SAndreas Gohr ['document_start', []], 476*0f694376SAndreas Gohr ['p_open', []], 477*0f694376SAndreas Gohr ['cdata', ["\nFoo "]], 478*0f694376SAndreas Gohr ['internallink', ['page', 'link [bar']], 479*0f694376SAndreas Gohr ['cdata', [' Baz']], 480*0f694376SAndreas Gohr ['p_close', []], 481*0f694376SAndreas Gohr ['document_end', []], 482*0f694376SAndreas Gohr ]; 483*0f694376SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 484*0f694376SAndreas Gohr } 485*0f694376SAndreas Gohr 486*0f694376SAndreas Gohr function testBackslashEscapedClosingBracketInLabel() 487*0f694376SAndreas Gohr { 488*0f694376SAndreas Gohr // The `\]` form is symmetric with `\[`. Both must be accepted by 489*0f694376SAndreas Gohr // the label class without ending the outer match early. 490*0f694376SAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 491*0f694376SAndreas Gohr $this->P->parse('Foo [a\\]b](page) Bar'); 492*0f694376SAndreas Gohr $calls = [ 493*0f694376SAndreas Gohr ['document_start', []], 494*0f694376SAndreas Gohr ['p_open', []], 495*0f694376SAndreas Gohr ['cdata', ["\nFoo "]], 496*0f694376SAndreas Gohr ['internallink', ['page', 'a]b']], 497*0f694376SAndreas Gohr ['cdata', [' Bar']], 498*0f694376SAndreas Gohr ['p_close', []], 499*0f694376SAndreas Gohr ['document_end', []], 500*0f694376SAndreas Gohr ]; 501*0f694376SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 502*0f694376SAndreas Gohr } 503*0f694376SAndreas Gohr 50474031e46SAndreas Gohr function testBackslashEscapesInUrl() 50574031e46SAndreas Gohr { 50674031e46SAndreas Gohr // §6.1 unescape fires on the URL after classify() picks the 50774031e46SAndreas Gohr // handler — it lets users put a literal punctuation char in a 50874031e46SAndreas Gohr // URL slot that would otherwise carry markup meaning. 50974031e46SAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 51074031e46SAndreas Gohr $this->P->parse('Foo [text](http://example.com/pa\\!ge) Bar'); 51174031e46SAndreas Gohr $calls = [ 51274031e46SAndreas Gohr ['document_start', []], 51374031e46SAndreas Gohr ['p_open', []], 51474031e46SAndreas Gohr ['cdata', ["\nFoo "]], 51574031e46SAndreas Gohr ['externallink', ['http://example.com/pa!ge', 'text']], 51674031e46SAndreas Gohr ['cdata', [' Bar']], 51774031e46SAndreas Gohr ['p_close', []], 51874031e46SAndreas Gohr ['document_end', []], 51974031e46SAndreas Gohr ]; 52074031e46SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 52174031e46SAndreas Gohr } 52274031e46SAndreas Gohr 52374031e46SAndreas Gohr function testWindowsShareUrlSkipsBackslashUnescape() 52474031e46SAndreas Gohr { 52574031e46SAndreas Gohr // Carve-out: a `\\host\path` URL must survive classify() and 52674031e46SAndreas Gohr // stay intact as a windowssharelink. Applying §6.1 unescape 52774031e46SAndreas Gohr // would collapse the leading `\\` to `\` and destroy the share 52874031e46SAndreas Gohr // marker, so the unescape pass is skipped for this classifier. 52974031e46SAndreas Gohr $this->P->addMode('gfm_link', new GfmLink()); 53074031e46SAndreas Gohr $this->P->parse('Foo [share](\\\\server\\share\\sub) Bar'); 53174031e46SAndreas Gohr $calls = [ 53274031e46SAndreas Gohr ['document_start', []], 53374031e46SAndreas Gohr ['p_open', []], 53474031e46SAndreas Gohr ['cdata', ["\nFoo "]], 53574031e46SAndreas Gohr ['windowssharelink', ['\\\\server\\share\\sub', 'share']], 53674031e46SAndreas Gohr ['cdata', [' Bar']], 53774031e46SAndreas Gohr ['p_close', []], 53874031e46SAndreas Gohr ['document_end', []], 53974031e46SAndreas Gohr ]; 54074031e46SAndreas Gohr $this->assertCalls($calls, $this->H->calls); 54174031e46SAndreas Gohr } 54274031e46SAndreas Gohr 543e89aeebdSAndreas Gohr function testSortValue() 544e89aeebdSAndreas Gohr { 545e89aeebdSAndreas Gohr $this->assertSame(300, (new GfmLink())->getSort()); 546e89aeebdSAndreas Gohr } 547e89aeebdSAndreas Gohr} 548