Skip to content

Commit 2c64696

Browse files
committed
🌟 feat(tests): add generic type safety tests for Chain, Mesh, and TypeDeclarations
1 parent f971140 commit 2c64696

File tree

5 files changed

+376
-0
lines changed

5 files changed

+376
-0
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Noem\State\Tests\Unit\Middleware\TypeSafety;
6+
7+
use Noem\State\Middleware\Chain;
8+
use PHPUnit\Framework\Attributes\Group;
9+
use PHPUnit\Framework\TestCase;
10+
11+
/**
12+
* Acceptance Criterion: Chain is generic over Context (C) and Return (R) types
13+
*/
14+
#[Group('middleware')]
15+
#[Group('type-safety')]
16+
class ChainGenericsTest extends TestCase
17+
{
18+
public function testChainHandlesStringContext(): void
19+
{
20+
$chain = new Chain(fn(string $c): string => strtoupper($c));
21+
22+
$result = $chain->call('hello');
23+
24+
$this->assertIsString($result);
25+
$this->assertEquals('HELLO', $result);
26+
}
27+
28+
public function testChainHandlesIntContext(): void
29+
{
30+
$chain = new Chain(fn(int $c): int => $c * 2);
31+
32+
$result = $chain->call(5);
33+
34+
$this->assertIsInt($result);
35+
$this->assertEquals(10, $result);
36+
}
37+
38+
public function testChainHandlesObjectContext(): void
39+
{
40+
$chain = new Chain(fn(\stdClass $c): \stdClass => $c);
41+
42+
$obj = new \stdClass();
43+
$obj->value = 'test';
44+
45+
$result = $chain->call($obj);
46+
47+
$this->assertInstanceOf(\stdClass::class, $result);
48+
$this->assertEquals('test', $result->value);
49+
}
50+
51+
public function testChainHandlesArrayContext(): void
52+
{
53+
$chain = new Chain(fn(array $c): array => array_map(fn($x) => $x * 2, $c));
54+
55+
$result = $chain->call([1, 2, 3]);
56+
57+
$this->assertIsArray($result);
58+
$this->assertEquals([2, 4, 6], $result);
59+
}
60+
61+
public function testChainHandlesDifferentReturnType(): void
62+
{
63+
$chain = new Chain(fn(string $c): int => strlen($c));
64+
65+
$result = $chain->call('hello');
66+
67+
$this->assertIsInt($result);
68+
$this->assertEquals(5, $result);
69+
}
70+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Noem\State\Tests\Unit\Middleware\TypeSafety;
6+
7+
use Noem\State\Middleware\Chain;
8+
use Noem\State\Middleware\ChainInterface;
9+
use PHPUnit\Framework\Attributes\Group;
10+
use PHPUnit\Framework\TestCase;
11+
12+
/**
13+
* Acceptance Criterion: ChainInterface supports generic type parameters
14+
*/
15+
#[Group('middleware')]
16+
#[Group('type-safety')]
17+
class ChainInterfaceTest extends TestCase
18+
{
19+
public function testChainImplementsChainInterface(): void
20+
{
21+
$chain = new Chain(fn($c) => $c);
22+
23+
$this->assertInstanceOf(ChainInterface::class, $chain);
24+
}
25+
26+
public function testChainInterfaceLink(): void
27+
{
28+
$chain = new Chain(fn($c) => $c);
29+
30+
$this->assertInstanceOf(ChainInterface::class, $chain);
31+
32+
$newChain = $chain->link(function ($context, $next) {
33+
return $next($context);
34+
});
35+
36+
$this->assertInstanceOf(ChainInterface::class, $newChain);
37+
$this->assertSame($chain, $newChain); // link returns same instance
38+
}
39+
40+
public function testChainInterfaceCall(): void
41+
{
42+
$chain = new Chain(fn(string $c): string => strtoupper($c));
43+
44+
$result = $chain->call('test');
45+
46+
$this->assertEquals('TEST', $result);
47+
}
48+
49+
public function testChainInterfaceWithMiddleware(): void
50+
{
51+
$chain = new Chain(fn($c) => $c);
52+
$chain->link(function ($context, $next) {
53+
$modified = $context . '-modified';
54+
return $next($modified);
55+
});
56+
57+
$result = $chain->call('input');
58+
59+
$this->assertEquals('input-modified', $result);
60+
}
61+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Noem\State\Tests\Unit\Middleware\TypeSafety;
6+
7+
use Noem\State\Middleware\Mesh;
8+
use PHPUnit\Framework\Attributes\Group;
9+
use PHPUnit\Framework\TestCase;
10+
11+
/**
12+
* Acceptance Criterion: Mesh supports generic Key (TKey) and Value (TValue) types
13+
*/
14+
#[Group('middleware')]
15+
#[Group('type-safety')]
16+
class MeshGenericsTest extends TestCase
17+
{
18+
public function testMeshHandlesStringKeys(): void
19+
{
20+
$data = ['key1' => 'value1', 'key2' => 'value2'];
21+
$mesh = new Mesh($data);
22+
23+
$this->assertEquals('value1', $mesh['key1']);
24+
$this->assertEquals('value2', $mesh['key2']);
25+
}
26+
27+
public function testMeshHandlesIntKeys(): void
28+
{
29+
$data = [0 => 'first', 1 => 'second', 2 => 'third'];
30+
$mesh = new Mesh($data);
31+
32+
$this->assertEquals('first', $mesh[0]);
33+
$this->assertEquals('second', $mesh[1]);
34+
$this->assertEquals('third', $mesh[2]);
35+
}
36+
37+
public function testMeshHandlesStringValues(): void
38+
{
39+
$mesh = new Mesh();
40+
$mesh['key'] = 'string value';
41+
42+
$this->assertIsString($mesh['key']);
43+
$this->assertEquals('string value', $mesh['key']);
44+
}
45+
46+
public function testMeshHandlesIntValues(): void
47+
{
48+
$mesh = new Mesh();
49+
$mesh['number'] = 42;
50+
51+
$this->assertIsInt($mesh['number']);
52+
$this->assertEquals(42, $mesh['number']);
53+
}
54+
55+
public function testMeshHandlesObjectValues(): void
56+
{
57+
$mesh = new Mesh();
58+
$obj = new \stdClass();
59+
$obj->property = 'value';
60+
61+
$mesh['object'] = $obj;
62+
63+
$this->assertInstanceOf(\stdClass::class, $mesh['object']);
64+
$this->assertEquals('value', $mesh['object']->property);
65+
}
66+
67+
public function testMeshHandlesArrayValues(): void
68+
{
69+
$mesh = new Mesh();
70+
$mesh['array'] = [1, 2, 3];
71+
72+
$this->assertIsArray($mesh['array']);
73+
$this->assertEquals([1, 2, 3], $mesh['array']);
74+
}
75+
76+
public function testMeshHandlesMixedTypes(): void
77+
{
78+
$data = [
79+
'string' => 'text',
80+
'int' => 123,
81+
'array' => [1, 2, 3],
82+
'object' => (object)['key' => 'value']
83+
];
84+
$mesh = new Mesh($data);
85+
86+
$this->assertIsString($mesh['string']);
87+
$this->assertIsInt($mesh['int']);
88+
$this->assertIsArray($mesh['array']);
89+
$this->assertIsObject($mesh['object']);
90+
}
91+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Noem\State\Tests\Unit\Middleware\TypeSafety;
6+
7+
use Noem\State\Middleware\ChainMail;
8+
use PHPUnit\Framework\Attributes\Group;
9+
use PHPUnit\Framework\TestCase;
10+
use TypeError;
11+
12+
/**
13+
* Acceptance Criterion: Return type enforcement for factory callables
14+
*/
15+
#[Group('middleware')]
16+
#[Group('type-safety')]
17+
class ReturnTypeEnforcementTest extends TestCase
18+
{
19+
public function testFactoryRequiresReturnType(): void
20+
{
21+
$this->expectException(TypeError::class);
22+
23+
$mail = new ChainMail();
24+
25+
// Factory without return type should throw TypeError
26+
$mail->supply(function () {
27+
return 'value';
28+
});
29+
}
30+
31+
public function testFactoryWithReturnTypeAccepted(): void
32+
{
33+
$mail = new ChainMail();
34+
35+
// Factory with return type should work
36+
$mail->supply(fn(): string => 'value');
37+
38+
$mail->boot();
39+
40+
$result = $mail->get('string');
41+
42+
$this->assertEquals('value', $result);
43+
}
44+
45+
public function testMultipleFactoriesWithReturnTypes(): void
46+
{
47+
$mail = new ChainMail();
48+
49+
$mail->supply(
50+
fn(): string => 'text',
51+
fn(): int => 42,
52+
fn(): array => [1, 2, 3]
53+
);
54+
55+
$mail->boot();
56+
57+
$this->assertEquals('text', $mail->get('string'));
58+
$this->assertEquals(42, $mail->get('int'));
59+
$this->assertEquals([1, 2, 3], $mail->get('array'));
60+
}
61+
62+
public function testReturnTypeEnforcedAtRuntime(): void
63+
{
64+
$mail = new ChainMail();
65+
66+
$mail->supply(fn(): string => 'correct type');
67+
68+
$mail->boot();
69+
70+
$result = $mail->get('string');
71+
72+
$this->assertIsString($result);
73+
}
74+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Noem\State\Tests\Unit\Middleware\TypeSafety;
6+
7+
use Noem\State\Middleware\Chain;
8+
use Noem\State\Middleware\ChainMail;
9+
use Noem\State\Middleware\Mesh;
10+
use PHPUnit\Framework\Attributes\Group;
11+
use PHPUnit\Framework\TestCase;
12+
use ReflectionClass;
13+
14+
/**
15+
* Acceptance Criterion: Proper PHP 8.4+ type declarations throughout
16+
*/
17+
#[Group('middleware')]
18+
#[Group('type-safety')]
19+
class TypeDeclarationsTest extends TestCase
20+
{
21+
public function testChainHasStrictTypes(): void
22+
{
23+
$reflection = new ReflectionClass(Chain::class);
24+
$fileName = $reflection->getFileName();
25+
26+
$this->assertNotFalse($fileName);
27+
28+
$content = file_get_contents($fileName);
29+
30+
$this->assertStringContainsString('declare(strict_types=1);', $content);
31+
}
32+
33+
public function testMeshHasStrictTypes(): void
34+
{
35+
$reflection = new ReflectionClass(Mesh::class);
36+
$fileName = $reflection->getFileName();
37+
38+
$this->assertNotFalse($fileName);
39+
40+
$content = file_get_contents($fileName);
41+
42+
$this->assertStringContainsString('declare(strict_types=1);', $content);
43+
}
44+
45+
public function testChainMailHasStrictTypes(): void
46+
{
47+
$reflection = new ReflectionClass(ChainMail::class);
48+
$fileName = $reflection->getFileName();
49+
50+
$this->assertNotFalse($fileName);
51+
52+
$content = file_get_contents($fileName);
53+
54+
$this->assertStringContainsString('declare(strict_types=1);', $content);
55+
}
56+
57+
public function testChainCallMethodHasReturnType(): void
58+
{
59+
$reflection = new ReflectionClass(Chain::class);
60+
$method = $reflection->getMethod('call');
61+
62+
$this->assertTrue($method->hasReturnType());
63+
}
64+
65+
public function testChainLinkMethodHasReturnType(): void
66+
{
67+
$reflection = new ReflectionClass(Chain::class);
68+
$method = $reflection->getMethod('link');
69+
70+
$this->assertTrue($method->hasReturnType());
71+
}
72+
73+
public function testMeshOffsetGetHasReturnType(): void
74+
{
75+
$reflection = new ReflectionClass(Mesh::class);
76+
$method = $reflection->getMethod('offsetGet');
77+
78+
$this->assertTrue($method->hasReturnType());
79+
}
80+
}

0 commit comments

Comments
 (0)