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