packages/requirements/tests/Unit/Config/Markdown/Profile/TextConstraintTest.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\Attributes\UsesClass;
11use PHPUnit\Framework\TestCase;
12use Requirements\Config\Markdown\Profile\TextConstraint;
13use Requirements\Input\Fields;
14use Requirements\Input\InvalidInputException;
15
16#[CoversClass(TextConstraint::class)]
17#[UsesClass(Fields::class)]
18#[Small]
19final class TextConstraintTest extends TestCase
20{
21 /**
22 * @param array<string, mixed> $schema
23 */
24 #[DataProvider('providerTexts')]
25 public function testMatchesEvaluatesConstraints(string $text, array $schema, bool $expected): void
26 {
27 self::assertSame($expected, TextConstraint::matches($text, $schema));
28 }
29
30 /**
31 * @return array<string, array{string, array<string, mixed>, bool}>
32 */
33 public static function providerTexts(): array
34 {
35 return [
36 'no constraint' => ['anything', [], true],
37 'pattern matches' => ['SPEC-001', ['pattern' => '^[A-Za-z][A-Za-z0-9_.-]*$'], true],
38 'pattern does not match' => ['1SPEC', ['pattern' => '^[A-Za-z][A-Za-z0-9_.-]*$'], false],
39 'unanchored pattern matches inside' => ['abc', ['pattern' => 'b'], true],
40 'pattern with tilde' => ['a~b', ['pattern' => '^a~b$'], true],
41 'pattern with tilde does not match' => ['ab', ['pattern' => '^a~b$'], false],
42 'pattern in unicode mode' => ["\u{e9}", ['pattern' => '^.$'], true],
43 'const equal' => ['origin', ['const' => 'origin'], true],
44 'const different' => ['Origin', ['const' => 'origin'], false],
45 'enum contains' => ['b', ['enum' => ['a', 'b']], true],
46 'enum lacks' => ['c', ['enum' => ['a', 'b']], false],
47 'null const ignored' => ['x', ['const' => null], true],
48 'null enum ignored' => ['x', ['enum' => null], true],
49 'pattern matches but const differs' => ['abc', ['pattern' => 'a', 'const' => 'abd'], false],
50 'pattern matches but enum lacks' => ['abc', ['pattern' => 'a', 'enum' => ['abd']], false],
51 'const matches but enum lacks' => ['abc', ['const' => 'abc', 'enum' => ['abd']], false],
52 'every constraint holds' => ['abc', ['pattern' => '^a', 'const' => 'abc', 'enum' => ['abc']], true],
53 ];
54 }
55
56 /**
57 * @param array<string, mixed> $schema
58 */
59 #[DataProvider('providerMalformed')]
60 public function testMatchesRejectsMalformedConstraint(array $schema, string $message): void
61 {
62 $this->expectException(InvalidInputException::class);
63 $this->expectExceptionMessage($message);
64 TextConstraint::matches('text', $schema);
65 }
66
67 /**
68 * @return array<string, array{array<string, mixed>, string}>
69 */
70 public static function providerMalformed(): array
71 {
72 return [
73 'unknown key' => [['format' => 'id'], "text constraint: unknown field 'format'."],
74 'pattern not a string' => [['pattern' => 1], 'pattern must be a nonempty string.'],
75 'blank pattern' => [['pattern' => ' '], 'pattern must be a nonempty string.'],
76 'enum not a list' => [['enum' => 'a'], 'enum must be a list.'],
77 'enum with non-string' => [['enum' => [1]], 'enum must contain nonempty strings.'],
78 ];
79 }
80}
81