@@ -45,6 +45,141 @@ public static function provideUnsupportedTypes(): \Generator
4545 yield 'object without DateTimeInterface ' => [new \stdClass (), 'stdClass ' ];
4646 yield 'null ' => [null , 'null ' ];
4747 }
48+
49+ public static function provideJsonSerializableData (): \Generator
50+ {
51+ yield 'string value ' => [
52+ new class implements \JsonSerializable {
53+ public function jsonSerialize (): string
54+ {
55+ return 'serialized string ' ;
56+ }
57+ },
58+ StringValue::class,
59+ 'serialized string ' ,
60+ ];
61+
62+ yield 'integer value ' => [
63+ new class implements \JsonSerializable {
64+ public function jsonSerialize (): int
65+ {
66+ return 42 ;
67+ }
68+ },
69+ IntegerValue::class,
70+ 42 ,
71+ ];
72+
73+ yield 'float value ' => [
74+ new class implements \JsonSerializable {
75+ public function jsonSerialize (): float
76+ {
77+ return 3.14 ;
78+ }
79+ },
80+ FloatValue::class,
81+ 3.14 ,
82+ ];
83+
84+ yield 'boolean true ' => [
85+ new class implements \JsonSerializable {
86+ public function jsonSerialize (): bool
87+ {
88+ return true ;
89+ }
90+ },
91+ BooleanValue::class,
92+ true ,
93+ ];
94+
95+ yield 'boolean false ' => [
96+ new class implements \JsonSerializable {
97+ public function jsonSerialize (): bool
98+ {
99+ return false ;
100+ }
101+ },
102+ BooleanValue::class,
103+ false ,
104+ ];
105+
106+ yield 'indexed array ' => [
107+ new class implements \JsonSerializable {
108+ public function jsonSerialize (): array
109+ {
110+ return [1 , 2 , 3 ];
111+ }
112+ },
113+ ArrayValue::class,
114+ [1 , 2 , 3 ],
115+ ];
116+
117+ yield 'associative array ' => [
118+ new class implements \JsonSerializable {
119+ public function jsonSerialize (): array
120+ {
121+ return ['name ' => 'John ' , 'age ' => 30 ];
122+ }
123+ },
124+ InlineTableValue::class,
125+ ['name ' => 'John ' , 'age ' => 30 ],
126+ ];
127+
128+ yield 'empty array ' => [
129+ new class implements \JsonSerializable {
130+ public function jsonSerialize (): array
131+ {
132+ return [];
133+ }
134+ },
135+ ArrayValue::class,
136+ [],
137+ ];
138+
139+ yield 'datetime value ' => [
140+ new class implements \JsonSerializable {
141+ public function jsonSerialize (): \DateTimeImmutable
142+ {
143+ return new \DateTimeImmutable ('2024-01-15T10:30:00Z ' );
144+ }
145+ },
146+ DateTimeValue::class,
147+ '2024-01-15T10:30:00Z ' ,
148+ ];
149+ }
150+
151+ public static function provideJsonSerializableInvalidData (): \Generator
152+ {
153+ yield 'null value ' => [
154+ new class implements \JsonSerializable {
155+ public function jsonSerialize (): mixed
156+ {
157+ return null ;
158+ }
159+ },
160+ 'null ' ,
161+ ];
162+
163+ yield 'stdClass object ' => [
164+ new class implements \JsonSerializable {
165+ public function jsonSerialize (): object
166+ {
167+ return new \stdClass ();
168+ }
169+ },
170+ 'stdClass ' ,
171+ ];
172+
173+ yield 'resource ' => [
174+ new class implements \JsonSerializable {
175+ public function jsonSerialize (): mixed
176+ {
177+ return \fopen ('php://memory ' , 'r ' );
178+ }
179+ },
180+ 'resource (stream) ' ,
181+ ];
182+ }
48183 // ============================================
49184 // String Value Tests
50185 // ============================================
@@ -332,4 +467,111 @@ public function testCreateArrayHandlesNestedAssociativeArrays(): void
332467 self ::assertArrayHasKey ('outer ' , $ result ->pairs );
333468 self ::assertInstanceOf (InlineTableValue::class, $ result ->pairs ['outer ' ]);
334469 }
470+
471+ // ============================================
472+ // JsonSerializable Tests
473+ // ============================================
474+
475+ #[DataProvider('provideJsonSerializableData ' )]
476+ public function testCreateJsonSerializableReturnsCorrectValueType (
477+ \JsonSerializable $ jsonSerializable ,
478+ string $ expectedClass ,
479+ mixed $ expectedValue ,
480+ ): void {
481+ // Act
482+ $ result = ValueFactory::create ($ jsonSerializable );
483+
484+ // Assert
485+ self ::assertInstanceOf ($ expectedClass , $ result );
486+
487+ // Verify the actual value based on type
488+ match ($ expectedClass ) {
489+ StringValue::class => self ::assertSame ($ expectedValue , $ result ->value ),
490+ IntegerValue::class => self ::assertSame ($ expectedValue , $ result ->value ),
491+ FloatValue::class => self ::assertSame ($ expectedValue , $ result ->value ),
492+ BooleanValue::class => self ::assertSame ($ expectedValue , $ result ->value ),
493+ DateTimeValue::class => self ::assertSame ($ expectedValue , $ result ->raw ),
494+ ArrayValue::class => self ::assertCount (\count ($ expectedValue ), $ result ->elements ),
495+ InlineTableValue::class => self ::assertCount (\count ($ expectedValue ), $ result ->pairs ),
496+ default => self ::fail ("Unexpected value class: $ expectedClass " ),
497+ };
498+ }
499+
500+ #[DataProvider('provideJsonSerializableInvalidData ' )]
501+ public function testCreateJsonSerializableThrowsExceptionForInvalidData (
502+ \JsonSerializable $ jsonSerializable ,
503+ string $ expectedType ,
504+ ): void {
505+ // Assert (before Act for exceptions)
506+ $ this ->expectException (InvalidTypeException::class);
507+ $ this ->expectExceptionMessage ("Unsupported value type: $ expectedType " );
508+
509+ // Act
510+ ValueFactory::create ($ jsonSerializable );
511+ }
512+
513+ public function testCreateJsonSerializableWithNestedJsonSerializable (): void
514+ {
515+ // Arrange
516+ $ innerJsonSerializable = new class implements \JsonSerializable {
517+ public function jsonSerialize (): string
518+ {
519+ return 'inner value ' ;
520+ }
521+ };
522+
523+ $ outerJsonSerializable = new class ($ innerJsonSerializable ) implements \JsonSerializable {
524+ public function __construct (private readonly \JsonSerializable $ inner )
525+ {
526+ }
527+
528+ public function jsonSerialize (): array
529+ {
530+ return ['nested ' => $ this ->inner ];
531+ }
532+ };
533+
534+ // Act
535+ $ result = ValueFactory::create ($ outerJsonSerializable );
536+
537+ // Assert
538+ self ::assertInstanceOf (InlineTableValue::class, $ result );
539+ self ::assertArrayHasKey ('nested ' , $ result ->pairs );
540+ self ::assertInstanceOf (StringValue::class, $ result ->pairs ['nested ' ]);
541+ self ::assertSame ('inner value ' , $ result ->pairs ['nested ' ]->value );
542+ }
543+
544+ public function testCreateJsonSerializableWithComplexStructure (): void
545+ {
546+ // Arrange
547+ $ jsonSerializable = new class implements \JsonSerializable {
548+ public function jsonSerialize (): array
549+ {
550+ return [
551+ 'title ' => 'Test ' ,
552+ 'count ' => 10 ,
553+ 'enabled ' => true ,
554+ 'ratio ' => 0.5 ,
555+ 'tags ' => ['php ' , 'toml ' ],
556+ 'metadata ' => [
557+ 'created ' => '2024-01-15 ' ,
558+ 'version ' => 1 ,
559+ ],
560+ ];
561+ }
562+ };
563+
564+ // Act
565+ $ result = ValueFactory::create ($ jsonSerializable );
566+
567+ // Assert
568+ self ::assertInstanceOf (InlineTableValue::class, $ result );
569+ self ::assertCount (6 , $ result ->pairs );
570+ self ::assertInstanceOf (StringValue::class, $ result ->pairs ['title ' ]);
571+ self ::assertInstanceOf (IntegerValue::class, $ result ->pairs ['count ' ]);
572+ self ::assertInstanceOf (BooleanValue::class, $ result ->pairs ['enabled ' ]);
573+ self ::assertInstanceOf (FloatValue::class, $ result ->pairs ['ratio ' ]);
574+ self ::assertInstanceOf (ArrayValue::class, $ result ->pairs ['tags ' ]);
575+ self ::assertInstanceOf (InlineTableValue::class, $ result ->pairs ['metadata ' ]);
576+ }
335577}
0 commit comments