1<?php
2
3namespace dokuwiki\test\File;
4
5use dokuwiki\File\PageResolver;
6
7/**
8 * @todo tests that make use of revisions might be wanted
9 */
10class PageResolverTest extends \DokuWikiTest
11{
12    /**
13     * @return \Generator|array
14     * @see testResolveID
15     */
16    public function provideResolveData()
17    {
18        $data = [
19            // relative current in root
20            ['context', 'page', 'page'],
21            ['context', '.page', 'page'],
22            ['context', '.:page', 'page'],
23
24            // relative current in namespace
25            ['lev1:lev2:context', 'page', 'lev1:lev2:page'],
26            ['lev1:lev2:context', '.page', 'lev1:lev2:page'],
27            ['lev1:lev2:context', '.:page', 'lev1:lev2:page'],
28
29            // relative upper in root
30            ['context', '..page', 'page'],
31            ['context', '..:page', 'page'],
32
33            // relative upper in namespace
34            ['lev1:lev2:context', '..page', 'lev1:page'],
35            ['lev1:lev2:context', '..:page', 'lev1:page'],
36            ['lev1:lev2:context', '..:..:page', 'page'],
37            ['lev1:lev2:context', '..:..:..:page', 'page'],
38
39            // deeper nesting
40            ['lev1:lev2:lev3:context', '..page', 'lev1:lev2:page'],
41            ['lev1:lev2:lev3:context', '..:page', 'lev1:lev2:page'],
42            ['lev1:lev2:lev3:context', '..:..page', 'lev1:page'],
43            ['lev1:lev2:lev3:context', '..:..:page', 'lev1:page'],
44            ['lev1:lev2:lev3:context', '..:..:..page', 'page'],
45            ['lev1:lev2:lev3:context', '..:..:..:page', 'page'],
46            ['lev1:lev2:lev3:context', '..:..:..:..page', 'page'],
47            ['lev1:lev2:lev3:context', '..:..:..:..:page', 'page'],
48
49            // strange and broken ones
50            ['lev1:lev2:context', '....:....:page', 'lev1:lev2:page'],
51            ['lev1:lev2:context', '..:..:lev3:page', 'lev3:page'],
52            ['lev1:lev2:context', '..:..:lev3:..:page', 'page'],
53            ['lev1:lev2:context', '..:..:lev3:..:page:....:...', 'page:start'],
54
55            // relative to current page
56            ['context', '~page', 'context:page'],
57            ['context', '~:page', 'context:page'],
58            ['lev1:lev2:context', '~page', 'lev1:lev2:context:page'],
59            ['lev1:lev2:context', '~:page', 'lev1:lev2:context:page'],
60
61            // start pages
62            ['context', '.:', 'start'],
63            ['foo:context', '.:', 'foo:start'],
64            ['context', 'foo:', 'foo:start'],
65            ['foo:context', 'foo:', 'foo:start'],
66            ['context', '~foo:', 'context:foo:start'],
67            ['foo:context', '~foo:', 'foo:context:foo:start'],
68
69            // empty page links to itself
70            ['context', '', 'context'],
71            ['foo:context', '', 'foo:context'],
72
73            // issue 4072
74            ['context', ':..', 'start'],
75            ['context', ':foo:bar:..', 'foo:bar:start'],
76        ];
77
78        // run each test without a hash
79        foreach ($data as $row) {
80            yield $row;
81        }
82
83        // run each test with a hash
84        foreach ($data as $row) {
85            $row[1] .= '#somehash';
86            $row[2] .= '#somehash';
87            yield $row;
88        }
89    }
90
91    /**
92     * @dataProvider provideResolveData
93     * @param string $context
94     * @param string $id
95     * @param string $expected
96     */
97    public function testResolveID($context, $id, $expected)
98    {
99        $resolver = new PageResolver($context);
100        $this->assertEquals($expected, $resolver->resolveId($id));
101    }
102
103    /**
104     * Tilde start page behaviour
105     *
106     * Please note that a ~ alone is the same as ~:
107     */
108    public function testTildeStartPage()
109    {
110        $context = 'foo:context';
111        $resolver = new PageResolver($context);
112
113        // the $context page itself does not exist
114        // a link like that is usually not possible, but we fall back to standard start
115        // page behaviour
116        $this->assertEquals("$context:start", $resolver->resolveId('~:'));
117        $this->assertEquals("$context:start", $resolver->resolveId('~'));
118
119        // now $context has become the start page
120        saveWikiText($context, 'test', 'test');
121        $this->assertEquals($context, $resolver->resolveId('~:'));
122
123        // now we have a startpage named like the namespace
124        saveWikiText("$context:context", 'test', 'test');
125        $this->assertEquals("$context:context", $resolver->resolveId('~:'));
126        $this->assertEquals("$context:context", $resolver->resolveId('~'));
127
128        // now we have a dedicated start page
129        saveWikiText("$context:start", 'test', 'test');
130        $this->assertEquals("$context:start", $resolver->resolveId('~:'));
131        $this->assertEquals("$context:start", $resolver->resolveId('~'));
132    }
133
134    public function testResolveStartPage()
135    {
136
137        $resolver = new PageResolver('arbitrary');
138
139        $expect = 'foo:start';
140        $actual = $this->callInaccessibleMethod($resolver, 'resolveStartPage', ['foo:', false, false]);
141        $this->assertEquals($expect, $actual, 'default non-existing');
142
143        saveWikiText('foo', 'test', 'test');
144        $expect = 'foo';
145        $actual = $this->callInaccessibleMethod($resolver, 'resolveStartPage', ['foo:', false, false]);
146        $this->assertEquals($expect, $actual, 'page like namespace outside');
147
148        saveWikiText('foo:foo', 'test', 'test');
149        $expect = 'foo:foo';
150        $actual = $this->callInaccessibleMethod($resolver, 'resolveStartPage', ['foo:', false, false]);
151        $this->assertEquals($expect, $actual, 'page like namespace inside');
152
153        saveWikiText('foo:start', 'test', 'test');
154        $expect = 'foo:start';
155        $actual = $this->callInaccessibleMethod($resolver, 'resolveStartPage', ['foo:', false, false]);
156        $this->assertEquals($expect, $actual, 'default existing');
157    }
158
159    /**
160     * @return array
161     * @see testResolveRelatives
162     */
163    public function provideResolveRelatives()
164    {
165        return [
166            ['foo', 'foo'],
167            ['foo:bar', 'foo:bar'],
168            ['foo:..:bar', 'bar'],
169            ['foo:..:..:bar', 'bar'],
170            ['foo:.:bar', 'foo:bar'],
171            ['foo:.:..:.:bar', 'bar'],
172            ['foo:.:.:.:bar', 'foo:bar'],
173            ['foo::::bar', 'foo:bar'],
174            ['foo::::bar:', 'foo:bar:'],
175            ['foo:bar:', 'foo:bar:'],
176        ];
177    }
178
179    /**
180     * @dataProvider provideResolveRelatives
181     * @param string $input
182     * @param string $expected
183     */
184    public function testResolveRelatives($input, $expected)
185    {
186        $resolver = new PageResolver('arbitrary');
187
188        $actual = $this->callInaccessibleMethod($resolver, 'resolveRelatives', [$input]);
189        $this->assertEquals($expected, $actual);
190    }
191
192    public function testAutoPlural()
193    {
194        $resolver = new PageResolver('arbitrary');
195
196        $singular = 'some:page';
197        $plural = 'some:pages';
198
199        $actual = $this->callInaccessibleMethod($resolver, 'resolveAutoPlural', [$singular, '', false]);
200        $this->assertEquals($singular, $actual); // no pages exist
201
202        saveWikiText($plural, 'plural', 'plural');
203        $actual = $this->callInaccessibleMethod($resolver, 'resolveAutoPlural', [$singular, '', false]);
204        $this->assertEquals($plural, $actual); // plural exists
205
206        saveWikiText($singular, 'singular', 'singular');
207        $actual = $this->callInaccessibleMethod($resolver, 'resolveAutoPlural', [$singular, '', false]);
208        $this->assertEquals($singular, $actual); // requested singular has preference
209    }
210}
211