xref: /dokuwiki/_test/tests/Parsing/ModeRegistryTest.php (revision 72b2703b4f922ca520e7cb2e7765a252175f30d3)
1<?php
2
3namespace dokuwiki\test\Parsing;
4
5use dokuwiki\Parsing\ModeRegistry;
6use dokuwiki\Parsing\ParserMode\ModeInterface;
7
8class ModeRegistryTest extends \DokuWikiTest
9{
10    /** @var ModeRegistry */
11    private $registry;
12
13    function setUp(): void
14    {
15        parent::setUp();
16        ModeRegistry::reset();
17        $this->registry = ModeRegistry::getInstance();
18    }
19
20    function tearDown(): void
21    {
22        ModeRegistry::reset();
23        parent::tearDown();
24    }
25
26    function testSingleton()
27    {
28        $this->assertSame(
29            ModeRegistry::getInstance(),
30            ModeRegistry::getInstance()
31        );
32    }
33
34    function testResetCreatesFreshInstance()
35    {
36        $first = ModeRegistry::getInstance();
37        ModeRegistry::reset();
38        $second = ModeRegistry::getInstance();
39        $this->assertNotSame($first, $second);
40    }
41
42    function testConstructorPopulatesGlobal()
43    {
44        global $PARSER_MODES;
45        $this->assertIsArray($PARSER_MODES);
46        $this->assertArrayHasKey('container', $PARSER_MODES);
47        $this->assertArrayHasKey('formatting', $PARSER_MODES);
48        $this->assertArrayHasKey('substition', $PARSER_MODES);
49        $this->assertArrayHasKey('protected', $PARSER_MODES);
50        $this->assertArrayHasKey('disabled', $PARSER_MODES);
51        $this->assertArrayHasKey('paragraphs', $PARSER_MODES);
52        $this->assertArrayHasKey('baseonly', $PARSER_MODES);
53    }
54
55    function testGetCategories()
56    {
57        global $PARSER_MODES;
58        $this->assertSame($PARSER_MODES, $this->registry->getCategories());
59    }
60
61    function testGetModesForSingleCategory()
62    {
63        $modes = $this->registry->getModesForCategories([ModeRegistry::CATEGORY_CONTAINER]);
64        $this->assertContains('listblock', $modes);
65        $this->assertContains('table', $modes);
66        $this->assertContains('quote', $modes);
67        $this->assertContains('hr', $modes);
68    }
69
70    function testGetModesForMultipleCategories()
71    {
72        $modes = $this->registry->getModesForCategories([
73            ModeRegistry::CATEGORY_CONTAINER,
74            ModeRegistry::CATEGORY_BASEONLY,
75        ]);
76        $this->assertContains('listblock', $modes);
77        $this->assertContains('header', $modes);
78    }
79
80    function testGetModesForCategoriesDeduplicates()
81    {
82        $modes = $this->registry->getModesForCategories([
83            ModeRegistry::CATEGORY_CONTAINER,
84            ModeRegistry::CATEGORY_CONTAINER,
85        ]);
86        $counts = array_count_values($modes);
87        foreach ($counts as $count) {
88            $this->assertEquals(1, $count);
89        }
90    }
91
92    function testGetModesForUnknownCategoryReturnsEmpty()
93    {
94        $modes = $this->registry->getModesForCategories(['nonexistent']);
95        $this->assertSame([], $modes);
96    }
97
98    function testRegisterMode()
99    {
100        global $PARSER_MODES;
101        $this->registry->registerMode(ModeRegistry::CATEGORY_CONTAINER, 'testmode');
102        $this->assertContains('testmode', $PARSER_MODES[ModeRegistry::CATEGORY_CONTAINER]);
103        $this->assertContains(
104            'testmode',
105            $this->registry->getModesForCategories([ModeRegistry::CATEGORY_CONTAINER])
106        );
107    }
108
109    function testGlobalModificationsAreVisible()
110    {
111        global $PARSER_MODES;
112        $PARSER_MODES[ModeRegistry::CATEGORY_FORMATTING][] = 'custom_format';
113        $modes = $this->registry->getModesForCategories([ModeRegistry::CATEGORY_FORMATTING]);
114        $this->assertContains('custom_format', $modes);
115    }
116
117    function testGetModesReturnsSortedArray()
118    {
119        $modes = $this->registry->getModes();
120        $this->assertNotEmpty($modes);
121
122        $sortValues = array_column($modes, 'sort');
123        $sorted = $sortValues;
124        sort($sorted);
125        $this->assertSame($sorted, $sortValues);
126    }
127
128    function testGetModesContainsExpectedKeys()
129    {
130        $modes = $this->registry->getModes();
131        foreach ($modes as $entry) {
132            $this->assertArrayHasKey('sort', $entry);
133            $this->assertArrayHasKey('mode', $entry);
134            $this->assertArrayHasKey('obj', $entry);
135            $this->assertIsInt($entry['sort']);
136            $this->assertIsString($entry['mode']);
137            $this->assertInstanceOf(ModeInterface::class, $entry['obj']);
138        }
139    }
140
141    function testGetModesContainsBuiltinModes()
142    {
143        $modes = $this->registry->getModes();
144        $modeNames = array_column($modes, 'mode');
145        $this->assertContains('strong', $modeNames);
146        $this->assertContains('header', $modeNames);
147        $this->assertContains('listblock', $modeNames);
148        $this->assertContains('eol', $modeNames);
149        $this->assertContains('smiley', $modeNames);
150        $this->assertContains('acronym', $modeNames);
151        $this->assertContains('entity', $modeNames);
152    }
153
154    function testSortModes()
155    {
156        $a = ['sort' => 10, 'mode' => 'a'];
157        $b = ['sort' => 20, 'mode' => 'b'];
158        $this->assertLessThan(0, ModeRegistry::sortModes($a, $b));
159        $this->assertGreaterThan(0, ModeRegistry::sortModes($b, $a));
160        $this->assertEquals(0, ModeRegistry::sortModes($a, $a));
161    }
162
163    function testBlockEolModesEmptyByDefault()
164    {
165        $this->assertSame([], $this->registry->getBlockEolModes());
166    }
167
168    function testRegisterBlockEolMode()
169    {
170        $this->registry->registerBlockEolMode('listblock');
171        $this->registry->registerBlockEolMode('table');
172        $this->assertSame(['listblock', 'table'], $this->registry->getBlockEolModes());
173    }
174
175    function testLineStartMarkersEmptyByDefault()
176    {
177        $this->assertSame([], $this->registry->getLineStartMarkers());
178    }
179
180    function testRegisterLineStartMarkers()
181    {
182        $this->registry->registerLineStartMarkers('listblock', ['\\*', '\\-']);
183        $markers = $this->registry->getLineStartMarkers();
184        $this->assertContains('\\*', $markers);
185        $this->assertContains('\\-', $markers);
186    }
187
188    function testLineStartMarkersDeduplicates()
189    {
190        $this->registry->registerLineStartMarkers('mode_a', ['\\*', '\\-']);
191        $this->registry->registerLineStartMarkers('mode_b', ['\\-', '\\+']);
192        $markers = $this->registry->getLineStartMarkers();
193        $this->assertCount(3, $markers);
194        $this->assertContains('\\*', $markers);
195        $this->assertContains('\\-', $markers);
196        $this->assertContains('\\+', $markers);
197    }
198
199    function testBlockEolModesResetWithInstance()
200    {
201        $this->registry->registerBlockEolMode('listblock');
202        ModeRegistry::reset();
203        $fresh = ModeRegistry::getInstance();
204        $this->assertSame([], $fresh->getBlockEolModes());
205    }
206
207    /**
208     * The default syntax setting must produce the exact same mode set as before
209     * the syntax setting was introduced (no-op guarantee).
210     */
211    function testGetModesDefaultSyntaxMatchesLegacy()
212    {
213        global $conf;
214        $conf['syntax'] = 'dokuwiki';
215        ModeRegistry::reset();
216        $registry = ModeRegistry::getInstance();
217        $modes = $registry->getModes();
218        $modeNames = array_column($modes, 'mode');
219
220        // All original built-in modes must be present
221        $expected = [
222            'listblock', 'preformatted', 'notoc', 'nocache',
223            'header', 'table', 'linebreak', 'footnote',
224            'hr', 'unformatted', 'code', 'file', 'quote',
225            'internallink', 'rss', 'media', 'externallink',
226            'emaillink', 'windowssharelink', 'eol',
227            'strong', 'emphasis', 'underline', 'monospace',
228            'subscript', 'superscript', 'deleted',
229            'smiley', 'acronym', 'entity',
230        ];
231        foreach ($expected as $mode) {
232            $this->assertContains($mode, $modeNames, "Mode '$mode' missing in dokuwiki syntax setting");
233        }
234    }
235
236    /** DW-only modes must be absent when syntax is 'markdown' */
237    function testGetModesDwModesSkippedInMarkdownOnly()
238    {
239        global $conf;
240        $conf['syntax'] = 'markdown';
241        ModeRegistry::reset();
242        $registry = ModeRegistry::getInstance();
243        $modes = $registry->getModes();
244        $modeNames = array_column($modes, 'mode');
245
246        $dwOnly = [
247            'emphasis', 'deleted', 'code', 'header', 'hr',
248            'linebreak', 'internallink', 'media', 'listblock', 'table',
249        ];
250        foreach ($dwOnly as $mode) {
251            $this->assertNotContains($mode, $modeNames, "DW mode '$mode' should not load in markdown-only mode");
252        }
253    }
254
255    /** Always-loaded modes must still be present in markdown-only mode */
256    function testGetModesAlwaysModesPresentInMarkdownOnly()
257    {
258        global $conf;
259        $conf['syntax'] = 'markdown';
260        ModeRegistry::reset();
261        $registry = ModeRegistry::getInstance();
262        $modes = $registry->getModes();
263        $modeNames = array_column($modes, 'mode');
264
265        $always = [
266            'strong', 'underline', 'monospace', 'subscript', 'superscript',
267            'footnote', 'eol', 'unformatted', 'preformatted', 'file',
268            'quote', 'externallink', 'emaillink', 'windowssharelink',
269            'notoc', 'nocache', 'rss',
270            'smiley', 'acronym', 'entity',
271        ];
272        foreach ($always as $mode) {
273            $this->assertContains($mode, $modeNames, "Always-loaded mode '$mode' missing in markdown syntax setting");
274        }
275    }
276
277    /** In mixed modes, DW modes must still load */
278    function testGetModesMixedModesLoadDwModes()
279    {
280        $dwOnly = [
281            'emphasis', 'deleted', 'code', 'header', 'hr',
282            'linebreak', 'internallink', 'media', 'listblock', 'table',
283        ];
284
285        foreach (['dw+md', 'md+dw'] as $syntax) {
286            global $conf;
287            $conf['syntax'] = $syntax;
288            ModeRegistry::reset();
289            $registry = ModeRegistry::getInstance();
290            $modes = $registry->getModes();
291            $modeNames = array_column($modes, 'mode');
292
293            foreach ($dwOnly as $mode) {
294                $this->assertContains($mode, $modeNames, "DW mode '$mode' missing in '$syntax' syntax setting");
295            }
296        }
297    }
298
299}
300