packages/requirements/tests/Unit/Config/Markdown/QuotationBlocksTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Config\Markdown;
6
7use League\CommonMark\Exception\CommonMarkException;
8use PHPUnit\Framework\Attributes\CoversClass;
9use PHPUnit\Framework\Attributes\Small;
10use PHPUnit\Framework\Attributes\UsesClass;
11use PHPUnit\Framework\TestCase;
12use Requirements\Config\Markdown\Citation;
13use Requirements\Config\Markdown\LocalPath;
14use Requirements\Config\Markdown\Nodes;
15use Requirements\Config\Markdown\Presentation;
16use Requirements\Config\Markdown\Quotation;
17use Requirements\Config\Markdown\QuotationBlocks;
18use Requirements\Input\InvalidInputException;
19use Requirements\Model\Source;
20use Requirements\Source\TextFragment;
21use Requirements\Source\Unit;
22use Tests\Fake\MarkdownNodes;
23
24#[CoversClass(QuotationBlocks::class)]
25#[UsesClass(Citation::class)]
26#[UsesClass(LocalPath::class)]
27#[UsesClass(Nodes::class)]
28#[UsesClass(Presentation::class)]
29#[UsesClass(Quotation::class)]
30#[UsesClass(Source::class)]
31#[UsesClass(TextFragment::class)]
32#[UsesClass(Unit::class)]
33#[Small]
34final class QuotationBlocksTest extends TestCase
35{
36    /**
37     * @throws CommonMarkException
38     */
39    public function testReadReadsTheLeadingQuotationsAndTheirAttributions(): void
40    {
41        $presentation = new Presentation();
42        $blocks = MarkdownNodes::blocks("> Names shall start with a letter.\n\n[Names](../source.html#a)\n\n> Names may contain digits.\n>\n> [Digits](../source.html#:~:text=Names%20may%20contain%20digits.)\n\n> <!-- selector: #c -->\n> The generator shall produce C code.\n\n**reason**\n\nWhy.\n");
43        [$evidence, $rest] = (new QuotationBlocks($presentation, new Citation(new Source('manual', 'source.html', 'html', 'main p'), '/project/definitions/names.md', '/project')))->read($blocks, 'names.md', 'SPEC-001');
44        self::assertEquals([
45            (object) ['selector' => '#a', 'quote' => 'Names shall start with a letter.'],
46            (object) ['selector' => '#:~:text=Names%20may%20contain%20digits.', 'quote' => 'Names may contain digits.'],
47            (object) ['selector' => '#c', 'quote' => 'The generator shall produce C code.'],
48        ], $evidence);
49        self::assertSame(array_slice($blocks, 4), $rest);
50        self::assertSame(['SPEC-001' => [
51            ['url' => '../source.html#a', 'label' => 'Names'],
52            ['url' => '../source.html#:~:text=Names%20may%20contain%20digits.', 'label' => 'Digits'],
53            null,
54        ]], $presentation->citations);
55    }
56
57    /**
58     * @throws CommonMarkException
59     */
60    public function testReadLeavesBlocksWithoutQuotationsUnchanged(): void
61    {
62        $presentation = new Presentation();
63        $blocks = MarkdownNodes::blocks("[Names](source.html#a)\n\n> Names shall start with a letter.\n");
64        self::assertSame([[], $blocks], (new QuotationBlocks($presentation, null))->read($blocks, 'definition.md', 'SPEC-001'));
65        self::assertSame([], $presentation->citations);
66    }
67
68    /**
69     * @throws CommonMarkException
70     */
71    public function testReadLeavesAParagraphWithMoreThanALinkAfterTheQuotation(): void
72    {
73        $blocks = MarkdownNodes::blocks("> <!-- selector: #a -->\n> Names shall start with a letter.\n\n[Names](source.html#a) and more\n");
74        [$evidence, $rest] = (new QuotationBlocks(new Presentation(), new Citation(new Source('manual', 'source.html', 'html', 'main p'), '/project/definition.md', '/project')))->read($blocks, 'definition.md', 'SPEC-001');
75        self::assertEquals([(object) ['selector' => '#a', 'quote' => 'Names shall start with a letter.']], $evidence);
76        self::assertSame([$blocks[1]], $rest);
77    }
78
79    /**
80     * @throws CommonMarkException
81     */
82    public function testReadPrefixesTheErrorsWithTheFileAndItem(): void
83    {
84        $blocks = MarkdownNodes::blocks("> <!-- selector: #a -->\n> Names shall start with a letter.\n");
85        $this->expectException(InvalidInputException::class);
86        $this->expectExceptionMessage('definition.md: SPEC-001: A quotation needs a declared source.');
87        (new QuotationBlocks(new Presentation(), null))->read($blocks, 'definition.md', 'SPEC-001');
88    }
89
90    /**
91     * @throws CommonMarkException
92     */
93    public function testReadRejectsTwoSourceCitations(): void
94    {
95        $blocks = MarkdownNodes::blocks("> Names shall start with a letter.\n>\n> [Source](source.html#a)\n\n[Source](source.html#a)\n");
96        $this->expectException(InvalidInputException::class);
97        $this->expectExceptionMessage('definition.md: SPEC-001: An evidence quotation can have only one source citation.');
98        (new QuotationBlocks(new Presentation(), new Citation(new Source('manual', 'source.html', 'html', 'main p'), '/project/definition.md', '/project')))->read($blocks, 'definition.md', 'SPEC-001');
99    }
100
101    /**
102     * @throws CommonMarkException
103     */
104    public function testReadRejectsAnAttributionWithAnEmptyDestination(): void
105    {
106        $blocks = MarkdownNodes::blocks("> Names shall start with a letter.\n\n[Source]()\n");
107        $this->expectException(InvalidInputException::class);
108        $this->expectExceptionMessage('Expected one Markdown link with a nonempty destination.');
109        (new QuotationBlocks(new Presentation(), new Citation(new Source('manual', 'source.html', 'html', 'main p'), '/project/definition.md', '/project')))->read($blocks, 'definition.md', 'SPEC-001');
110    }
111}
112