xref: /dokuwiki/_test/tests/Parsing/ParserMode/GfmQuoteTest.php (revision dccbd514161d242fdd8495ae23b9690bc52463c9)
1309a0852SAndreas Gohr<?php
2309a0852SAndreas Gohr
3309a0852SAndreas Gohrnamespace dokuwiki\test\Parsing\ParserMode;
4309a0852SAndreas Gohr
5309a0852SAndreas Gohruse dokuwiki\Parsing\ModeRegistry;
6309a0852SAndreas Gohruse dokuwiki\Parsing\ParserMode\GfmQuote;
7*dccbd514SAndreas Gohruse dokuwiki\Parsing\ParserMode\GfmTable;
8*dccbd514SAndreas Gohruse dokuwiki\Parsing\ParserMode\Listblock;
9*dccbd514SAndreas Gohruse dokuwiki\Parsing\ParserMode\Table;
10309a0852SAndreas Gohr
11309a0852SAndreas Gohr/**
12309a0852SAndreas Gohr * Tests for GFM-style block quotes.
13309a0852SAndreas Gohr *
14309a0852SAndreas Gohr * GfmQuote is the unified blockquote implementation covering both DW and
15309a0852SAndreas Gohr * GFM dialects. The mode captures the entire quote via addSpecialPattern
16309a0852SAndreas Gohr * and sub-parses the stripped body, so the outer parser only needs
17309a0852SAndreas Gohr * gfm_quote attached; inline modes and block modes (lists, code blocks,
18309a0852SAndreas Gohr * nested quotes) are picked up by the sub-parser.
19309a0852SAndreas Gohr *
20309a0852SAndreas Gohr * Two rendering shapes are exercised. Under DW-preferred syntax, a
21309a0852SAndreas Gohr * post-pass flattens the sub-parser's paragraph wrapping into linebreak-
22309a0852SAndreas Gohr * separated cdata so existing DW pages keep their `<br/>`-between-lines
23309a0852SAndreas Gohr * rendering. Under MD-preferred syntax the sub-parser's paragraph
24309a0852SAndreas Gohr * wrapping survives — a quote with one paragraph emits
25309a0852SAndreas Gohr * `<blockquote><p>...</p></blockquote>`.
26309a0852SAndreas Gohr */
27309a0852SAndreas Gohrclass GfmQuoteTest extends ParserTestBase
28309a0852SAndreas Gohr{
29309a0852SAndreas Gohr    public function tearDown(): void
30309a0852SAndreas Gohr    {
31309a0852SAndreas Gohr        ModeRegistry::reset();
32309a0852SAndreas Gohr        parent::tearDown();
33309a0852SAndreas Gohr    }
34309a0852SAndreas Gohr
35309a0852SAndreas Gohr    private function setSyntax(string $syntax): void
36309a0852SAndreas Gohr    {
37309a0852SAndreas Gohr        global $conf;
38309a0852SAndreas Gohr        $conf['syntax'] = $syntax;
39309a0852SAndreas Gohr        ModeRegistry::reset();
40309a0852SAndreas Gohr    }
41309a0852SAndreas Gohr
42309a0852SAndreas Gohr    /**
43309a0852SAndreas Gohr     * Recursively flatten call lists, descending into `nest` content.
44309a0852SAndreas Gohr     * Useful for tests that just check whether an instruction appears
45309a0852SAndreas Gohr     * somewhere in the rendered output regardless of nesting depth.
46309a0852SAndreas Gohr     */
47309a0852SAndreas Gohr    private function flatNames(array $calls): array
48309a0852SAndreas Gohr    {
49309a0852SAndreas Gohr        $names = [];
50309a0852SAndreas Gohr        foreach ($calls as $call) {
51309a0852SAndreas Gohr            $names[] = $call[0];
52309a0852SAndreas Gohr            if ($call[0] === 'nest') {
53309a0852SAndreas Gohr                $names = array_merge($names, $this->flatNames($call[1][0]));
54309a0852SAndreas Gohr            }
55309a0852SAndreas Gohr        }
56309a0852SAndreas Gohr        return $names;
57309a0852SAndreas Gohr    }
58309a0852SAndreas Gohr
59309a0852SAndreas Gohr    public function testSortValue()
60309a0852SAndreas Gohr    {
61309a0852SAndreas Gohr        $mode = new GfmQuote();
62309a0852SAndreas Gohr        $this->assertSame(220, $mode->getSort());
63309a0852SAndreas Gohr    }
64309a0852SAndreas Gohr
65309a0852SAndreas Gohr    // ----- DW-preferred rendering: linebreak-separated, no <p> ------------
66309a0852SAndreas Gohr
67309a0852SAndreas Gohr    public function testDwSingleLine()
68309a0852SAndreas Gohr    {
6913a62f81SAndreas Gohr        $this->setSyntax('dw');
70309a0852SAndreas Gohr        $this->P->addMode('gfm_quote', new GfmQuote());
71309a0852SAndreas Gohr        $this->P->parse("> foo\n");
72309a0852SAndreas Gohr
73309a0852SAndreas Gohr        $expected = [
74309a0852SAndreas Gohr            ['document_start', []],
75309a0852SAndreas Gohr            ['quote_open', []],
76309a0852SAndreas Gohr            ['nest', [[ ['cdata', ['foo']] ]]],
77309a0852SAndreas Gohr            ['quote_close', []],
78309a0852SAndreas Gohr            ['document_end', []],
79309a0852SAndreas Gohr        ];
80309a0852SAndreas Gohr        $this->assertCalls($expected, $this->H->calls);
81309a0852SAndreas Gohr    }
82309a0852SAndreas Gohr
83309a0852SAndreas Gohr    public function testDwSpaceAfterMarkerOptional()
84309a0852SAndreas Gohr    {
85309a0852SAndreas Gohr        // GFM allows omitting the space after `>`; DW always did. Strip
86309a0852SAndreas Gohr        // logic removes one optional space after the `>`, so `>foo` and
87309a0852SAndreas Gohr        // `> foo` both produce cdata "foo".
8813a62f81SAndreas Gohr        $this->setSyntax('dw');
89309a0852SAndreas Gohr        $this->P->addMode('gfm_quote', new GfmQuote());
90309a0852SAndreas Gohr        $this->P->parse(">foo\n");
91309a0852SAndreas Gohr
92309a0852SAndreas Gohr        $names = $this->flatNames($this->H->calls);
93309a0852SAndreas Gohr        $this->assertContains('quote_open', $names);
94309a0852SAndreas Gohr        $this->assertContains('cdata', $names);
95309a0852SAndreas Gohr    }
96309a0852SAndreas Gohr
97309a0852SAndreas Gohr    public function testDwTwoLinesEmitLinebreak()
98309a0852SAndreas Gohr    {
99309a0852SAndreas Gohr        // The DW-preferred post-pass converts the sub-parser's paragraph
100309a0852SAndreas Gohr        // wrapping into a linebreak between the two cdata calls, matching
101309a0852SAndreas Gohr        // the historical `<blockquote>foo<br/>bar</blockquote>` shape.
10213a62f81SAndreas Gohr        $this->setSyntax('dw');
103309a0852SAndreas Gohr        $this->P->addMode('gfm_quote', new GfmQuote());
104309a0852SAndreas Gohr        $this->P->parse("> foo\n> bar\n");
105309a0852SAndreas Gohr
106309a0852SAndreas Gohr        $expected = [
107309a0852SAndreas Gohr            ['document_start', []],
108309a0852SAndreas Gohr            ['quote_open', []],
109309a0852SAndreas Gohr            ['nest', [[
110309a0852SAndreas Gohr                ['cdata', ['foo']],
111309a0852SAndreas Gohr                ['linebreak', []],
112309a0852SAndreas Gohr                ['cdata', ['bar']],
113309a0852SAndreas Gohr            ]]],
114309a0852SAndreas Gohr            ['quote_close', []],
115309a0852SAndreas Gohr            ['document_end', []],
116309a0852SAndreas Gohr        ];
117309a0852SAndreas Gohr        $this->assertCalls($expected, $this->H->calls);
118309a0852SAndreas Gohr    }
119309a0852SAndreas Gohr
120309a0852SAndreas Gohr    public function testDwBlankMarkerLineEmitsTwoLinebreaks()
121309a0852SAndreas Gohr    {
122309a0852SAndreas Gohr        // `>` alone between content lines is a paragraph break in GFM.
123309a0852SAndreas Gohr        // The DW post-pass replaces each p_open and each p_close with a
124309a0852SAndreas Gohr        // linebreak, producing two adjacent linebreak calls between the
125309a0852SAndreas Gohr        // two content cdata — matches the historical DW two-`<br/>` shape.
12613a62f81SAndreas Gohr        $this->setSyntax('dw');
127309a0852SAndreas Gohr        $this->P->addMode('gfm_quote', new GfmQuote());
128309a0852SAndreas Gohr        $this->P->parse("> foo\n>\n> bar\n");
129309a0852SAndreas Gohr
130309a0852SAndreas Gohr        $expected = [
131309a0852SAndreas Gohr            ['document_start', []],
132309a0852SAndreas Gohr            ['quote_open', []],
133309a0852SAndreas Gohr            ['nest', [[
134309a0852SAndreas Gohr                ['cdata', ['foo']],
135309a0852SAndreas Gohr                ['linebreak', []],
136309a0852SAndreas Gohr                ['linebreak', []],
137309a0852SAndreas Gohr                ['cdata', ['bar']],
138309a0852SAndreas Gohr            ]]],
139309a0852SAndreas Gohr            ['quote_close', []],
140309a0852SAndreas Gohr            ['document_end', []],
141309a0852SAndreas Gohr        ];
142309a0852SAndreas Gohr        $this->assertCalls($expected, $this->H->calls);
143309a0852SAndreas Gohr    }
144309a0852SAndreas Gohr
145309a0852SAndreas Gohr    public function testDwNested()
146309a0852SAndreas Gohr    {
14713a62f81SAndreas Gohr        $this->setSyntax('dw');
148309a0852SAndreas Gohr        $this->P->addMode('gfm_quote', new GfmQuote());
149309a0852SAndreas Gohr        $this->P->parse("> > foo\n");
150309a0852SAndreas Gohr
151309a0852SAndreas Gohr        // The outer captures a single line `> > foo`. Stripping the
152309a0852SAndreas Gohr        // outer marker leaves `> foo`, which the sub-parser feeds back
153309a0852SAndreas Gohr        // through GfmQuote — recursion produces a nested quote_open /
154309a0852SAndreas Gohr        // quote_close pair carrying the cdata.
155309a0852SAndreas Gohr        $names = $this->flatNames($this->H->calls);
156309a0852SAndreas Gohr        $opens  = array_filter($names, static fn($n) => $n === 'quote_open');
157309a0852SAndreas Gohr        $closes = array_filter($names, static fn($n) => $n === 'quote_close');
158309a0852SAndreas Gohr        $this->assertCount(2, $opens, 'two levels of quote_open expected');
159309a0852SAndreas Gohr        $this->assertCount(2, $closes, 'two levels of quote_close expected');
160309a0852SAndreas Gohr    }
161309a0852SAndreas Gohr
162309a0852SAndreas Gohr    public function testDwNoLazyContinuation()
163309a0852SAndreas Gohr    {
164309a0852SAndreas Gohr        // GfmQuote does not implement lazy continuation: every quote
165309a0852SAndreas Gohr        // line must begin with `>`. `bar` without a `>` prefix terminates
166309a0852SAndreas Gohr        // the quote, so it ends up as a separate paragraph — matching
167309a0852SAndreas Gohr        // today's DW behavior.
16813a62f81SAndreas Gohr        $this->setSyntax('dw');
169309a0852SAndreas Gohr        $this->P->addMode('gfm_quote', new GfmQuote());
170309a0852SAndreas Gohr        $this->P->parse("> foo\nbar\n");
171309a0852SAndreas Gohr
172309a0852SAndreas Gohr        $opens = array_filter($this->H->calls, static fn($c) => $c[0] === 'quote_open');
173309a0852SAndreas Gohr        $this->assertCount(1, $opens, 'quote opens once and stops at the non-`>` line');
174309a0852SAndreas Gohr
175309a0852SAndreas Gohr        // `bar` is outside the quote — find a top-level cdata after the close
176309a0852SAndreas Gohr        $afterClose = false;
177309a0852SAndreas Gohr        $sawBarOutside = false;
178309a0852SAndreas Gohr        foreach ($this->H->calls as $call) {
179309a0852SAndreas Gohr            if ($call[0] === 'quote_close') $afterClose = true;
180309a0852SAndreas Gohr            if ($afterClose && $call[0] === 'cdata' && str_contains($call[1][0], 'bar')) {
181309a0852SAndreas Gohr                $sawBarOutside = true;
182309a0852SAndreas Gohr            }
183309a0852SAndreas Gohr        }
184309a0852SAndreas Gohr        $this->assertTrue($sawBarOutside, '`bar` must appear as cdata outside the quote');
185309a0852SAndreas Gohr    }
186309a0852SAndreas Gohr
187309a0852SAndreas Gohr    public function testDwBlankLineSeparatesQuotes()
188309a0852SAndreas Gohr    {
189309a0852SAndreas Gohr        // A truly blank line ends the quote. The next `>` starts a new
190309a0852SAndreas Gohr        // quote, producing two distinct quote_open / quote_close pairs.
19113a62f81SAndreas Gohr        $this->setSyntax('dw');
192309a0852SAndreas Gohr        $this->P->addMode('gfm_quote', new GfmQuote());
193309a0852SAndreas Gohr        $this->P->parse("> foo\n\n> bar\n");
194309a0852SAndreas Gohr
195309a0852SAndreas Gohr        $opens = array_filter($this->H->calls, static fn($c) => $c[0] === 'quote_open');
196309a0852SAndreas Gohr        $closes = array_filter($this->H->calls, static fn($c) => $c[0] === 'quote_close');
197309a0852SAndreas Gohr        $this->assertCount(2, $opens, 'two distinct quote blocks');
198309a0852SAndreas Gohr        $this->assertCount(2, $closes);
199309a0852SAndreas Gohr    }
200309a0852SAndreas Gohr
201309a0852SAndreas Gohr    public function testDwHeaderInsideQuoteStaysCdata()
202309a0852SAndreas Gohr    {
203309a0852SAndreas Gohr        // Sub-parser excludes BASEONLY (Header / GfmHeader). Header
204309a0852SAndreas Gohr        // instructions drive section-edit anchors and TOC entries that
205309a0852SAndreas Gohr        // do not compose with `<blockquote>`. `# Foo` therefore stays
206309a0852SAndreas Gohr        // as plain cdata text.
20713a62f81SAndreas Gohr        $this->setSyntax('dw');
208309a0852SAndreas Gohr        $this->P->addMode('gfm_quote', new GfmQuote());
209309a0852SAndreas Gohr        $this->P->parse("> # Foo\n");
210309a0852SAndreas Gohr
211309a0852SAndreas Gohr        $names = $this->flatNames($this->H->calls);
212309a0852SAndreas Gohr        $this->assertNotContains('header', $names);
213309a0852SAndreas Gohr        $this->assertNotContains('section_open', $names);
214309a0852SAndreas Gohr        $this->assertContains('cdata', $names);
215309a0852SAndreas Gohr    }
216309a0852SAndreas Gohr
217309a0852SAndreas Gohr    // ----- MD-preferred rendering: paragraph wrapping survives ------------
218309a0852SAndreas Gohr
219309a0852SAndreas Gohr    public function testMdSingleParagraph()
220309a0852SAndreas Gohr    {
22113a62f81SAndreas Gohr        $this->setSyntax('md');
222309a0852SAndreas Gohr        $this->P->addMode('gfm_quote', new GfmQuote());
223309a0852SAndreas Gohr        $this->P->parse("> foo\n> bar\n");
224309a0852SAndreas Gohr
225309a0852SAndreas Gohr        // Sub-parser wraps the body in `p_open` / `p_close`. The outer
226309a0852SAndreas Gohr        // wraps them inside a `nest`, and Block treats the nest as
227309a0852SAndreas Gohr        // opaque. Two `>`-content lines join into one paragraph.
228309a0852SAndreas Gohr        $expected = [
229309a0852SAndreas Gohr            ['document_start', []],
230309a0852SAndreas Gohr            ['quote_open', []],
231309a0852SAndreas Gohr            ['nest', [[
232309a0852SAndreas Gohr                ['p_open', []],
233309a0852SAndreas Gohr                ['cdata', ["foo\nbar"]],
234309a0852SAndreas Gohr                ['p_close', []],
235309a0852SAndreas Gohr            ]]],
236309a0852SAndreas Gohr            ['quote_close', []],
237309a0852SAndreas Gohr            ['document_end', []],
238309a0852SAndreas Gohr        ];
239309a0852SAndreas Gohr        $this->assertCalls($expected, $this->H->calls);
240309a0852SAndreas Gohr    }
241309a0852SAndreas Gohr
242309a0852SAndreas Gohr    public function testMdMultiParagraph()
243309a0852SAndreas Gohr    {
244309a0852SAndreas Gohr        // `>` alone between content lines creates two paragraphs in one
245309a0852SAndreas Gohr        // blockquote — under MD-preferred the post-pass does not run, so
246309a0852SAndreas Gohr        // the sub-parser's `p_open` / `p_close` pairs survive intact.
24713a62f81SAndreas Gohr        $this->setSyntax('md');
248309a0852SAndreas Gohr        $this->P->addMode('gfm_quote', new GfmQuote());
249309a0852SAndreas Gohr        $this->P->parse("> foo\n>\n> bar\n");
250309a0852SAndreas Gohr
251309a0852SAndreas Gohr        $names = $this->flatNames($this->H->calls);
252309a0852SAndreas Gohr        $pOpens = array_filter($names, static fn($n) => $n === 'p_open');
253309a0852SAndreas Gohr        $pCloses = array_filter($names, static fn($n) => $n === 'p_close');
254309a0852SAndreas Gohr        $this->assertCount(2, $pOpens, 'two paragraphs inside one blockquote');
255309a0852SAndreas Gohr        $this->assertCount(2, $pCloses);
256309a0852SAndreas Gohr    }
257309a0852SAndreas Gohr
258*dccbd514SAndreas Gohr    // ----- Handoff from preceding block modes ----------------------------
259*dccbd514SAndreas Gohr    //
260*dccbd514SAndreas Gohr    // GfmTable, DW Table, and DW Listblock all consume the boundary \n on
261*dccbd514SAndreas Gohr    // their way out (their exit pattern is \n by structural necessity: at
262*dccbd514SAndreas Gohr    // the boundary there is no leading unmatched content for a zero-width
263*dccbd514SAndreas Gohr    // lookahead exit to attach to). The pattern (?:^|\n)>... lets GfmQuote
264*dccbd514SAndreas Gohr    // open at the line that starts the blockquote regardless of whether
265*dccbd514SAndreas Gohr    // the preceding mode left the \n in the stream.
266*dccbd514SAndreas Gohr
267*dccbd514SAndreas Gohr    public function testHandoffFromGfmTable()
268*dccbd514SAndreas Gohr    {
269*dccbd514SAndreas Gohr        // Spec example 201: a `>` line immediately following a GFM table
270*dccbd514SAndreas Gohr        // ends the table and opens a blockquote. GfmTable's exit consumes
271*dccbd514SAndreas Gohr        // the boundary \n, so GfmQuote relies on the line-start (^)
272*dccbd514SAndreas Gohr        // alternative to fire here.
273*dccbd514SAndreas Gohr        $this->setSyntax('md');
274*dccbd514SAndreas Gohr        $this->P->addMode('gfm_table', new GfmTable());
275*dccbd514SAndreas Gohr        $this->P->addMode('gfm_quote', new GfmQuote());
276*dccbd514SAndreas Gohr        $this->P->parse("| abc | def |\n| --- | --- |\n| bar | baz |\n> bar");
277*dccbd514SAndreas Gohr
278*dccbd514SAndreas Gohr        $names = array_map(static fn($c) => $c[0], $this->H->calls);
279*dccbd514SAndreas Gohr        $this->assertContains('table_open', $names);
280*dccbd514SAndreas Gohr        $this->assertContains('table_close', $names);
281*dccbd514SAndreas Gohr        $this->assertContains('quote_open', $names);
282*dccbd514SAndreas Gohr        $this->assertContains('quote_close', $names);
283*dccbd514SAndreas Gohr
284*dccbd514SAndreas Gohr        // Order: the quote must open after the table closes.
285*dccbd514SAndreas Gohr        $tableCloseIdx = array_search('table_close', $names, true);
286*dccbd514SAndreas Gohr        $quoteOpenIdx  = array_search('quote_open', $names, true);
287*dccbd514SAndreas Gohr        $this->assertGreaterThan($tableCloseIdx, $quoteOpenIdx);
288*dccbd514SAndreas Gohr    }
289*dccbd514SAndreas Gohr
290*dccbd514SAndreas Gohr    public function testHandoffFromDwTable()
291*dccbd514SAndreas Gohr    {
292*dccbd514SAndreas Gohr        // Same as the GFM-table case for a DW-style table. DW Table's
293*dccbd514SAndreas Gohr        // exit also consumes \n, so the line-start (^) alternative is
294*dccbd514SAndreas Gohr        // what lets the blockquote open.
295*dccbd514SAndreas Gohr        $this->setSyntax('dw');
296*dccbd514SAndreas Gohr        $this->P->addMode('table', new Table());
297*dccbd514SAndreas Gohr        $this->P->addMode('gfm_quote', new GfmQuote());
298*dccbd514SAndreas Gohr        $this->P->parse("| foo | bar |\n> baz");
299*dccbd514SAndreas Gohr
300*dccbd514SAndreas Gohr        $names = array_map(static fn($c) => $c[0], $this->H->calls);
301*dccbd514SAndreas Gohr        $this->assertContains('table_open', $names);
302*dccbd514SAndreas Gohr        $this->assertContains('table_close', $names);
303*dccbd514SAndreas Gohr        $this->assertContains('quote_open', $names);
304*dccbd514SAndreas Gohr        $this->assertContains('quote_close', $names);
305*dccbd514SAndreas Gohr
306*dccbd514SAndreas Gohr        $tableCloseIdx = array_search('table_close', $names, true);
307*dccbd514SAndreas Gohr        $quoteOpenIdx  = array_search('quote_open', $names, true);
308*dccbd514SAndreas Gohr        $this->assertGreaterThan($tableCloseIdx, $quoteOpenIdx);
309*dccbd514SAndreas Gohr    }
310*dccbd514SAndreas Gohr
311*dccbd514SAndreas Gohr    public function testHandoffFromDwListblock()
312*dccbd514SAndreas Gohr    {
313*dccbd514SAndreas Gohr        // DW Listblock also exits on \n, consuming the boundary. The
314*dccbd514SAndreas Gohr        // line-start alternative lets a `>` line right after the list
315*dccbd514SAndreas Gohr        // open a blockquote without an intervening blank line.
316*dccbd514SAndreas Gohr        $this->setSyntax('dw');
317*dccbd514SAndreas Gohr        $this->P->addMode('listblock', new Listblock());
318*dccbd514SAndreas Gohr        $this->P->addMode('gfm_quote', new GfmQuote());
319*dccbd514SAndreas Gohr        $this->P->parse("  * foo\n  * bar\n> baz");
320*dccbd514SAndreas Gohr
321*dccbd514SAndreas Gohr        $names = array_map(static fn($c) => $c[0], $this->H->calls);
322*dccbd514SAndreas Gohr        $this->assertContains('listu_open', $names);
323*dccbd514SAndreas Gohr        $this->assertContains('listu_close', $names);
324*dccbd514SAndreas Gohr        $this->assertContains('quote_open', $names);
325*dccbd514SAndreas Gohr        $this->assertContains('quote_close', $names);
326*dccbd514SAndreas Gohr
327*dccbd514SAndreas Gohr        $listCloseIdx  = array_search('listu_close', $names, true);
328*dccbd514SAndreas Gohr        $quoteOpenIdx  = array_search('quote_open', $names, true);
329*dccbd514SAndreas Gohr        $this->assertGreaterThan($listCloseIdx, $quoteOpenIdx);
330*dccbd514SAndreas Gohr    }
331*dccbd514SAndreas Gohr
332309a0852SAndreas Gohr    public function testMdListInsideQuote()
333309a0852SAndreas Gohr    {
334309a0852SAndreas Gohr        // GfmListblock is loaded under MD-preferred syntax, so a list
335309a0852SAndreas Gohr        // inside a quote parses as a real list. The sub-parser's list
336309a0852SAndreas Gohr        // calls land inside the outer `nest` wrapper.
33713a62f81SAndreas Gohr        $this->setSyntax('md');
338309a0852SAndreas Gohr        ModeRegistry::reset();
339309a0852SAndreas Gohr        // Add the registry's full mode set so gfm_listblock is reachable
340309a0852SAndreas Gohr        // via the sub-parser (the sub-parser uses ModeRegistry::getModes,
341309a0852SAndreas Gohr        // which honors $conf['syntax']).
342309a0852SAndreas Gohr        foreach (ModeRegistry::getInstance()->getModes() as $m) {
343309a0852SAndreas Gohr            $this->P->addMode($m['mode'], $m['obj']);
344309a0852SAndreas Gohr        }
345309a0852SAndreas Gohr
346309a0852SAndreas Gohr        $this->P->parse("> - foo\n> - bar\n");
347309a0852SAndreas Gohr
348309a0852SAndreas Gohr        $names = $this->flatNames($this->H->calls);
349309a0852SAndreas Gohr        $this->assertContains('quote_open', $names);
350309a0852SAndreas Gohr        $this->assertContains('listu_open', $names, 'list inside quote must parse');
351309a0852SAndreas Gohr        $this->assertContains('listu_close', $names);
352309a0852SAndreas Gohr        $this->assertContains('quote_close', $names);
353309a0852SAndreas Gohr    }
354309a0852SAndreas Gohr}
355