Skip to content

Commit d8a0400

Browse files
committed
feat(Data): Add DataUnion attribute
1 parent a771833 commit d8a0400

File tree

3 files changed

+137
-2
lines changed

3 files changed

+137
-2
lines changed

src/Data/DataUnion.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Testo\Data;
6+
7+
use Testo\Data\Internal\DataProviderAttribute;
8+
use Testo\Data\Internal\DataProviderInterceptor;
9+
use Testo\Pipeline\Attribute\FallbackInterceptor;
10+
use Testo\Pipeline\Attribute\Interceptable;
11+
12+
/**
13+
* Concatenates multiple data providers into a single sequence.
14+
*
15+
* Primarily useful as a nested attribute inside {@see DataCross} or {@see DataZip}
16+
* to combine multiple providers before crossing or zipping with others.
17+
*
18+
* @api
19+
*/
20+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION | \Attribute::IS_REPEATABLE)]
21+
#[FallbackInterceptor(DataProviderInterceptor::class)]
22+
final class DataUnion implements Interceptable, DataProviderAttribute
23+
{
24+
/**
25+
* @param array<DataProviderAttribute> $providers Data providers to concatenate.
26+
*/
27+
public readonly array $providers;
28+
29+
public function __construct(DataProviderAttribute ...$providers)
30+
{
31+
$this->providers = $providers;
32+
}
33+
}

src/Data/Internal/DataProviderInterceptor.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Testo\Data\DataCross;
1313
use Testo\Data\DataProvider;
1414
use Testo\Data\DataSet;
15+
use Testo\Data\DataUnion;
1516
use Testo\Data\DataZip;
1617
use Testo\Data\MultipleResult;
1718
use Testo\Event\Test\TestBatchFinished;
@@ -171,6 +172,7 @@ private static function extractDataSets(TestInfo $info, object $attr): iterable
171172
$attr instanceof DataSet => [($attr->name ?? 0) => $attr->arguments],
172173
$attr instanceof DataCross => self::fromDataCross($info, $attr),
173174
$attr instanceof DataZip => self::fromDataZip($info, $attr),
175+
$attr instanceof DataUnion => self::fromDataUnion($info, $attr),
174176
default => throw new \RuntimeException('Unknown Data Provider Attribute type.'),
175177
};
176178
}
@@ -192,7 +194,7 @@ private static function fromDataProvider(TestInfo $info, DataProvider $attribute
192194
$m = $class->getMethod($provider);
193195
$provider = match (true) {
194196
$m->isStatic() => $m->getClosure(null),
195-
default => static fn () => $m->getClosure($info->caseInfo->instance->getInstance()),
197+
default => static fn() => $m->getClosure($info->caseInfo->instance->getInstance()),
196198
};
197199
}
198200

@@ -216,7 +218,7 @@ private static function fromDataProvider(TestInfo $info, DataProvider $attribute
216218
private static function fromDataZip(TestInfo $info, DataZip $attr): iterable
217219
{
218220
$generators = \array_map(
219-
static fn ($providerAttr): DeferredGenerator => DeferredGenerator::fromHandler(
221+
static fn($providerAttr): DeferredGenerator => DeferredGenerator::fromHandler(
220222
static function () use ($info, $providerAttr) {
221223
yield from self::extractDataSets($info, $providerAttr);
222224
},
@@ -294,4 +296,14 @@ private static function cartesianProduct(
294296
);
295297
}
296298
}
299+
300+
/**
301+
* Extract data sets from a DataUnion attribute (concatenation).
302+
*/
303+
private static function fromDataUnion(TestInfo $info, DataUnion $attr): iterable
304+
{
305+
foreach ($attr->providers as $providerAttr) {
306+
yield from self::extractDataSets($info, $providerAttr);
307+
}
308+
}
297309
}

tests/Data/Self/DataUnion.php

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Data\Self;
6+
7+
use Testo\Application\Attribute\Test;
8+
use Testo\Assert;
9+
use Testo\Data\DataProvider;
10+
use Testo\Data\DataSet;
11+
12+
final class DataUnion
13+
{
14+
public static function adminsProvider(): array
15+
{
16+
return [
17+
'root' => ['root', true],
18+
'admin' => ['admin', true],
19+
];
20+
}
21+
22+
public static function usersProvider(): iterable
23+
{
24+
yield 'alice' => ['alice', false];
25+
yield 'bob' => ['bob', false];
26+
}
27+
28+
#[Test]
29+
#[\Testo\Data\DataCross(
30+
new \Testo\Data\DataUnion(
31+
new DataProvider('adminsProvider'),
32+
new DataProvider('usersProvider'),
33+
),
34+
new DataSet(['read'], 'read'),
35+
)]
36+
public function crossWithUnion(string $user, bool $isAdmin, string $permission): void
37+
{
38+
// (2 admins + 2 users) × 1 permission = 4 combinations
39+
Assert::same('read', $permission);
40+
Assert::true(\in_array([$user, $isAdmin], [
41+
['root', true],
42+
['admin', true],
43+
['alice', false],
44+
['bob', false],
45+
], true));
46+
}
47+
48+
#[Test]
49+
#[\Testo\Data\DataZip(
50+
new \Testo\Data\DataUnion(
51+
new DataProvider('adminsProvider'),
52+
new DataProvider('usersProvider'),
53+
),
54+
new DataProvider('permissionsProvider'),
55+
)]
56+
public function zipWithUnion(string $user, bool $isAdmin, string $permission): void
57+
{
58+
// zip stops at shortest: 4 users | 3 permissions = 3 iterations
59+
Assert::true(\in_array([$user, $isAdmin, $permission], [
60+
['root', true, 'read'],
61+
['admin', true, 'write'],
62+
['alice', false, 'delete'],
63+
], true));
64+
}
65+
66+
public static function permissionsProvider(): array
67+
{
68+
return [
69+
'read' => ['read'],
70+
'write' => ['write'],
71+
'delete' => ['delete'],
72+
];
73+
}
74+
75+
#[Test]
76+
#[\Testo\Data\DataUnion(
77+
new DataProvider('adminsProvider'),
78+
new DataProvider('usersProvider'),
79+
)]
80+
public function simpleUnion(string $user, bool $isAdmin): void
81+
{
82+
// 2 admins + 2 users = 4 iterations
83+
Assert::true(\in_array([$user, $isAdmin], [
84+
['root', true],
85+
['admin', true],
86+
['alice', false],
87+
['bob', false],
88+
], true));
89+
}
90+
}

0 commit comments

Comments
 (0)