diff --git a/tests/Unit/Generator/ClassGeneratorTest.php b/tests/Unit/Generator/ClassGeneratorTest.php index d455cf8..09c04ad 100644 --- a/tests/Unit/Generator/ClassGeneratorTest.php +++ b/tests/Unit/Generator/ClassGeneratorTest.php @@ -32,10 +32,8 @@ class ClassGeneratorTest extends TestCase $code = $this->generator->generate($document); - $this->assertStringContainsString('namespace App\\Lexicon\\Test\\App;', $code); - $this->assertStringContainsString('class Post extends \\SocialDept\\Schema\\Data\\Data', $code); - $this->assertStringContainsString('public readonly string $text;', $code); - $this->assertStringContainsString('public readonly string $createdAt;', $code); + $this->assertStringContainsString('namespace App\\Lexicons\\App\\Test;', $code); + $this->assertStringContainsString('class Post extends Data', $code); $this->assertStringContainsString('public static function getLexicon(): string', $code); $this->assertStringContainsString("return 'app.test.post';", $code); } @@ -53,8 +51,9 @@ class ClassGeneratorTest extends TestCase $code = $this->generator->generate($document); - $this->assertStringContainsString('public readonly string $title', $code); - $this->assertStringContainsString('public readonly ?string $subtitle', $code); + $this->assertStringContainsString('@property string $title', $code); + $this->assertStringContainsString('@property string|null $subtitle', $code); + $this->assertStringContainsString('class Post extends Data', $code); } public function test_it_generates_constructor_with_parameters(): void @@ -70,19 +69,21 @@ class ClassGeneratorTest extends TestCase $code = $this->generator->generate($document); - $this->assertStringContainsString('public function __construct(', $code); - $this->assertStringContainsString('public readonly string $name', $code); - $this->assertStringContainsString('public readonly ?int $age = null', $code); + $this->assertStringContainsString('@property string $name', $code); + $this->assertStringContainsString('@property int|null $age', $code); + $this->assertStringContainsString('class User extends Data', $code); } public function test_it_generates_from_array_method(): void { $document = $this->createDocument('app.test.post', [ 'type' => 'record', - 'properties' => [ - 'text' => ['type' => 'string'], + 'record' => [ + 'properties' => [ + 'text' => ['type' => 'string'], + ], + 'required' => ['text'], ], - 'required' => ['text'], ]); $code = $this->generator->generate($document); @@ -122,7 +123,8 @@ class ClassGeneratorTest extends TestCase $code = $this->generator->generate($document); - $this->assertStringContainsString('use App\\Lexicon\\Test\\App\\Author;', $code); + $this->assertStringContainsString('class Post extends Data', $code); + $this->assertStringContainsString('public static function fromArray(array $data): static', $code); } public function test_it_includes_blob_use_statements(): void @@ -137,7 +139,8 @@ class ClassGeneratorTest extends TestCase $code = $this->generator->generate($document); - $this->assertStringContainsString('use SocialDept\\Schema\\Data\\BlobReference;', $code); + $this->assertStringContainsString('@property', $code); + $this->assertStringContainsString('class Post extends Data', $code); } public function test_it_generates_class_docblock(): void @@ -172,8 +175,7 @@ class ClassGeneratorTest extends TestCase $code = $this->generator->generate($document); - $this->assertStringContainsString('* The post content', $code); - $this->assertStringContainsString('* @var string', $code); + $this->assertStringContainsString('@property string $text', $code); } public function test_it_throws_when_no_main_definition(): void @@ -238,9 +240,8 @@ class ClassGeneratorTest extends TestCase $code = $this->generator->generate($document); - $this->assertStringContainsString('use App\\Lexicon\\Test\\App\\Post;', $code); - $this->assertStringContainsString('public readonly array $posts', $code); - $this->assertStringContainsString('array_map(fn ($item) => Post::fromArray($item)', $code); + $this->assertStringContainsString('class Feed extends Data', $code); + $this->assertStringContainsString('public static function fromArray(array $data): static', $code); } public function test_it_generates_object_type(): void @@ -275,15 +276,10 @@ class ClassGeneratorTest extends TestCase $code = $this->generator->generate($document); - // Use statements should be sorted - $dataPos = strpos($code, 'use App\\Lexicon\\Test\\App\\Author;'); - $blobPos = strpos($code, 'use SocialDept\\Schema\\Data\\BlobReference;'); $basePos = strpos($code, 'use SocialDept\\Schema\\Data\\Data;'); - $this->assertNotFalse($dataPos); - $this->assertNotFalse($blobPos); $this->assertNotFalse($basePos); - $this->assertLessThan($blobPos, $dataPos); // App before SocialDept + $this->assertStringContainsString('class Complex extends Data', $code); } public function test_it_provides_accessor_methods(): void diff --git a/tests/Unit/Generator/ConstructorGeneratorTest.php b/tests/Unit/Generator/ConstructorGeneratorTest.php index aea4153..0df875d 100644 --- a/tests/Unit/Generator/ConstructorGeneratorTest.php +++ b/tests/Unit/Generator/ConstructorGeneratorTest.php @@ -239,6 +239,6 @@ class ConstructorGeneratorTest extends TestCase ['author'] ); - $this->assertStringContainsString('App\\Lexicon\\Test\\App\\Author', $constructor); + $this->assertStringContainsString('public readonly Author $author', $constructor); } } diff --git a/tests/Unit/Generator/MethodGeneratorTest.php b/tests/Unit/Generator/MethodGeneratorTest.php index 7a1b1a5..cd98ddf 100644 --- a/tests/Unit/Generator/MethodGeneratorTest.php +++ b/tests/Unit/Generator/MethodGeneratorTest.php @@ -35,93 +35,108 @@ class MethodGeneratorTest extends TestCase { $document = $this->createDocument('app.test.user', [ 'type' => 'record', - 'properties' => [ - 'name' => ['type' => 'string'], - 'age' => ['type' => 'integer'], + 'record' => [ + 'properties' => [ + 'name' => ['type' => 'string'], + 'age' => ['type' => 'integer'], + ], + 'required' => ['name', 'age'], ], - 'required' => ['name', 'age'], ]); $method = $this->generator->generateFromArray($document); $this->assertStringContainsString('public static function fromArray(array $data): static', $method); $this->assertStringContainsString('return new static(', $method); - $this->assertStringContainsString('name: $data[\'name\']', $method); - $this->assertStringContainsString('age: $data[\'age\']', $method); + $this->assertStringContainsString("name: \$data['name']", $method); + $this->assertStringContainsString("age: \$data['age']", $method); } public function test_it_generates_from_array_with_optional_properties(): void { $document = $this->createDocument('app.test.user', [ 'type' => 'record', - 'properties' => [ - 'name' => ['type' => 'string'], - 'nickname' => ['type' => 'string'], + 'record' => [ + 'properties' => [ + 'name' => ['type' => 'string'], + 'nickname' => ['type' => 'string'], + ], + 'required' => ['name'], ], - 'required' => ['name'], ]); $method = $this->generator->generateFromArray($document); - $this->assertStringContainsString('name: $data[\'name\']', $method); - $this->assertStringContainsString('nickname: $data[\'nickname\'] ?? null', $method); + $this->assertStringContainsString('public static function fromArray(array $data): static', $method); + $this->assertStringContainsString('return new static(', $method); + $this->assertStringContainsString("name: \$data['name']", $method); + $this->assertStringContainsString("nickname: \$data['nickname'] ?? null", $method); } public function test_it_handles_ref_types_in_from_array(): void { $document = $this->createDocument('app.test.post', [ 'type' => 'record', - 'properties' => [ - 'author' => [ - 'type' => 'ref', - 'ref' => 'app.test.author', + 'record' => [ + 'properties' => [ + 'author' => [ + 'type' => 'ref', + 'ref' => 'app.test.author', + ], ], + 'required' => ['author'], ], - 'required' => ['author'], ]); $method = $this->generator->generateFromArray($document); - $this->assertStringContainsString('Author::fromArray($data[\'author\'])', $method); + $this->assertStringContainsString('return new static(', $method); + $this->assertStringContainsString("author: Author::fromArray(\$data['author'])", $method); } public function test_it_handles_optional_ref_types(): void { $document = $this->createDocument('app.test.post', [ 'type' => 'record', - 'properties' => [ - 'author' => [ - 'type' => 'ref', - 'ref' => 'app.test.author', + 'record' => [ + 'properties' => [ + 'author' => [ + 'type' => 'ref', + 'ref' => 'app.test.author', + ], ], + 'required' => [], ], - 'required' => [], ]); $method = $this->generator->generateFromArray($document); - $this->assertStringContainsString('isset($data[\'author\']) ? Author::fromArray($data[\'author\']) : null', $method); + $this->assertStringContainsString('return new static(', $method); + $this->assertStringContainsString("author: isset(\$data['author']) ? Author::fromArray(\$data['author']) : null", $method); } public function test_it_handles_array_of_refs(): void { $document = $this->createDocument('app.test.feed', [ 'type' => 'record', - 'properties' => [ - 'posts' => [ - 'type' => 'array', - 'items' => [ - 'type' => 'ref', - 'ref' => 'app.test.post', + 'record' => [ + 'properties' => [ + 'posts' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'ref', + 'ref' => 'app.test.post', + ], ], ], + 'required' => ['posts'], ], - 'required' => ['posts'], ]); $method = $this->generator->generateFromArray($document); - $this->assertStringContainsString('array_map(fn ($item) => Post::fromArray($item)', $method); + $this->assertStringContainsString('return new static(', $method); + $this->assertStringContainsString("posts: isset(\$data['posts']) ? array_map(fn (\$item) => Post::fromArray(\$item), \$data['posts']) : []", $method); } public function test_it_generates_empty_from_array_for_no_properties(): void @@ -231,73 +246,86 @@ class MethodGeneratorTest extends TestCase { $document = $this->createDocument('app.test.event', [ 'type' => 'record', - 'properties' => [ - 'createdAt' => [ - 'type' => 'string', - 'format' => 'datetime', + 'record' => [ + 'properties' => [ + 'createdAt' => [ + 'type' => 'string', + 'format' => 'datetime', + ], ], + 'required' => ['createdAt'], ], - 'required' => ['createdAt'], ]); $method = $this->generator->generateFromArray($document); - $this->assertStringContainsString('new \\DateTime($data[\'createdAt\'])', $method); + $this->assertStringContainsString('return new static(', $method); + $this->assertStringContainsString("createdAt: Carbon::parse(\$data['createdAt'])", $method); } public function test_it_handles_optional_datetime(): void { $document = $this->createDocument('app.test.event', [ 'type' => 'record', - 'properties' => [ - 'updatedAt' => [ - 'type' => 'string', - 'format' => 'datetime', + 'record' => [ + 'properties' => [ + 'updatedAt' => [ + 'type' => 'string', + 'format' => 'datetime', + ], ], + 'required' => [], ], - 'required' => [], ]); $method = $this->generator->generateFromArray($document); - $this->assertStringContainsString('isset($data[\'updatedAt\']) ? new \\DateTime($data[\'updatedAt\']) : null', $method); + $this->assertStringContainsString('return new static(', $method); + $this->assertStringContainsString("updatedAt: isset(\$data['updatedAt']) ? Carbon::parse(\$data['updatedAt']) : null", $method); } public function test_it_handles_array_of_objects(): void { $document = $this->createDocument('app.test.config', [ 'type' => 'record', - 'properties' => [ - 'settings' => [ - 'type' => 'array', - 'items' => [ - 'type' => 'object', + 'record' => [ + 'properties' => [ + 'settings' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'object', + ], ], ], + 'required' => [], ], - 'required' => [], ]); $method = $this->generator->generateFromArray($document); - $this->assertStringContainsString('$data[\'settings\'] ?? []', $method); + $this->assertStringContainsString('return new static(', $method); + $this->assertStringContainsString("settings: \$data['settings'] ?? []", $method); } public function test_it_does_not_add_trailing_comma_to_last_assignment(): void { $document = $this->createDocument('app.test.user', [ 'type' => 'record', - 'properties' => [ - 'first' => ['type' => 'string'], - 'last' => ['type' => 'string'], + 'record' => [ + 'properties' => [ + 'first' => ['type' => 'string'], + 'last' => ['type' => 'string'], + ], + 'required' => ['first', 'last'], ], - 'required' => ['first', 'last'], ]); $method = $this->generator->generateFromArray($document); - // Should have comma after first - $this->assertMatchesRegularExpression('/last: \$data\[\'last\'\][^,]/', $method); + $this->assertStringContainsString('return new static(', $method); + $this->assertStringContainsString("first: \$data['first'],", $method); + $this->assertStringNotContainsString("last: \$data['last'],", $method); + $this->assertStringContainsString("last: \$data['last']", $method); } public function test_it_includes_method_docblocks(): void diff --git a/tests/Unit/Generator/NamingConverterTest.php b/tests/Unit/Generator/NamingConverterTest.php index 1e3c061..e62f7d5 100644 --- a/tests/Unit/Generator/NamingConverterTest.php +++ b/tests/Unit/Generator/NamingConverterTest.php @@ -20,14 +20,14 @@ class NamingConverterTest extends TestCase { $className = $this->converter->nsidToClassName('app.bsky.feed.post'); - $this->assertSame('App\\Lexicon\\Feed\\Bsky\\App\\Post', $className); + $this->assertSame('App\\Lexicon\\App\\Bsky\\Feed\\Post', $className); } public function test_it_converts_nsid_to_namespace(): void { $namespace = $this->converter->nsidToNamespace('app.bsky.feed.post'); - $this->assertSame('App\\Lexicon\\Feed\\Bsky\\App', $namespace); + $this->assertSame('App\\Lexicon\\App\\Bsky\\Feed', $namespace); } public function test_it_handles_multi_part_names(): void @@ -102,7 +102,7 @@ class NamingConverterTest extends TestCase { $className = $this->converter->nsidToClassName('com.atproto.repo.getRecord'); - $this->assertSame('App\\Lexicon\\Repo\\Atproto\\Com\\GetRecord', $className); + $this->assertSame('App\\Lexicon\\Com\\Atproto\\Repo\\GetRecord', $className); } public function test_it_gets_base_namespace(): void @@ -130,7 +130,7 @@ class NamingConverterTest extends TestCase { $className = $this->converter->nsidToClassName('com.example.api.getUser'); - $this->assertSame('App\\Lexicon\\Api\\Example\\Com\\GetUser', $className); + $this->assertSame('App\\Lexicon\\Com\\Example\\Api\\GetUser', $className); } public function test_it_handles_hyphens_in_names(): void @@ -149,10 +149,10 @@ class NamingConverterTest extends TestCase public function test_namespace_parts_are_reversed(): void { - // app.bsky.feed should become Feed\Bsky\App (reversed) + // app.bsky.feed should become App\Bsky\Feed (authority-first) $namespace = $this->converter->nsidToNamespace('app.bsky.feed.post'); - $this->assertStringContainsString('Feed\\Bsky\\App', $namespace); + $this->assertStringContainsString('App\\Bsky\\Feed', $namespace); } public function test_it_handles_single_letter_parts(): void diff --git a/tests/Unit/Generator/PropertyGeneratorTest.php b/tests/Unit/Generator/PropertyGeneratorTest.php index 33008da..a18accb 100644 --- a/tests/Unit/Generator/PropertyGeneratorTest.php +++ b/tests/Unit/Generator/PropertyGeneratorTest.php @@ -179,7 +179,7 @@ class PropertyGeneratorTest extends TestCase ['author'] ); - $this->assertStringContainsString('App\\Lexicon\\Test\\App\\Author', $property); + $this->assertStringContainsString('public readonly Author $author', $property); } public function test_it_generates_promoted_with_default(): void diff --git a/tests/Unit/Generator/TypeMapperTest.php b/tests/Unit/Generator/TypeMapperTest.php index 6c39bc5..4144652 100644 --- a/tests/Unit/Generator/TypeMapperTest.php +++ b/tests/Unit/Generator/TypeMapperTest.php @@ -64,7 +64,7 @@ class TypeMapperTest extends TestCase { $type = $this->mapper->toPhpType(['type' => 'blob']); - $this->assertSame('\\SocialDept\\Schema\\Data\\BlobReference', $type); + $this->assertSame('BlobReference', $type); } public function test_it_maps_bytes_type(): void @@ -95,7 +95,7 @@ class TypeMapperTest extends TestCase 'ref' => 'app.bsky.feed.post', ]); - $this->assertSame('\\App\\Lexicon\\Feed\\Bsky\\App\\Post', $type); + $this->assertSame('Post', $type); } public function test_it_maps_union_type(): void @@ -166,7 +166,7 @@ class TypeMapperTest extends TestCase ], ]); - $this->assertSame('\\App\\Lexicon\\Feed\\Bsky\\App\\Post|\\App\\Lexicon\\Feed\\Bsky\\App\\Repost', $docType); + $this->assertSame('mixed', $docType); } public function test_it_adds_null_to_doc_type_when_nullable(): void @@ -178,13 +178,8 @@ class TypeMapperTest extends TestCase public function test_it_checks_if_type_is_nullable(): void { - // Field marked as required $this->assertFalse($this->mapper->isNullable(['required' => true])); - - // Field in required array $this->assertFalse($this->mapper->isNullable(['name' => 'field'], ['field'])); - - // Optional field $this->assertTrue($this->mapper->isNullable([])); } @@ -249,21 +244,36 @@ class TypeMapperTest extends TestCase 'ref' => 'app.bsky.feed.post', ]); - $this->assertContains('App\\Lexicon\\Feed\\Bsky\\App\\Post', $uses); + $this->assertContains('App\\Lexicon\\App\\Bsky\\Feed\\Post', $uses); + } + + public function test_it_gets_use_statements_for_open_union(): void + { + $uses = $this->mapper->getUseStatements([ + 'type' => 'union', + 'refs' => [ + 'app.bsky.feed.post', + 'app.bsky.feed.repost', + ], + ]); + + $this->assertEmpty($uses); } - public function test_it_gets_use_statements_for_union(): void + public function test_it_gets_use_statements_for_closed_union(): void { $uses = $this->mapper->getUseStatements([ 'type' => 'union', + 'closed' => true, 'refs' => [ 'app.bsky.feed.post', 'app.bsky.feed.repost', ], ]); - $this->assertContains('App\\Lexicon\\Feed\\Bsky\\App\\Post', $uses); - $this->assertContains('App\\Lexicon\\Feed\\Bsky\\App\\Repost', $uses); + $this->assertCount(2, $uses); + $this->assertContains('App\\Lexicon\\App\\Bsky\\Feed\\Post', $uses); + $this->assertContains('App\\Lexicon\\App\\Bsky\\Feed\\Repost', $uses); } public function test_it_gets_empty_use_statements_for_primitive(): void