1+ <?php
2+
3+ declare (strict_types=1 );
4+
5+ use TypePHP \Tests \Fixtures \Types \DatabaseDriverMap ;
6+ use TypePHP \Tests \Fixtures \Types \DoctrineLikeConnection ;
7+ use TypePHP \Tests \Fixtures \Types \StatusEnum ;
8+
9+ /**
10+ * External function evaluating key-of on a public class constant
11+ *
12+ * @param key-of<DatabaseDriverMap::PUBLIC_MAP> $action
13+ */
14+ function testExternalPublicConstKey (string $ action ): string
15+ {
16+ return $ action ;
17+ }
18+
19+ /**
20+ * @param key-of<StatusEnum> $statusName
21+ */
22+ function testEnumKeyOf (string $ statusName ): string
23+ {
24+ return $ statusName ;
25+ }
26+
27+ /**
28+ * @param value-of<StatusEnum> $statusValue
29+ */
30+ function testEnumValueOf (string $ statusValue ): string
31+ {
32+ return $ statusValue ;
33+ }
34+
35+ describe ('key-of<T> and value-of<T> Annotations ' , function () {
36+
37+ describe ('Array Constants (e.g. self::DRIVER_MAP) ' , function () {
38+
39+ test ('accepts valid key-of on a private constant from a static method ' , function () {
40+ expect (DatabaseDriverMap::checkStaticDriverKey ('pdo_mysql ' ))->toBe ('pdo_mysql ' );
41+ expect (DatabaseDriverMap::checkStaticDriverKey ('pdo_sqlite ' ))->toBe ('pdo_sqlite ' );
42+ });
43+
44+ test ('throws TypeError on invalid key-of on a private constant from a static method ' , function () {
45+ expect (fn () => DatabaseDriverMap::checkStaticDriverKey ('pdo_pgsql ' ))
46+ ->toThrow (TypeError::class, 'must be a key of TypePHP\Tests\Fixtures\Types\DatabaseDriverMap::DRIVER_MAP ' );
47+ });
48+
49+ test ('accepts valid key-of on a private constant from an instance method ' , function () {
50+ $ dbMap = new DatabaseDriverMap ();
51+ expect ($ dbMap ->checkInstanceDriverKey ('pdo_mysql ' ))->toBe ('pdo_mysql ' );
52+ });
53+
54+ test ('throws TypeError on invalid key-of on a public constant from a private method ' , function () {
55+ $ dbMap = new DatabaseDriverMap ();
56+
57+ expect ($ dbMap ->proxyPrivateMethod ('read ' ))->toBe ('read ' );
58+
59+ expect (fn () => $ dbMap ->proxyPrivateMethod ('delete ' ))
60+ ->toThrow (TypeError::class, 'must be a key of TypePHP\Tests\Fixtures\Types\DatabaseDriverMap::PUBLIC_MAP ' );
61+ });
62+
63+ test ('accepts valid key-of on a public constant from an external function ' , function () {
64+ expect (testExternalPublicConstKey ('write ' ))->toBe ('write ' );
65+
66+ expect (fn () => testExternalPublicConstKey ('execute ' ))
67+ ->toThrow (TypeError::class, 'must be a key of TypePHP\Tests\Fixtures\Types\DatabaseDriverMap::PUBLIC_MAP ' );
68+ });
69+
70+ test ('accepts valid value-of on a private constant array ' , function () {
71+ expect (DatabaseDriverMap::checkStaticDriverValue ('PDO\MySQL\Driver ' ))->toBe ('PDO\MySQL\Driver ' );
72+ });
73+
74+ test ('throws TypeError on invalid value-of on a private constant array ' , function () {
75+ expect (fn () => DatabaseDriverMap::checkStaticDriverValue ('PDO\PgSQL\Driver ' ))
76+ ->toThrow (TypeError::class, 'must be a value of TypePHP\Tests\Fixtures\Types\DatabaseDriverMap::DRIVER_MAP ' );
77+ });
78+
79+ });
80+
81+ describe ('Enums (e.g. StatusEnum) ' , function () {
82+
83+ test ('key-of<Enum> strictly checks against the Enum CASE NAMES ' , function () {
84+ expect (testEnumKeyOf ('Active ' ))->toBe ('Active ' );
85+ expect (testEnumKeyOf ('Pending ' ))->toBe ('Pending ' );
86+
87+ expect (fn () => testEnumKeyOf ('Archived ' ))
88+ ->toThrow (TypeError::class, 'must be a key of enum TypePHP\Tests\Fixtures\Types\StatusEnum ' );
89+
90+ expect (fn () => testEnumKeyOf ('active ' ))
91+ ->toThrow (TypeError::class, "must be a key of enum TypePHP\Tests\Fixtures\Types\StatusEnum, string 'active' given " );
92+ });
93+
94+ test ('value-of<Enum> strictly checks against the Enum BACKING VALUES ' , function () {
95+ expect (testEnumValueOf ('active ' ))->toBe ('active ' );
96+ expect (testEnumValueOf ('pending ' ))->toBe ('pending ' );
97+
98+ expect (fn () => testEnumValueOf ('archived ' ))
99+ ->toThrow (TypeError::class, 'must be a value of enum TypePHP\Tests\Fixtures\Types\StatusEnum ' );
100+
101+ expect (fn () => testEnumValueOf ('Active ' ))
102+ ->toThrow (TypeError::class, "must be a value of enum TypePHP\Tests\Fixtures\Types\StatusEnum, string 'Active' given " );
103+ });
104+
105+ });
106+
107+ describe ('Array Shapes (e.g. key-of<array{id: int, name: string}>) ' , function () {
108+
109+ test ('accepts valid string keys of an inline array shape ' , function () {
110+ expect (DatabaseDriverMap::checkArrayShapeKey ('id ' ))->toBe ('id ' );
111+ expect (DatabaseDriverMap::checkArrayShapeKey ('name ' ))->toBe ('name ' );
112+ });
113+
114+ test ('throws TypeError on invalid string key of an inline array shape ' , function () {
115+ expect (fn () => DatabaseDriverMap::checkArrayShapeKey ('invalid_key ' ))
116+ ->toThrow (TypeError::class, 'must be a key of the specified array shape ' );
117+ });
118+
119+ });
120+
121+ describe ('Type Aliases (@phpstan-type nested shapes) ' , function () {
122+
123+ test ('validates key-of and value-of correctly when deeply nested inside an array shape type alias ' , function () {
124+ $ conn = new DoctrineLikeConnection ();
125+
126+ expect ($ conn ->connect ([
127+ 'driver ' => 'pdo_mysql ' ,
128+ 'driverClass ' => 'PDO\MySQL\Driver '
129+ ]))->toBeTrue ();
130+
131+ expect (fn () => $ conn ->connect (['driver ' => 'pdo_pgsql ' ]))
132+ ->toThrow (TypeError::class, "['driver'] must be a key of TypePHP\Tests\Fixtures\Types\DatabaseDriverMap::DRIVER_MAP " );
133+
134+ expect (fn () => $ conn ->connect ([
135+ 'driver ' => 'pdo_mysql ' ,
136+ 'driverClass ' => 'PDO\PgSQL\Driver '
137+ ]))->toThrow (TypeError::class, "['driverClass'] must be a value of TypePHP\Tests\Fixtures\Types\DatabaseDriverMap::DRIVER_MAP " );
138+ });
139+
140+ test ('validates key-of with self:: reference natively inside a local type alias ' , function () {
141+ $ conn = new DoctrineLikeConnection ();
142+
143+ expect ($ conn ->localAction (['action ' => 'start ' ]))->toBeTrue ();
144+
145+ expect (fn () => $ conn ->localAction (['action ' => 'pause ' ]))
146+ ->toThrow (TypeError::class, "['action'] must be a key of TypePHP\Tests\Fixtures\Types\DoctrineLikeConnection::LOCAL_ACTIONS " );
147+ });
148+
149+ });
150+ });
0 commit comments