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