xref: /dokuwiki/_test/tests/Parsing/ParserMode/GfmLinkTest.php (revision 47a02a102092be9e1e6f1ddaf158bdfffdb13d4f)
1e89aeebdSAndreas Gohr<?php
2e89aeebdSAndreas Gohr
3e89aeebdSAndreas Gohrnamespace dokuwiki\test\Parsing\ParserMode;
4e89aeebdSAndreas Gohr
5e89aeebdSAndreas Gohruse dokuwiki\Parsing\ParserMode\GfmLink;
6e89aeebdSAndreas Gohruse dokuwiki\Parsing\ParserMode\Internallink;
7e89aeebdSAndreas Gohr
8e89aeebdSAndreas Gohr/**
9e89aeebdSAndreas Gohr * Tests for GFM inline links `[text](url)` dispatching to DokuWiki's
10e89aeebdSAndreas Gohr * internal / external / interwiki / email / windowsshare / local link
11e89aeebdSAndreas Gohr * handler instructions.
12e89aeebdSAndreas Gohr */
13e89aeebdSAndreas Gohrclass GfmLinkTest extends ParserTestBase
14e89aeebdSAndreas Gohr{
15e89aeebdSAndreas Gohr    public function setUp(): void
16e89aeebdSAndreas Gohr    {
17e89aeebdSAndreas Gohr        parent::setUp();
18*47a02a10SAndreas Gohr        $this->setSyntax('md');
19e89aeebdSAndreas Gohr    }
20e89aeebdSAndreas Gohr
21e89aeebdSAndreas Gohr    function testInternalPage()
22e89aeebdSAndreas Gohr    {
23e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
24e89aeebdSAndreas Gohr        $this->P->parse('Foo [text](page) Bar');
25e89aeebdSAndreas Gohr        $calls = [
26e89aeebdSAndreas Gohr            ['document_start', []],
27e89aeebdSAndreas Gohr            ['p_open', []],
28e89aeebdSAndreas Gohr            ['cdata', ["\nFoo "]],
29e89aeebdSAndreas Gohr            ['internallink', ['page', 'text']],
30e89aeebdSAndreas Gohr            ['cdata', [' Bar']],
31e89aeebdSAndreas Gohr            ['p_close', []],
32e89aeebdSAndreas Gohr            ['document_end', []],
33e89aeebdSAndreas Gohr        ];
34e89aeebdSAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
35e89aeebdSAndreas Gohr    }
36e89aeebdSAndreas Gohr
37e89aeebdSAndreas Gohr    function testInternalPageWithNamespace()
38e89aeebdSAndreas Gohr    {
39e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
40e89aeebdSAndreas Gohr        $this->P->parse('Foo [Syntax](wiki:syntax#internal) Bar');
41e89aeebdSAndreas Gohr        $calls = [
42e89aeebdSAndreas Gohr            ['document_start', []],
43e89aeebdSAndreas Gohr            ['p_open', []],
44e89aeebdSAndreas Gohr            ['cdata', ["\nFoo "]],
45e89aeebdSAndreas Gohr            ['internallink', ['wiki:syntax#internal', 'Syntax']],
46e89aeebdSAndreas Gohr            ['cdata', [' Bar']],
47e89aeebdSAndreas Gohr            ['p_close', []],
48e89aeebdSAndreas Gohr            ['document_end', []],
49e89aeebdSAndreas Gohr        ];
50e89aeebdSAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
51e89aeebdSAndreas Gohr    }
52e89aeebdSAndreas Gohr
53e89aeebdSAndreas Gohr    function testExternalLink()
54e89aeebdSAndreas Gohr    {
55e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
56e89aeebdSAndreas Gohr        $this->P->parse('Foo [Google](http://google.com) Bar');
57e89aeebdSAndreas Gohr        $calls = [
58e89aeebdSAndreas Gohr            ['document_start', []],
59e89aeebdSAndreas Gohr            ['p_open', []],
60e89aeebdSAndreas Gohr            ['cdata', ["\nFoo "]],
61e89aeebdSAndreas Gohr            ['externallink', ['http://google.com', 'Google']],
62e89aeebdSAndreas Gohr            ['cdata', [' Bar']],
63e89aeebdSAndreas Gohr            ['p_close', []],
64e89aeebdSAndreas Gohr            ['document_end', []],
65e89aeebdSAndreas Gohr        ];
66e89aeebdSAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
67e89aeebdSAndreas Gohr    }
68e89aeebdSAndreas Gohr
69e89aeebdSAndreas Gohr    function testInterwikiLink()
70e89aeebdSAndreas Gohr    {
71e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
72e89aeebdSAndreas Gohr        $this->P->parse('Foo [callbacks](wp>Callback) Bar');
73e89aeebdSAndreas Gohr        $calls = [
74e89aeebdSAndreas Gohr            ['document_start', []],
75e89aeebdSAndreas Gohr            ['p_open', []],
76e89aeebdSAndreas Gohr            ['cdata', ["\nFoo "]],
77e89aeebdSAndreas Gohr            ['interwikilink', ['wp>Callback', 'callbacks', 'wp', 'Callback']],
78e89aeebdSAndreas Gohr            ['cdata', [' Bar']],
79e89aeebdSAndreas Gohr            ['p_close', []],
80e89aeebdSAndreas Gohr            ['document_end', []],
81e89aeebdSAndreas Gohr        ];
82e89aeebdSAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
83e89aeebdSAndreas Gohr    }
84e89aeebdSAndreas Gohr
85e89aeebdSAndreas Gohr    function testInterwikiLinkCaseNormalized()
86e89aeebdSAndreas Gohr    {
87e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
88e89aeebdSAndreas Gohr        $this->P->parse('Foo [Page](IW>somepage) Bar');
89e89aeebdSAndreas Gohr        $calls = [
90e89aeebdSAndreas Gohr            ['document_start', []],
91e89aeebdSAndreas Gohr            ['p_open', []],
92e89aeebdSAndreas Gohr            ['cdata', ["\nFoo "]],
93e89aeebdSAndreas Gohr            ['interwikilink', ['IW>somepage', 'Page', 'iw', 'somepage']],
94e89aeebdSAndreas Gohr            ['cdata', [' Bar']],
95e89aeebdSAndreas Gohr            ['p_close', []],
96e89aeebdSAndreas Gohr            ['document_end', []],
97e89aeebdSAndreas Gohr        ];
98e89aeebdSAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
99e89aeebdSAndreas Gohr    }
100e89aeebdSAndreas Gohr
101e89aeebdSAndreas Gohr    function testEmailLink()
102e89aeebdSAndreas Gohr    {
103e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
104e89aeebdSAndreas Gohr        $this->P->parse('Foo [mail](user@example.com) Bar');
105e89aeebdSAndreas Gohr        $calls = [
106e89aeebdSAndreas Gohr            ['document_start', []],
107e89aeebdSAndreas Gohr            ['p_open', []],
108e89aeebdSAndreas Gohr            ['cdata', ["\nFoo "]],
109e89aeebdSAndreas Gohr            ['emaillink', ['user@example.com', 'mail']],
110e89aeebdSAndreas Gohr            ['cdata', [' Bar']],
111e89aeebdSAndreas Gohr            ['p_close', []],
112e89aeebdSAndreas Gohr            ['document_end', []],
113e89aeebdSAndreas Gohr        ];
114e89aeebdSAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
115e89aeebdSAndreas Gohr    }
116e89aeebdSAndreas Gohr
117e89aeebdSAndreas Gohr    function testLocalAnchor()
118e89aeebdSAndreas Gohr    {
119e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
120e89aeebdSAndreas Gohr        $this->P->parse('Foo [section](#anchor) Bar');
121e89aeebdSAndreas Gohr        $calls = [
122e89aeebdSAndreas Gohr            ['document_start', []],
123e89aeebdSAndreas Gohr            ['p_open', []],
124e89aeebdSAndreas Gohr            ['cdata', ["\nFoo "]],
125e89aeebdSAndreas Gohr            ['locallink', ['anchor', 'section']],
126e89aeebdSAndreas Gohr            ['cdata', [' Bar']],
127e89aeebdSAndreas Gohr            ['p_close', []],
128e89aeebdSAndreas Gohr            ['document_end', []],
129e89aeebdSAndreas Gohr        ];
130e89aeebdSAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
131e89aeebdSAndreas Gohr    }
132e89aeebdSAndreas Gohr
133e89aeebdSAndreas Gohr    function testWindowsShare()
134e89aeebdSAndreas Gohr    {
135e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
136e89aeebdSAndreas Gohr        $this->P->parse('Foo [share](\\\\server\\share) Bar');
137e89aeebdSAndreas Gohr        $calls = [
138e89aeebdSAndreas Gohr            ['document_start', []],
139e89aeebdSAndreas Gohr            ['p_open', []],
140e89aeebdSAndreas Gohr            ['cdata', ["\nFoo "]],
141e89aeebdSAndreas Gohr            ['windowssharelink', ['\\\\server\\share', 'share']],
142e89aeebdSAndreas Gohr            ['cdata', [' Bar']],
143e89aeebdSAndreas Gohr            ['p_close', []],
144e89aeebdSAndreas Gohr            ['document_end', []],
145e89aeebdSAndreas Gohr        ];
146e89aeebdSAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
147e89aeebdSAndreas Gohr    }
148e89aeebdSAndreas Gohr
149e89aeebdSAndreas Gohr    function testTitleInDoubleQuotesIsDiscarded()
150e89aeebdSAndreas Gohr    {
151e89aeebdSAndreas Gohr        // GFM allows [text](url "title") but DokuWiki's link handler
152e89aeebdSAndreas Gohr        // instructions have no title-attribute slot. The title parses
153e89aeebdSAndreas Gohr        // cleanly but is dropped; the resulting handler call is identical
154e89aeebdSAndreas Gohr        // to the no-title case.
155e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
156e89aeebdSAndreas Gohr        $this->P->parse('Foo [Google](http://google.com "Search engine") Bar');
157e89aeebdSAndreas Gohr        $calls = [
158e89aeebdSAndreas Gohr            ['document_start', []],
159e89aeebdSAndreas Gohr            ['p_open', []],
160e89aeebdSAndreas Gohr            ['cdata', ["\nFoo "]],
161e89aeebdSAndreas Gohr            ['externallink', ['http://google.com', 'Google']],
162e89aeebdSAndreas Gohr            ['cdata', [' Bar']],
163e89aeebdSAndreas Gohr            ['p_close', []],
164e89aeebdSAndreas Gohr            ['document_end', []],
165e89aeebdSAndreas Gohr        ];
166e89aeebdSAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
167e89aeebdSAndreas Gohr    }
168e89aeebdSAndreas Gohr
169e89aeebdSAndreas Gohr    function testTitleInSingleQuotesIsDiscarded()
170e89aeebdSAndreas Gohr    {
171e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
172e89aeebdSAndreas Gohr        $this->P->parse("Foo [page](target 'a title') Bar");
173e89aeebdSAndreas Gohr        $calls = [
174e89aeebdSAndreas Gohr            ['document_start', []],
175e89aeebdSAndreas Gohr            ['p_open', []],
176e89aeebdSAndreas Gohr            ['cdata', ["\nFoo "]],
177e89aeebdSAndreas Gohr            ['internallink', ['target', 'page']],
178e89aeebdSAndreas Gohr            ['cdata', [' Bar']],
179e89aeebdSAndreas Gohr            ['p_close', []],
180e89aeebdSAndreas Gohr            ['document_end', []],
181e89aeebdSAndreas Gohr        ];
182e89aeebdSAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
183e89aeebdSAndreas Gohr    }
184e89aeebdSAndreas Gohr
185e89aeebdSAndreas Gohr    function testSpaceBetweenBracketsAndParensIsNotALink()
186e89aeebdSAndreas Gohr    {
187e89aeebdSAndreas Gohr        // GFM explicitly forbids whitespace between `]` and `(`.
188e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
189e89aeebdSAndreas Gohr        $this->P->parse('[foo] (bar)');
190e89aeebdSAndreas Gohr        $modes = array_column($this->H->calls, 0);
191e89aeebdSAndreas Gohr        $this->assertNotContains('internallink', $modes);
192e89aeebdSAndreas Gohr        $this->assertNotContains('externallink', $modes);
193e89aeebdSAndreas Gohr    }
194e89aeebdSAndreas Gohr
195e89aeebdSAndreas Gohr    function testDwDoubleBracketNotConsumedByGfmLink()
196e89aeebdSAndreas Gohr    {
197e89aeebdSAndreas Gohr        // With both gfm_link and DW internallink loaded (mixed syntax),
198e89aeebdSAndreas Gohr        // `[[foo]]` must go to Internallink. GfmLink's `\[(?!\[)` guard
199e89aeebdSAndreas Gohr        // refuses single-bracket matches that are actually part of `[[`.
200e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
201e89aeebdSAndreas Gohr        $this->P->addMode('internallink', new Internallink());
202e89aeebdSAndreas Gohr        $this->P->parse('Foo [[bar]] Baz');
203e89aeebdSAndreas Gohr        $calls = [
204e89aeebdSAndreas Gohr            ['document_start', []],
205e89aeebdSAndreas Gohr            ['p_open', []],
206e89aeebdSAndreas Gohr            ['cdata', ["\nFoo "]],
207e89aeebdSAndreas Gohr            ['internallink', ['bar', null]],
208e89aeebdSAndreas Gohr            ['cdata', [' Baz']],
209e89aeebdSAndreas Gohr            ['p_close', []],
210e89aeebdSAndreas Gohr            ['document_end', []],
211e89aeebdSAndreas Gohr        ];
212e89aeebdSAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
213e89aeebdSAndreas Gohr    }
214e89aeebdSAndreas Gohr
215e89aeebdSAndreas Gohr    function testMultibyteLinkText()
216e89aeebdSAndreas Gohr    {
217e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
218e89aeebdSAndreas Gohr        $this->P->parse('Foo [日本語](page) Bar');
219e89aeebdSAndreas Gohr        $calls = [
220e89aeebdSAndreas Gohr            ['document_start', []],
221e89aeebdSAndreas Gohr            ['p_open', []],
222e89aeebdSAndreas Gohr            ['cdata', ["\nFoo "]],
223e89aeebdSAndreas Gohr            ['internallink', ['page', '日本語']],
224e89aeebdSAndreas Gohr            ['cdata', [' Bar']],
225e89aeebdSAndreas Gohr            ['p_close', []],
226e89aeebdSAndreas Gohr            ['document_end', []],
227e89aeebdSAndreas Gohr        ];
228e89aeebdSAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
229e89aeebdSAndreas Gohr    }
230e89aeebdSAndreas Gohr
231e89aeebdSAndreas Gohr    function testReferenceStyleLinkNotMatched()
232e89aeebdSAndreas Gohr    {
233e89aeebdSAndreas Gohr        // `[foo][bar]` (reference-style) requires a reference definition
234e89aeebdSAndreas Gohr        // we do not support; each `[...]` should stay literal text.
235e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
236e89aeebdSAndreas Gohr        $this->P->parse('[foo][bar]');
237e89aeebdSAndreas Gohr        $modes = array_column($this->H->calls, 0);
238e89aeebdSAndreas Gohr        $this->assertNotContains('internallink', $modes);
239e89aeebdSAndreas Gohr        $this->assertNotContains('externallink', $modes);
240e89aeebdSAndreas Gohr    }
241e89aeebdSAndreas Gohr
242e89aeebdSAndreas Gohr    function testTwoLinksInOneLine()
243e89aeebdSAndreas Gohr    {
244e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
245e89aeebdSAndreas Gohr        $this->P->parse('Foo [one](a) and [two](b) Bar');
246e89aeebdSAndreas Gohr        $calls = [
247e89aeebdSAndreas Gohr            ['document_start', []],
248e89aeebdSAndreas Gohr            ['p_open', []],
249e89aeebdSAndreas Gohr            ['cdata', ["\nFoo "]],
250e89aeebdSAndreas Gohr            ['internallink', ['a', 'one']],
251e89aeebdSAndreas Gohr            ['cdata', [' and ']],
252e89aeebdSAndreas Gohr            ['internallink', ['b', 'two']],
253e89aeebdSAndreas Gohr            ['cdata', [' Bar']],
254e89aeebdSAndreas Gohr            ['p_close', []],
255e89aeebdSAndreas Gohr            ['document_end', []],
256e89aeebdSAndreas Gohr        ];
257e89aeebdSAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
258e89aeebdSAndreas Gohr    }
259e89aeebdSAndreas Gohr
260e89aeebdSAndreas Gohr    function testFragmentInExternalUrl()
261e89aeebdSAndreas Gohr    {
262e89aeebdSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
263e89aeebdSAndreas Gohr        $this->P->parse('Foo [x](http://example.com#fragment) Bar');
264e89aeebdSAndreas Gohr        $calls = [
265e89aeebdSAndreas Gohr            ['document_start', []],
266e89aeebdSAndreas Gohr            ['p_open', []],
267e89aeebdSAndreas Gohr            ['cdata', ["\nFoo "]],
268e89aeebdSAndreas Gohr            ['externallink', ['http://example.com#fragment', 'x']],
269e89aeebdSAndreas Gohr            ['cdata', [' Bar']],
270e89aeebdSAndreas Gohr            ['p_close', []],
271e89aeebdSAndreas Gohr            ['document_end', []],
272e89aeebdSAndreas Gohr        ];
273e89aeebdSAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
274e89aeebdSAndreas Gohr    }
275e89aeebdSAndreas Gohr
2763440a8c0SAndreas Gohr    // ----- image-as-label (`[![alt](img)](target)`) -----
2773440a8c0SAndreas Gohr
2783440a8c0SAndreas Gohr    /**
2793440a8c0SAndreas Gohr     * Media descriptor shape GfmLink emits for image-as-label, matching
2803440a8c0SAndreas Gohr     * what Media::parseMedia() returns.
2813440a8c0SAndreas Gohr     */
2823440a8c0SAndreas Gohr    private function mediaArray(array $overrides): array
2833440a8c0SAndreas Gohr    {
2843440a8c0SAndreas Gohr        return array_merge([
2853440a8c0SAndreas Gohr            'type'    => 'internalmedia',
2863440a8c0SAndreas Gohr            'src'     => 'wiki:image.png',
2873440a8c0SAndreas Gohr            'title'   => 'alt',
2883440a8c0SAndreas Gohr            'align'   => null,
2893440a8c0SAndreas Gohr            'width'   => null,
2903440a8c0SAndreas Gohr            'height'  => null,
2913440a8c0SAndreas Gohr            'cache'   => 'cache',
2923440a8c0SAndreas Gohr            'linking' => 'details',
2933440a8c0SAndreas Gohr        ], $overrides);
2943440a8c0SAndreas Gohr    }
2953440a8c0SAndreas Gohr
2963440a8c0SAndreas Gohr    function testImageAsLabelInternalPageLink()
2973440a8c0SAndreas Gohr    {
2983440a8c0SAndreas Gohr        // The canonical case: image that links to a wiki page.
2993440a8c0SAndreas Gohr        // Markdown equivalent of DW's `[[test:link|{{wiki:image.png}}]]`.
3003440a8c0SAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
3013440a8c0SAndreas Gohr        $this->P->parse('Foo [![alt](wiki:image.png)](test:link) Bar');
3023440a8c0SAndreas Gohr        $calls = [
3033440a8c0SAndreas Gohr            ['document_start', []],
3043440a8c0SAndreas Gohr            ['p_open', []],
3053440a8c0SAndreas Gohr            ['cdata', ["\nFoo "]],
3063440a8c0SAndreas Gohr            ['internallink', ['test:link', $this->mediaArray([])]],
3073440a8c0SAndreas Gohr            ['cdata', [' Bar']],
3083440a8c0SAndreas Gohr            ['p_close', []],
3093440a8c0SAndreas Gohr            ['document_end', []],
3103440a8c0SAndreas Gohr        ];
3113440a8c0SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
3123440a8c0SAndreas Gohr    }
3133440a8c0SAndreas Gohr
3143440a8c0SAndreas Gohr    function testImageAsLabelExternalLink()
3153440a8c0SAndreas Gohr    {
3163440a8c0SAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
3173440a8c0SAndreas Gohr        $this->P->parse('Foo [![alt](wiki:image.png)](http://example.com) Bar');
3183440a8c0SAndreas Gohr        $calls = [
3193440a8c0SAndreas Gohr            ['document_start', []],
3203440a8c0SAndreas Gohr            ['p_open', []],
3213440a8c0SAndreas Gohr            ['cdata', ["\nFoo "]],
3223440a8c0SAndreas Gohr            ['externallink', ['http://example.com', $this->mediaArray([])]],
3233440a8c0SAndreas Gohr            ['cdata', [' Bar']],
3243440a8c0SAndreas Gohr            ['p_close', []],
3253440a8c0SAndreas Gohr            ['document_end', []],
3263440a8c0SAndreas Gohr        ];
3273440a8c0SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
3283440a8c0SAndreas Gohr    }
3293440a8c0SAndreas Gohr
3303440a8c0SAndreas Gohr    function testImageAsLabelWithExternalMedia()
3313440a8c0SAndreas Gohr    {
3323440a8c0SAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
3333440a8c0SAndreas Gohr        $this->P->parse('Foo [![logo](https://example.com/logo.png)](test:link) Bar');
3343440a8c0SAndreas Gohr        $calls = [
3353440a8c0SAndreas Gohr            ['document_start', []],
3363440a8c0SAndreas Gohr            ['p_open', []],
3373440a8c0SAndreas Gohr            ['cdata', ["\nFoo "]],
3383440a8c0SAndreas Gohr            ['internallink', ['test:link', $this->mediaArray([
3393440a8c0SAndreas Gohr                'type'  => 'externalmedia',
3403440a8c0SAndreas Gohr                'src'   => 'https://example.com/logo.png',
3413440a8c0SAndreas Gohr                'title' => 'logo',
3423440a8c0SAndreas Gohr            ])]],
3433440a8c0SAndreas Gohr            ['cdata', [' Bar']],
3443440a8c0SAndreas Gohr            ['p_close', []],
3453440a8c0SAndreas Gohr            ['document_end', []],
3463440a8c0SAndreas Gohr        ];
3473440a8c0SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
3483440a8c0SAndreas Gohr    }
3493440a8c0SAndreas Gohr
3503440a8c0SAndreas Gohr    function testImageAsLabelInterwikiLink()
3513440a8c0SAndreas Gohr    {
3523440a8c0SAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
3533440a8c0SAndreas Gohr        $this->P->parse('Foo [![alt](wiki:image.png)](wp>Example) Bar');
3543440a8c0SAndreas Gohr        $calls = [
3553440a8c0SAndreas Gohr            ['document_start', []],
3563440a8c0SAndreas Gohr            ['p_open', []],
3573440a8c0SAndreas Gohr            ['cdata', ["\nFoo "]],
3583440a8c0SAndreas Gohr            ['interwikilink', ['wp>Example', $this->mediaArray([]), 'wp', 'Example']],
3593440a8c0SAndreas Gohr            ['cdata', [' Bar']],
3603440a8c0SAndreas Gohr            ['p_close', []],
3613440a8c0SAndreas Gohr            ['document_end', []],
3623440a8c0SAndreas Gohr        ];
3633440a8c0SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
3643440a8c0SAndreas Gohr    }
3653440a8c0SAndreas Gohr
3663440a8c0SAndreas Gohr    function testImageAsLabelEmailLink()
3673440a8c0SAndreas Gohr    {
3683440a8c0SAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
3693440a8c0SAndreas Gohr        $this->P->parse('Foo [![alt](wiki:image.png)](user@example.com) Bar');
3703440a8c0SAndreas Gohr        $calls = [
3713440a8c0SAndreas Gohr            ['document_start', []],
3723440a8c0SAndreas Gohr            ['p_open', []],
3733440a8c0SAndreas Gohr            ['cdata', ["\nFoo "]],
3743440a8c0SAndreas Gohr            ['emaillink', ['user@example.com', $this->mediaArray([])]],
3753440a8c0SAndreas Gohr            ['cdata', [' Bar']],
3763440a8c0SAndreas Gohr            ['p_close', []],
3773440a8c0SAndreas Gohr            ['document_end', []],
3783440a8c0SAndreas Gohr        ];
3793440a8c0SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
3803440a8c0SAndreas Gohr    }
3813440a8c0SAndreas Gohr
3823440a8c0SAndreas Gohr    function testImageAsLabelMediaParameters()
3833440a8c0SAndreas Gohr    {
3843440a8c0SAndreas Gohr        // Full DW parameter vocabulary works in the nested image slot.
3853440a8c0SAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
3863440a8c0SAndreas Gohr        $this->P->parse('Foo [![alt](wiki:image.png?200x100&right&nolink)](test:link) Bar');
3873440a8c0SAndreas Gohr        $calls = [
3883440a8c0SAndreas Gohr            ['document_start', []],
3893440a8c0SAndreas Gohr            ['p_open', []],
3903440a8c0SAndreas Gohr            ['cdata', ["\nFoo "]],
3913440a8c0SAndreas Gohr            ['internallink', ['test:link', $this->mediaArray([
3923440a8c0SAndreas Gohr                'align'   => 'right',
3933440a8c0SAndreas Gohr                'width'   => '200',
3943440a8c0SAndreas Gohr                'height'  => '100',
3953440a8c0SAndreas Gohr                'linking' => 'nolink',
3963440a8c0SAndreas Gohr            ])]],
3973440a8c0SAndreas Gohr            ['cdata', [' Bar']],
3983440a8c0SAndreas Gohr            ['p_close', []],
3993440a8c0SAndreas Gohr            ['document_end', []],
4003440a8c0SAndreas Gohr        ];
4013440a8c0SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
4023440a8c0SAndreas Gohr    }
4033440a8c0SAndreas Gohr
4043440a8c0SAndreas Gohr    function testImageAsLabelEmptyAlt()
4053440a8c0SAndreas Gohr    {
4063440a8c0SAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
4073440a8c0SAndreas Gohr        $this->P->parse('Foo [![](wiki:image.png)](test:link) Bar');
4083440a8c0SAndreas Gohr        $calls = [
4093440a8c0SAndreas Gohr            ['document_start', []],
4103440a8c0SAndreas Gohr            ['p_open', []],
4113440a8c0SAndreas Gohr            ['cdata', ["\nFoo "]],
4123440a8c0SAndreas Gohr            ['internallink', ['test:link', $this->mediaArray(['title' => null])]],
4133440a8c0SAndreas Gohr            ['cdata', [' Bar']],
4143440a8c0SAndreas Gohr            ['p_close', []],
4153440a8c0SAndreas Gohr            ['document_end', []],
4163440a8c0SAndreas Gohr        ];
4173440a8c0SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
4183440a8c0SAndreas Gohr    }
4193440a8c0SAndreas Gohr
4203440a8c0SAndreas Gohr    function testImageAsLabelBothTitlesDiscarded()
4213440a8c0SAndreas Gohr    {
4223440a8c0SAndreas Gohr        // Titles on both URLs parse cleanly but are dropped — neither
4233440a8c0SAndreas Gohr        // DW's media nor link instructions have a title-attribute slot.
4243440a8c0SAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
4253440a8c0SAndreas Gohr        $this->P->parse('Foo [![alt](wiki:image.png "img title")](test:link "link title") Bar');
4263440a8c0SAndreas Gohr        $calls = [
4273440a8c0SAndreas Gohr            ['document_start', []],
4283440a8c0SAndreas Gohr            ['p_open', []],
4293440a8c0SAndreas Gohr            ['cdata', ["\nFoo "]],
4303440a8c0SAndreas Gohr            ['internallink', ['test:link', $this->mediaArray([])]],
4313440a8c0SAndreas Gohr            ['cdata', [' Bar']],
4323440a8c0SAndreas Gohr            ['p_close', []],
4333440a8c0SAndreas Gohr            ['document_end', []],
4343440a8c0SAndreas Gohr        ];
4353440a8c0SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
4363440a8c0SAndreas Gohr    }
4373440a8c0SAndreas Gohr
43874031e46SAndreas Gohr    // ----- backslash-escape interaction (GFM §6.1) -----
43974031e46SAndreas Gohr
44074031e46SAndreas Gohr    function testBackslashEscapesInLabel()
44174031e46SAndreas Gohr    {
44274031e46SAndreas Gohr        // Plain-text label gets §6.1 unescape applied before it reaches
44374031e46SAndreas Gohr        // the link handler — `\*` collapses to a literal `*`.
44474031e46SAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
44574031e46SAndreas Gohr        $this->P->parse('Foo [te\\*xt](page) Bar');
44674031e46SAndreas Gohr        $calls = [
44774031e46SAndreas Gohr            ['document_start', []],
44874031e46SAndreas Gohr            ['p_open', []],
44974031e46SAndreas Gohr            ['cdata', ["\nFoo "]],
45074031e46SAndreas Gohr            ['internallink', ['page', 'te*xt']],
45174031e46SAndreas Gohr            ['cdata', [' Bar']],
45274031e46SAndreas Gohr            ['p_close', []],
45374031e46SAndreas Gohr            ['document_end', []],
45474031e46SAndreas Gohr        ];
45574031e46SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
45674031e46SAndreas Gohr    }
45774031e46SAndreas Gohr
4580f694376SAndreas Gohr    function testBackslashEscapedBracketInLabel()
4590f694376SAndreas Gohr    {
4600f694376SAndreas Gohr        // Spec example #523: an escaped `[` inside the label is allowed
4610f694376SAndreas Gohr        // and unescapes to a literal bracket. The label class accepts
4620f694376SAndreas Gohr        // `\[` / `\]` so the outer match still finds its `]` close.
4630f694376SAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
4640f694376SAndreas Gohr        $this->P->parse('Foo [link \\[bar](page) Baz');
4650f694376SAndreas Gohr        $calls = [
4660f694376SAndreas Gohr            ['document_start', []],
4670f694376SAndreas Gohr            ['p_open', []],
4680f694376SAndreas Gohr            ['cdata', ["\nFoo "]],
4690f694376SAndreas Gohr            ['internallink', ['page', 'link [bar']],
4700f694376SAndreas Gohr            ['cdata', [' Baz']],
4710f694376SAndreas Gohr            ['p_close', []],
4720f694376SAndreas Gohr            ['document_end', []],
4730f694376SAndreas Gohr        ];
4740f694376SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
4750f694376SAndreas Gohr    }
4760f694376SAndreas Gohr
4770f694376SAndreas Gohr    function testBackslashEscapedClosingBracketInLabel()
4780f694376SAndreas Gohr    {
4790f694376SAndreas Gohr        // The `\]` form is symmetric with `\[`. Both must be accepted by
4800f694376SAndreas Gohr        // the label class without ending the outer match early.
4810f694376SAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
4820f694376SAndreas Gohr        $this->P->parse('Foo [a\\]b](page) Bar');
4830f694376SAndreas Gohr        $calls = [
4840f694376SAndreas Gohr            ['document_start', []],
4850f694376SAndreas Gohr            ['p_open', []],
4860f694376SAndreas Gohr            ['cdata', ["\nFoo "]],
4870f694376SAndreas Gohr            ['internallink', ['page', 'a]b']],
4880f694376SAndreas Gohr            ['cdata', [' Bar']],
4890f694376SAndreas Gohr            ['p_close', []],
4900f694376SAndreas Gohr            ['document_end', []],
4910f694376SAndreas Gohr        ];
4920f694376SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
4930f694376SAndreas Gohr    }
4940f694376SAndreas Gohr
49574031e46SAndreas Gohr    function testBackslashEscapesInUrl()
49674031e46SAndreas Gohr    {
49774031e46SAndreas Gohr        // §6.1 unescape fires on the URL after classify() picks the
49874031e46SAndreas Gohr        // handler — it lets users put a literal punctuation char in a
49974031e46SAndreas Gohr        // URL slot that would otherwise carry markup meaning.
50074031e46SAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
50174031e46SAndreas Gohr        $this->P->parse('Foo [text](http://example.com/pa\\!ge) Bar');
50274031e46SAndreas Gohr        $calls = [
50374031e46SAndreas Gohr            ['document_start', []],
50474031e46SAndreas Gohr            ['p_open', []],
50574031e46SAndreas Gohr            ['cdata', ["\nFoo "]],
50674031e46SAndreas Gohr            ['externallink', ['http://example.com/pa!ge', 'text']],
50774031e46SAndreas Gohr            ['cdata', [' Bar']],
50874031e46SAndreas Gohr            ['p_close', []],
50974031e46SAndreas Gohr            ['document_end', []],
51074031e46SAndreas Gohr        ];
51174031e46SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
51274031e46SAndreas Gohr    }
51374031e46SAndreas Gohr
51474031e46SAndreas Gohr    function testWindowsShareUrlSkipsBackslashUnescape()
51574031e46SAndreas Gohr    {
51674031e46SAndreas Gohr        // Carve-out: a `\\host\path` URL must survive classify() and
51774031e46SAndreas Gohr        // stay intact as a windowssharelink. Applying §6.1 unescape
51874031e46SAndreas Gohr        // would collapse the leading `\\` to `\` and destroy the share
51974031e46SAndreas Gohr        // marker, so the unescape pass is skipped for this classifier.
52074031e46SAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
52174031e46SAndreas Gohr        $this->P->parse('Foo [share](\\\\server\\share\\sub) Bar');
52274031e46SAndreas Gohr        $calls = [
52374031e46SAndreas Gohr            ['document_start', []],
52474031e46SAndreas Gohr            ['p_open', []],
52574031e46SAndreas Gohr            ['cdata', ["\nFoo "]],
52674031e46SAndreas Gohr            ['windowssharelink', ['\\\\server\\share\\sub', 'share']],
52774031e46SAndreas Gohr            ['cdata', [' Bar']],
52874031e46SAndreas Gohr            ['p_close', []],
52974031e46SAndreas Gohr            ['document_end', []],
53074031e46SAndreas Gohr        ];
53174031e46SAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
53274031e46SAndreas Gohr    }
53374031e46SAndreas Gohr
5344f32c45bSAndreas Gohr    function testSoftLineBreakInLabel()
5354f32c45bSAndreas Gohr    {
5364f32c45bSAndreas Gohr        // CommonMark allows a soft line break inside link text. The `\n`
5374f32c45bSAndreas Gohr        // is preserved in the label string and rendered as a space by
5384f32c45bSAndreas Gohr        // HTML; the link still resolves to a single externallink call.
5394f32c45bSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
5404f32c45bSAndreas Gohr        $this->P->parse("A [link with\na newline](http://example.org)?");
5414f32c45bSAndreas Gohr        $calls = [
5424f32c45bSAndreas Gohr            ['document_start', []],
5434f32c45bSAndreas Gohr            ['p_open', []],
5444f32c45bSAndreas Gohr            ['cdata', ["\nA "]],
5454f32c45bSAndreas Gohr            ['externallink', ['http://example.org', "link with\na newline"]],
5464f32c45bSAndreas Gohr            ['cdata', ['?']],
5474f32c45bSAndreas Gohr            ['p_close', []],
5484f32c45bSAndreas Gohr            ['document_end', []],
5494f32c45bSAndreas Gohr        ];
5504f32c45bSAndreas Gohr        $this->assertCalls($calls, $this->H->calls);
5514f32c45bSAndreas Gohr    }
5524f32c45bSAndreas Gohr
5534f32c45bSAndreas Gohr    function testBlankLineEndsLabel()
5544f32c45bSAndreas Gohr    {
5554f32c45bSAndreas Gohr        // A blank line is not allowed inside link text — the regex
5564f32c45bSAndreas Gohr        // declines to cross it, so the bracket sequence stays literal.
5574f32c45bSAndreas Gohr        $this->P->addMode('gfm_link', new GfmLink());
5584f32c45bSAndreas Gohr        $this->P->parse("[link with\n\nblank line](http://example.org)");
5594f32c45bSAndreas Gohr        $modes = array_column($this->H->calls, 0);
5604f32c45bSAndreas Gohr        $this->assertNotContains('externallink', $modes);
5614f32c45bSAndreas Gohr        $this->assertNotContains('internallink', $modes);
5624f32c45bSAndreas Gohr    }
5634f32c45bSAndreas Gohr
564e89aeebdSAndreas Gohr    function testSortValue()
565e89aeebdSAndreas Gohr    {
566e89aeebdSAndreas Gohr        $this->assertSame(300, (new GfmLink())->getSort());
567e89aeebdSAndreas Gohr    }
568e89aeebdSAndreas Gohr}
569