packages/sql-faker/tests/Unit/Generation/Coverage/LexicalObservationTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\Generation\Coverage;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlFaker\Generation\Coverage\LexicalObservation;
11use SqlFaker\Generation\Lexeme\Lexeme;
12use SqlFaker\Generation\Lexeme\LexemeSequence;
13use SqlFaker\Generation\Lexeme\OutputPart;
14use SqlFaker\Generation\Lexeme\ResolvedOutput;
15use SqlFaker\Generation\Lexeme\SpacingConstraint;
16use SqlFaker\Generation\Token\TerminalOccurrence;
17use SqlFaker\Generation\Token\TerminalSequence;
18
19#[CoversClass(LexicalObservation::class)]
20#[UsesClass(Lexeme::class)]
21#[UsesClass(SpacingConstraint::class)]
22#[UsesClass(LexemeSequence::class)]
23#[UsesClass(OutputPart::class)]
24#[UsesClass(ResolvedOutput::class)]
25#[UsesClass(TerminalOccurrence::class)]
26#[UsesClass(TerminalSequence::class)]
27final class LexicalObservationTest extends TestCase
28{
29 public function testFeaturesKeepsValueTextOutOfFiniteDefinitionSets(): void
30 {
31 $origin = new TerminalOccurrence('COMPOUND', 0);
32 $first = new Lexeme('random-123', 'identifier', $origin, 'identifier-domain');
33 $second = new Lexeme('random-456', 'identifier', $origin, 'identifier-domain');
34 $candidate = new LexemeSequence([$first, $second], 'random-id', provenance: static fn (): array => ['source-a', 'version-case:release-17']);
35 $output = new ResolvedOutput([new OutputPart($first, ' ', 'random-id', ['phrase']), new OutputPart($second, '', 'random-id')], candidates: [$candidate]);
36 self::assertSame(['lexeme' => ['identifier-domain'], 'compound' => ['identifier-domain + identifier-domain'],
37 'version-case' => ['release-17'], 'spacing' => ['phrase:space', 'default:join']], (new LexicalObservation())->features($output));
38 self::assertSame(['lexeme' => [], 'compound' => [], 'version-case' => [], 'spacing' => []], (new LexicalObservation())->features(new ResolvedOutput()));
39 }
40
41 public function testSourcesRetainsAllCandidateProvenanceInOccurrenceOrder(): void
42 {
43 $output = new ResolvedOutput(candidates: [new LexemeSequence([], 'empty', provenance: static fn (): array => ['first', 'second', 'version-case:chosen'])]);
44 self::assertSame([['first', 'second', 'version-case:chosen']], (new LexicalObservation())->sources($output));
45 }
46
47 public function testConditionsRetainsTheInputMasksAndAllContributingRules(): void
48 {
49 $output = new ResolvedOutput(candidates: [new LexemeSequence([], 'empty', new SpacingConstraint(1, ['left']), [0 => new SpacingConstraint(2, ['internal'])]), new LexemeSequence([], 'unconstrained')]);
50 self::assertSame([['left' => ['allowed' => 1, 'rules' => ['left']], 'boundaries' => [['allowed' => 2, 'rules' => ['internal']]]], ['left' => null, 'boundaries' => []]], (new LexicalObservation())->conditions($output));
51 }
52
53 public function testRewritesRetainsEachIntermediateReplacementAndOriginalIdentity(): void
54 {
55 $original = new TerminalOccurrence('A', 7, [2], ['root']);
56 $sequence = new TerminalSequence([$original], [$original]);
57 $sequence = $sequence->replace(0, 1, [$original->replaced('B', 'first')], 'first');
58 $sequence = $sequence->replace(0, 1, [$sequence->terminals[0]->replaced('C', 'second')], 'second');
59 $operations = (new LexicalObservation())->rewrites($sequence);
60 self::assertSame('A', $operations[0]['removed'][0]['name']);
61 self::assertSame('B', $operations[0]['inserted'][0]['name']);
62 self::assertSame($operations[0]['inserted'], $operations[1]['removed']);
63 self::assertSame('C', $operations[1]['inserted'][0]['name']);
64 self::assertSame(['id' => 7, 'name' => 'C', 'ancestors' => [2], 'rules' => ['root'], 'rewrite' => 'second'], $operations[1]['inserted'][0]);
65 }
66}
67