packages/requirements/tests/Unit/Config/Markdown/Profile/OccurrencesTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Config\Markdown\Profile;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\Small;
10use PHPUnit\Framework\TestCase;
11use Requirements\Config\Markdown\Profile\Occurrences;
12use Requirements\Input\InvalidInputException;
13
14#[CoversClass(Occurrences::class)]
15#[Small]
16final class OccurrencesTest extends TestCase
17{
18    /**
19     * @param array<string, mixed> $schema
20     */
21    #[DataProvider('providerAccepted')]
22    public function testCheckAcceptsCountWithinBounds(int $count, array $schema): void
23    {
24        Occurrences::check($count, $schema, 'section');
25        $this->addToAssertionCount(1);
26    }
27
28    /**
29     * @return array<string, array{int, array<string, mixed>}>
30     */
31    public static function providerAccepted(): array
32    {
33        return [
34            'default minimum of one' => [1, []],
35            'unbounded default maximum' => [PHP_INT_MAX, []],
36            'explicit zero minimum' => [0, ['minContains' => 0]],
37            'count at maximum' => [2, ['maxContains' => 2]],
38            'count at both bounds' => [2, ['minContains' => 2, 'maxContains' => 2]],
39            'unrelated keys ignored' => [1, ['type' => 'paragraph']],
40        ];
41    }
42
43    /**
44     * @param array<string, mixed> $schema
45     */
46    #[DataProvider('providerRejected')]
47    public function testCheckRejectsCountOrBounds(int $count, array $schema): void
48    {
49        $this->expectException(InvalidInputException::class);
50        $this->expectExceptionMessage('doc.md: section block 2: document-schema occurrence constraint failed.');
51        Occurrences::check($count, $schema, 'doc.md: section block 2');
52    }
53
54    /**
55     * @return array<string, array{int, array<string, mixed>}>
56     */
57    public static function providerRejected(): array
58    {
59        return [
60            'below default minimum' => [0, []],
61            'below explicit minimum' => [1, ['minContains' => 2]],
62            'above maximum' => [3, ['maxContains' => 2]],
63            'above zero maximum' => [1, ['minContains' => 0, 'maxContains' => 0]],
64            'string minimum' => [1, ['minContains' => '1']],
65            'float maximum' => [1, ['maxContains' => 1.5]],
66            'null minimum treated as default' => [0, ['minContains' => null]],
67        ];
68    }
69}
70