packages/sql-catalog/tests/Unit/Core/Text/TextPatternTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Core\Text;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlCatalog\Core\Text\LiteralText;
11use SqlCatalog\Core\Text\Origin;
12use SqlCatalog\Core\Text\TextHole;
13use SqlCatalog\Core\Text\TextPattern;
14use SqlCatalog\Core\Type\TypeShape;
15
16#[CoversClass(TextPattern::class)]
17#[UsesClass(LiteralText::class)]
18#[UsesClass(TextHole::class)]
19#[UsesClass(TypeShape::class)]
20final class TextPatternTest extends TestCase
21{
22 public function testEmptyHasNoSegments(): void
23 {
24 self::assertSame([], TextPattern::empty()->segments);
25 }
26
27 public function testFromTextKeepsTheCharacters(): void
28 {
29 self::assertSame('SELECT 1', TextPattern::fromText('SELECT 1')->display());
30 }
31
32 public function testFromTextDropsAnEmptyString(): void
33 {
34 self::assertSame([], TextPattern::fromText('')->segments);
35 }
36
37 public function testFromHoleHoldsOneGap(): void
38 {
39 $pattern = TextPattern::fromHole(new TextHole(Origin::External, TypeShape::unknown()));
40 self::assertCount(1, $pattern->holes());
41 }
42
43 public function testFromSegmentsCoalescesAdjacentText(): void
44 {
45 $pattern = TextPattern::fromSegments([new LiteralText('SELECT '), new LiteralText('1')]);
46 self::assertCount(1, $pattern->segments);
47 self::assertSame('SELECT 1', $pattern->display());
48 }
49
50 public function testFromSegmentsDropsEmptyText(): void
51 {
52 $pattern = TextPattern::fromSegments([new LiteralText(''), new LiteralText('x')]);
53 self::assertCount(1, $pattern->segments);
54 }
55
56 public function testConcatJoinsTwoPatterns(): void
57 {
58 $joined = TextPattern::fromText('SELECT ')->concat(TextPattern::fromText('1'));
59 self::assertSame('SELECT 1', $joined->text());
60 }
61
62 public function testIsExactOnlyWithoutGaps(): void
63 {
64 self::assertTrue(TextPattern::fromText('SELECT 1')->isExact());
65 self::assertFalse(
66 TextPattern::fromHole(new TextHole(Origin::Loop, TypeShape::unknown()))->isExact(),
67 );
68 }
69
70 public function testTextIsNullWhenAGapRemains(): void
71 {
72 $pattern = TextPattern::fromText('id = ')
73 ->concat(TextPattern::fromHole(new TextHole(Origin::External, TypeShape::unknown())));
74 self::assertNull($pattern->text());
75 }
76
77 public function testHolesAreReturnedInSourceOrder(): void
78 {
79 $first = new TextHole(Origin::External, TypeShape::unknown());
80 $second = new TextHole(Origin::Loop, TypeShape::unknown());
81 $pattern = TextPattern::fromSegments([$first, new LiteralText(' and '), $second]);
82 self::assertSame([$first, $second], $pattern->holes());
83 }
84
85 public function testDisplayShowsGapsAsAMarker(): void
86 {
87 $pattern = TextPattern::fromText('id = ')
88 ->concat(TextPattern::fromHole(new TextHole(Origin::External, TypeShape::unknown())));
89 self::assertSame('id = {$}', $pattern->display());
90 }
91
92 public function testRenderSubstitutesTheGivenStandIn(): void
93 {
94 $pattern = TextPattern::fromText('id = ')
95 ->concat(TextPattern::fromHole(new TextHole(Origin::External, TypeShape::unknown())));
96 self::assertSame('id = gap', $pattern->render('gap'));
97 }
98
99 public function testSignatureKeepsGapOriginsApart(): void
100 {
101 $left = TextPattern::fromHole(new TextHole(Origin::External, TypeShape::unknown()));
102 $right = TextPattern::fromHole(new TextHole(Origin::Loop, TypeShape::unknown()));
103 self::assertNotSame($left->signature(), $right->signature());
104 }
105
106 public function testEqualsComparesShapesRatherThanObjects(): void
107 {
108 self::assertTrue(TextPattern::fromText('a')->equals(TextPattern::fromText('a')));
109 self::assertFalse(TextPattern::fromText('a')->equals(TextPattern::fromText('b')));
110 }
111
112 public function testIsEmptyOnlyWithoutAnySegment(): void
113 {
114 self::assertTrue(TextPattern::empty()->isEmpty());
115 self::assertFalse(TextPattern::fromText('x')->isEmpty());
116 }
117}
118