1<?php 2 3namespace dokuwiki\test\Parsing\ParserMode; 4 5use dokuwiki\Parsing\ModeRegistry; 6use dokuwiki\Parsing\ParserMode\GfmLink; 7use dokuwiki\Parsing\ParserMode\Internallink; 8 9/** 10 * Tests for GFM inline links `[text](url)` dispatching to DokuWiki's 11 * internal / external / interwiki / email / windowsshare / local link 12 * handler instructions. 13 */ 14class GfmLinkTest extends ParserTestBase 15{ 16 public function setUp(): void 17 { 18 parent::setUp(); 19 global $conf; 20 $conf['syntax'] = 'markdown'; 21 ModeRegistry::reset(); 22 } 23 24 public function tearDown(): void 25 { 26 ModeRegistry::reset(); 27 parent::tearDown(); 28 } 29 30 function testInternalPage() 31 { 32 $this->P->addMode('gfm_link', new GfmLink()); 33 $this->P->parse('Foo [text](page) Bar'); 34 $calls = [ 35 ['document_start', []], 36 ['p_open', []], 37 ['cdata', ["\nFoo "]], 38 ['internallink', ['page', 'text']], 39 ['cdata', [' Bar']], 40 ['p_close', []], 41 ['document_end', []], 42 ]; 43 $this->assertCalls($calls, $this->H->calls); 44 } 45 46 function testInternalPageWithNamespace() 47 { 48 $this->P->addMode('gfm_link', new GfmLink()); 49 $this->P->parse('Foo [Syntax](wiki:syntax#internal) Bar'); 50 $calls = [ 51 ['document_start', []], 52 ['p_open', []], 53 ['cdata', ["\nFoo "]], 54 ['internallink', ['wiki:syntax#internal', 'Syntax']], 55 ['cdata', [' Bar']], 56 ['p_close', []], 57 ['document_end', []], 58 ]; 59 $this->assertCalls($calls, $this->H->calls); 60 } 61 62 function testExternalLink() 63 { 64 $this->P->addMode('gfm_link', new GfmLink()); 65 $this->P->parse('Foo [Google](http://google.com) Bar'); 66 $calls = [ 67 ['document_start', []], 68 ['p_open', []], 69 ['cdata', ["\nFoo "]], 70 ['externallink', ['http://google.com', 'Google']], 71 ['cdata', [' Bar']], 72 ['p_close', []], 73 ['document_end', []], 74 ]; 75 $this->assertCalls($calls, $this->H->calls); 76 } 77 78 function testInterwikiLink() 79 { 80 $this->P->addMode('gfm_link', new GfmLink()); 81 $this->P->parse('Foo [callbacks](wp>Callback) Bar'); 82 $calls = [ 83 ['document_start', []], 84 ['p_open', []], 85 ['cdata', ["\nFoo "]], 86 ['interwikilink', ['wp>Callback', 'callbacks', 'wp', 'Callback']], 87 ['cdata', [' Bar']], 88 ['p_close', []], 89 ['document_end', []], 90 ]; 91 $this->assertCalls($calls, $this->H->calls); 92 } 93 94 function testInterwikiLinkCaseNormalized() 95 { 96 $this->P->addMode('gfm_link', new GfmLink()); 97 $this->P->parse('Foo [Page](IW>somepage) Bar'); 98 $calls = [ 99 ['document_start', []], 100 ['p_open', []], 101 ['cdata', ["\nFoo "]], 102 ['interwikilink', ['IW>somepage', 'Page', 'iw', 'somepage']], 103 ['cdata', [' Bar']], 104 ['p_close', []], 105 ['document_end', []], 106 ]; 107 $this->assertCalls($calls, $this->H->calls); 108 } 109 110 function testEmailLink() 111 { 112 $this->P->addMode('gfm_link', new GfmLink()); 113 $this->P->parse('Foo [mail](user@example.com) Bar'); 114 $calls = [ 115 ['document_start', []], 116 ['p_open', []], 117 ['cdata', ["\nFoo "]], 118 ['emaillink', ['user@example.com', 'mail']], 119 ['cdata', [' Bar']], 120 ['p_close', []], 121 ['document_end', []], 122 ]; 123 $this->assertCalls($calls, $this->H->calls); 124 } 125 126 function testLocalAnchor() 127 { 128 $this->P->addMode('gfm_link', new GfmLink()); 129 $this->P->parse('Foo [section](#anchor) Bar'); 130 $calls = [ 131 ['document_start', []], 132 ['p_open', []], 133 ['cdata', ["\nFoo "]], 134 ['locallink', ['anchor', 'section']], 135 ['cdata', [' Bar']], 136 ['p_close', []], 137 ['document_end', []], 138 ]; 139 $this->assertCalls($calls, $this->H->calls); 140 } 141 142 function testWindowsShare() 143 { 144 $this->P->addMode('gfm_link', new GfmLink()); 145 $this->P->parse('Foo [share](\\\\server\\share) Bar'); 146 $calls = [ 147 ['document_start', []], 148 ['p_open', []], 149 ['cdata', ["\nFoo "]], 150 ['windowssharelink', ['\\\\server\\share', 'share']], 151 ['cdata', [' Bar']], 152 ['p_close', []], 153 ['document_end', []], 154 ]; 155 $this->assertCalls($calls, $this->H->calls); 156 } 157 158 function testTitleInDoubleQuotesIsDiscarded() 159 { 160 // GFM allows [text](url "title") but DokuWiki's link handler 161 // instructions have no title-attribute slot. The title parses 162 // cleanly but is dropped; the resulting handler call is identical 163 // to the no-title case. 164 $this->P->addMode('gfm_link', new GfmLink()); 165 $this->P->parse('Foo [Google](http://google.com "Search engine") Bar'); 166 $calls = [ 167 ['document_start', []], 168 ['p_open', []], 169 ['cdata', ["\nFoo "]], 170 ['externallink', ['http://google.com', 'Google']], 171 ['cdata', [' Bar']], 172 ['p_close', []], 173 ['document_end', []], 174 ]; 175 $this->assertCalls($calls, $this->H->calls); 176 } 177 178 function testTitleInSingleQuotesIsDiscarded() 179 { 180 $this->P->addMode('gfm_link', new GfmLink()); 181 $this->P->parse("Foo [page](target 'a title') Bar"); 182 $calls = [ 183 ['document_start', []], 184 ['p_open', []], 185 ['cdata', ["\nFoo "]], 186 ['internallink', ['target', 'page']], 187 ['cdata', [' Bar']], 188 ['p_close', []], 189 ['document_end', []], 190 ]; 191 $this->assertCalls($calls, $this->H->calls); 192 } 193 194 function testSpaceBetweenBracketsAndParensIsNotALink() 195 { 196 // GFM explicitly forbids whitespace between `]` and `(`. 197 $this->P->addMode('gfm_link', new GfmLink()); 198 $this->P->parse('[foo] (bar)'); 199 $modes = array_column($this->H->calls, 0); 200 $this->assertNotContains('internallink', $modes); 201 $this->assertNotContains('externallink', $modes); 202 } 203 204 function testDwDoubleBracketNotConsumedByGfmLink() 205 { 206 // With both gfm_link and DW internallink loaded (mixed syntax), 207 // `[[foo]]` must go to Internallink. GfmLink's `\[(?!\[)` guard 208 // refuses single-bracket matches that are actually part of `[[`. 209 $this->P->addMode('gfm_link', new GfmLink()); 210 $this->P->addMode('internallink', new Internallink()); 211 $this->P->parse('Foo [[bar]] Baz'); 212 $calls = [ 213 ['document_start', []], 214 ['p_open', []], 215 ['cdata', ["\nFoo "]], 216 ['internallink', ['bar', null]], 217 ['cdata', [' Baz']], 218 ['p_close', []], 219 ['document_end', []], 220 ]; 221 $this->assertCalls($calls, $this->H->calls); 222 } 223 224 function testMultibyteLinkText() 225 { 226 $this->P->addMode('gfm_link', new GfmLink()); 227 $this->P->parse('Foo [日本語](page) Bar'); 228 $calls = [ 229 ['document_start', []], 230 ['p_open', []], 231 ['cdata', ["\nFoo "]], 232 ['internallink', ['page', '日本語']], 233 ['cdata', [' Bar']], 234 ['p_close', []], 235 ['document_end', []], 236 ]; 237 $this->assertCalls($calls, $this->H->calls); 238 } 239 240 function testReferenceStyleLinkNotMatched() 241 { 242 // `[foo][bar]` (reference-style) requires a reference definition 243 // we do not support; each `[...]` should stay literal text. 244 $this->P->addMode('gfm_link', new GfmLink()); 245 $this->P->parse('[foo][bar]'); 246 $modes = array_column($this->H->calls, 0); 247 $this->assertNotContains('internallink', $modes); 248 $this->assertNotContains('externallink', $modes); 249 } 250 251 function testTwoLinksInOneLine() 252 { 253 $this->P->addMode('gfm_link', new GfmLink()); 254 $this->P->parse('Foo [one](a) and [two](b) Bar'); 255 $calls = [ 256 ['document_start', []], 257 ['p_open', []], 258 ['cdata', ["\nFoo "]], 259 ['internallink', ['a', 'one']], 260 ['cdata', [' and ']], 261 ['internallink', ['b', 'two']], 262 ['cdata', [' Bar']], 263 ['p_close', []], 264 ['document_end', []], 265 ]; 266 $this->assertCalls($calls, $this->H->calls); 267 } 268 269 function testFragmentInExternalUrl() 270 { 271 $this->P->addMode('gfm_link', new GfmLink()); 272 $this->P->parse('Foo [x](http://example.com#fragment) Bar'); 273 $calls = [ 274 ['document_start', []], 275 ['p_open', []], 276 ['cdata', ["\nFoo "]], 277 ['externallink', ['http://example.com#fragment', 'x']], 278 ['cdata', [' Bar']], 279 ['p_close', []], 280 ['document_end', []], 281 ]; 282 $this->assertCalls($calls, $this->H->calls); 283 } 284 285 function testSortValue() 286 { 287 $this->assertSame(300, (new GfmLink())->getSort()); 288 } 289} 290