workbench, tests

This commit is contained in:
icefox 2026-02-23 21:09:02 -03:00
parent d83a324eb0
commit 367858c97c
No known key found for this signature in database
27 changed files with 568 additions and 410 deletions

View file

@ -17,8 +17,8 @@ describe('caster priority', function () {
});
it('uses CastWith attribute over global config caster', function () {
$globalCaster = function (mixed $value): SimpleValue {
return new SimpleValue($value['value'] * 3);
$globalCaster = function (mixed $data): SimpleValue {
return new SimpleValue($data * 3);
};
config(['dto.cast.' . SimpleValue::class => $globalCaster]);
@ -30,16 +30,16 @@ describe('caster priority', function () {
});
it('falls back to global config caster when no CastWith attribute', function () {
$globalCaster = function (mixed $value): SimpleValue {
return new SimpleValue($value['value'] * 3);
$globalCaster = function (mixed $data): SimpleValue {
return new SimpleValue($data['value'] * 3);
};
config(['dto.cast.' . SimpleValue::class => $globalCaster]);
$object = WithGlobalCaster::fromArray([
'value' => ['value' => 5],
'simple' => ['value' => 5],
]);
expect($object->value->value)->toBe(15); // 5 * 3
expect($object->simple->value)->toBe(15); // 5 * 3
});
it('falls back to default construction when no caster exists', function () {

View file

@ -6,9 +6,9 @@ namespace Tests\Casters;
class SimpleValueCaster
{
public function cast(mixed $value): SimpleValue
public function cast(mixed $data): SimpleValue
{
return new SimpleValue($value['value'] * 2);
return new SimpleValue($data['value'] * 2);
}
public static function rules(): array

View file

@ -11,6 +11,6 @@ readonly class WithGlobalCaster
use DataObject;
public function __construct(
public SimpleValue $value,
public SimpleValue $simple,
) {}
}

View file

@ -7,9 +7,9 @@ use Illuminate\Support\Carbon;
class CarbonPeriodMapper
{
public function cast(mixed $value): CarbonPeriodImmutable
public function cast(mixed $data): CarbonPeriodImmutable
{
return new CarbonPeriodImmutable(Carbon::parse($value['start']), Carbon::parse($value['end']));
return new CarbonPeriodImmutable(Carbon::parse($data['start']), Carbon::parse($data['end']));
}
public static function rules(): array

View file

@ -1,12 +0,0 @@
<?php
namespace Tests\Flattening\Classes;
use Icefox\DTO\Attributes\Flat;
use Icefox\DTO\DataObject;
class BasicRoot
{
use DataObject;
public function __construct(public string $text, #[Flat] public RequiredLeaf $leaf) {}
}

View file

@ -1,12 +0,0 @@
<?php
namespace Tests\Flattening\Classes;
use Icefox\DTO\DataObject;
class RequiredLeaf
{
use DataObject;
public function __construct(public int $value) {}
}

View file

@ -1,18 +0,0 @@
<?php
namespace Tests\Flattening;
use Icefox\DTO\Log;
use Icefox\DTO\Support\RuleFactory;
use Tests\Flattening\Classes\BasicRoot;
describe('flattens required parameters', function () {
it('generates correct rules', function () {
$rules = (new RuleFactory(new Log()))->make(BasicRoot::class);
expect($rules)->toMatchArray([
'text' => ['required'],
'value' => ['required', 'numeric'],
]);
});
});

View file

@ -15,7 +15,7 @@ use Tests\Rules\WithOverwriteRules;
describe('rules array shape', function () {
it('returns inferred rules shape from RuleFactory::infer (inferred only)', function () {
$parameters = ReflectionHelper::getParametersMeta(WithMergedRules::class);
$rules = RuleFactory::infer($parameters, '', '');
$rules = RuleFactory::infer($parameters, '');
expect($rules)->toBe([
'value' => ['required', 'numeric'],
@ -24,7 +24,7 @@ describe('rules array shape', function () {
it('returns inferred rules shape regardless of OverwriteRules attribute', function () {
$parameters = ReflectionHelper::getParametersMeta(WithOverwriteRules::class);
$rules = RuleFactory::infer($parameters, '', '');
$rules = RuleFactory::infer($parameters, '');
expect($rules)->toBe([
'value' => ['required', 'numeric'],
@ -93,30 +93,8 @@ describe('rules overwrite', function () {
});
it('does not enforce inferred required rule when overwritten', function () {
$object = WithOverwriteRules::fromArray([]);
expect($object)->toBeInstanceOf(WithOverwriteRules::class);
});
it('does not enforce inferred numeric rule when overwritten', function () {
$rules = WithOverwriteRules::rules();
expect($rules)->toHaveKey('value');
expect($rules['value'])->toBe(['numeric', 'max:20']);
});
});
describe('empty rules overwrite', function () {
it('allows any value when rules are empty with OverwriteRules', function () {
$object = WithEmptyOverwriteRules::fromArray([
'value' => 999,
]);
expect($object->value)->toBe(999);
});
it('allows missing value when rules are empty with OverwriteRules', function () {
$object = WithEmptyOverwriteRules::fromArray([]);
expect($object)->toBeInstanceOf(WithEmptyOverwriteRules::class);
});
});

161
tests/RulesTest.php Normal file
View file

@ -0,0 +1,161 @@
<?php
declare(strict_types=1);
namespace Tests;
use Icefox\DTO\Attributes\Flat;
use Icefox\DTO\Support\RuleFactory;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
readonly class BasicPrimitives
{
public function __construct(
public string $text,
public int $number,
public bool $flag,
public ?array $items,
public float $floating = 0.0,
) {}
}
test('required rules', function () {
expect(RuleFactory::instance()->make(BasicPrimitives::class))->toBe([
'text' => ['required'],
'number' => ['required', 'numeric'],
'flag' => ['required', 'boolean'],
'items' => ['nullable', 'array'],
'floating' => ['sometimes', 'numeric'],
]);
});
readonly class AnnotatedArray
{
/**
* @param array<int,float> $items
*/
public function __construct(public array $items) {}
}
test('annotated array', function () {
expect(RuleFactory::instance()->make(AnnotatedArray::class))->toBe([
'items' => ['required', 'array'],
'items.*' => ['required', 'numeric'],
]);
});
readonly class AnnotatedArrayNullableValue
{
/**
* @param array<?float> $items
*/
public function __construct(public array $items) {}
}
test('annotated array with nullable items', function () {
expect(RuleFactory::instance()->make(AnnotatedArrayNullableValue::class))->toBe([
'items' => ['required', 'array'],
'items.*' => ['nullable', 'numeric'],
]);
});
readonly class PlainLeaf
{
public function __construct(public string $name) {}
}
readonly class PlainRoot
{
public function __construct(public int $value, public PlainLeaf $leaf) {}
}
test('plain nesting', function () {
expect(RuleFactory::instance()->make(PlainRoot::class))->toBe([
'value' => ['required', 'numeric'],
'leaf' => ['required'],
'leaf.name' => ['required'],
]);
});
readonly class AnnotatedArrayItem
{
public function __construct(public int $value) {}
}
readonly class AnnotatedArrayObject
{
/**
* @param ?array<AnnotatedArrayItem> $items
*/
public function __construct(public ?array $items) {}
}
test('annotated array with object', function () {
expect(RuleFactory::instance()->make(AnnotatedArrayObject::class))->toBe([
'items' => ['nullable', 'array'],
'items.*' => ['required'],
'items.*.value' => ['required', 'numeric'],
]);
});
readonly class FlattenedLeaf
{
public function __construct(public ?bool $flag) {}
}
readonly class NotFlattenedLeaf
{
public function __construct(public string $description) {}
}
readonly class FlattenedNode
{
public function __construct(
public string $id,
public NotFlattenedLeaf $leaf,
#[Flat]
public FlattenedLeaf $squish,
public int $level = 1,
) {}
}
readonly class FlattenedRoot
{
public function __construct(
public int $value,
#[Flat]
public FlattenedNode $node,
) {}
}
test('flattened basic', function () {
expect(RuleFactory::instance()->make(FlattenedRoot::class))->toBe([
'value' => ['required', 'numeric'],
'id' => ['required' ],
'leaf' => ['required'],
'leaf.description' => ['required'],
'flag' => ['nullable', 'boolean'],
'level' => ['sometimes', 'numeric'],
]);
});
readonly class AnnotatedCollectionItem
{
public function __construct(public int $value) {}
}
readonly class AnnotatedCollection
{
/**
* @param Collection<AnnotatedCollectionItem> $group
*/
public function __construct(public Collection $group) {}
}
test('annotated collection', function () {
expect(RuleFactory::instance()->make(AnnotatedCollection::class))->toBe([
'group' => ['required', 'array'],
'group.*' => ['required'],
'group.*.value' => ['required', 'numeric'],
]);
});

View file

@ -2,15 +2,11 @@
namespace Tests;
use Icefox\DTO\DataObjectServiceProvider;
use Orchestra\Testbench\TestCase as BaseTestCase;
use Illuminate\Contracts\Config\Repository;
use Monolog\Formatter\JsonFormatter;
use Orchestra\Testbench\Concerns\WithWorkbench;
abstract class TestCase extends BaseTestCase
abstract class TestCase extends \Orchestra\Testbench\TestCase
{
protected function getPackageProviders($app)
{
return [
DataObjectServiceProvider::class,
];
}
use WithWorkbench;
}