packages/deriver/tests/Unit/Finder/PathPatternTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Finder;
6
7use Deriver\Exception\InvalidInputException;
8use Deriver\Finder\PathPattern;
9use PHPUnit\Framework\Attributes\CoversClass;
10use PHPUnit\Framework\Attributes\DataProvider;
11use PHPUnit\Framework\Attributes\Small;
12use PHPUnit\Framework\Attributes\UsesClass;
13use PHPUnit\Framework\TestCase;
14
15#[CoversClass(PathPattern::class)]
16#[UsesClass(InvalidInputException::class)]
17#[Small]
18final class PathPatternTest extends TestCase
19{
20 public function testMatchesAnyAcceptsOneMatchAndRejectsEmptyAlternatives(): void
21 {
22 $patterns = [new PathPattern('src/*.php'), new PathPattern('tests/**/*.php')];
23 self::assertTrue(PathPattern::matchesAny($patterns, 'tests/deep/Test.php'));
24 self::assertFalse(PathPattern::matchesAny($patterns, 'vendor/File.php'));
25 self::assertFalse(PathPattern::matchesAny([], 'src/File.php'));
26 }
27
28 #[DataProvider('providerPatterns')]
29 public function testMatchesCompleteRelativePaths(string $pattern, string $path, bool $expected): void
30 {
31 self::assertSame($expected, (new PathPattern($pattern))->matches($path));
32 }
33
34 /**
35 * @return iterable<string, array{string, string, bool}>
36 */
37 public static function providerPatterns(): iterable
38 {
39 yield 'zero directories' => ['**/*.php', 'a.php', true];
40 yield 'multiple directories' => ['**/*.php', 'a/b/c.php', true];
41 yield 'one component' => ['*.php', 'a/b.php', false];
42 yield 'any extension' => ['src/**', 'src/deep/a.inc', true];
43 yield 'prunable directory' => ['**/vendor/**', 'src/vendor/', true];
44 yield 'root anchored' => ['vendor/**', 'src/vendor/a.php', false];
45 yield 'suffix required' => ['**/*.php', 'a.php.txt', false];
46 yield 'case sensitive' => ['**/*.php', 'A.PHP', false];
47 yield 'literal punctuation' => ['a[1]+(x).php', 'a[1]+(x).php', true];
48 yield 'no regex interpretation' => ['a+.php', 'aa.php', false];
49 yield 'dot prefix and separators' => ['././src\\*.php', 'src/a.php', true];
50 yield 'question does not cross directories' => ['a?b', 'a/b', false];
51 }
52
53 #[DataProvider('providerInvalidPatterns')]
54 public function testRejectsPatternsOutsideTheRoot(string $pattern): void
55 {
56 $this->expectException(InvalidInputException::class);
57 new PathPattern($pattern);
58 }
59
60 /**
61 * @return iterable<array{string}>
62 */
63 public static function providerInvalidPatterns(): iterable
64 {
65 foreach (['', './', '/src/*.php', '../*.php', 'src/../../*', 'php://*', 'C:\\src\\*'] as $pattern) {
66 yield [$pattern];
67 }
68 }
69}
70