packages/sql-catalog/tests/Unit/Core/Catalog/ValueDomainTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Core\Catalog;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlCatalog\Core\Catalog\ValueDomain;
11use SqlCatalog\Core\Evaluation\ArrayTerm;
12use SqlCatalog\Core\Evaluation\Domain;
13use SqlCatalog\Core\Evaluation\LiteralTerm;
14use SqlCatalog\Core\Evaluation\OpaqueTerm;
15use SqlCatalog\Core\Text\Origin;
16use SqlCatalog\Core\Text\TextHole;
17use SqlCatalog\Core\Text\TextPattern;
18use SqlCatalog\Core\Type\TypeShape;
19
20#[CoversClass(ValueDomain::class)]
21#[UsesClass(ArrayTerm::class)]
22#[UsesClass(Domain::class)]
23#[UsesClass(LiteralTerm::class)]
24#[UsesClass(OpaqueTerm::class)]
25#[UsesClass(TextHole::class)]
26#[UsesClass(TextPattern::class)]
27#[UsesClass(TypeShape::class)]
28#[UsesClass(\SqlCatalog\Core\Evaluation\PatternTerm::class)]
29#[UsesClass(\SqlCatalog\Core\Text\LiteralText::class)]
30#[UsesClass(\SqlCatalog\Core\Text\TextGeneralization::class)]
31final class ValueDomainTest extends TestCase
32{
33 public function testFromDomainListsTheResolvedAlternatives(): void
34 {
35 $value = ValueDomain::fromDomain(Domain::literal('active')->union(Domain::literal('banned')));
36 self::assertSame(['active', 'banned'], $value->values);
37 self::assertTrue($value->exhaustive);
38 self::assertSame('string', $value->type);
39 }
40
41 public function testFromDomainReportsOnlyTheTypeWhenAValueDidNotResolve(): void
42 {
43 $value = ValueDomain::fromDomain(Domain::opaque(TypeShape::of(['int', 'null']), Origin::Parameter));
44 self::assertSame([], $value->values);
45 self::assertFalse($value->exhaustive);
46 self::assertSame(['parameter'], $value->origins);
47 self::assertSame('int|null', $value->type);
48 }
49
50 public function testFromDomainIsNotExhaustiveOnceTheAlternativesWereWidened(): void
51 {
52 $widened = Domain::literal('a')->union(Domain::literal('b'))->collapse(Origin::Branch);
53 self::assertFalse(ValueDomain::fromDomain($widened)->exhaustive);
54 }
55
56 public function testFromDomainRecordsAnUnresolvedOriginForANonScalarTerm(): void
57 {
58 self::assertSame(['unresolved'], ValueDomain::fromDomain(Domain::of(new ArrayTerm([])))->origins);
59 }
60
61 public function testIsResolvedNeedsBothExhaustivenessAndValues(): void
62 {
63 self::assertTrue(ValueDomain::fromDomain(Domain::literal('a'))->isResolved());
64 self::assertFalse(ValueDomain::fromDomain(Domain::unknown())->isResolved());
65 }
66
67 public function testAdmitsEverythingWhenNothingWasResolved(): void
68 {
69 self::assertTrue(ValueDomain::fromDomain(Domain::unknown())->admits('anything'));
70 }
71
72 public function testAdmitsOnlyTheResolvedAlternatives(): void
73 {
74 $value = ValueDomain::fromDomain(Domain::literal('active')->union(Domain::literal('banned')));
75 self::assertTrue($value->admits('active'));
76 self::assertFalse($value->admits('deleted'));
77 }
78
79 public function testAdmitsAcrossTheDriverWideningIntegersToStrings(): void
80 {
81 self::assertTrue(ValueDomain::fromDomain(Domain::literal(1))->admits('1'));
82 }
83
84 public function testDisplayWritesTheAlternativesOrTheType(): void
85 {
86 self::assertSame("'a'|1", ValueDomain::fromDomain(Domain::literal('a')->union(Domain::literal(1)))->display());
87 self::assertSame('mixed', ValueDomain::fromDomain(Domain::unknown())->display());
88 }
89}
90