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