Passed
Pull Request — master (#464)
by Sergei
02:35
created

ValidatorTest.php$11 ➔ testOriginalValueUsage()   A

Complexity

Conditions 1

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 16
cc 1
rs 9.7333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Tests;
6
7
use InvalidArgumentException;
8
use PHPUnit\Framework\TestCase;
9
use stdClass;
10
use Yiisoft\Validator\AttributeTranslator\NullAttributeTranslator;
11
use Yiisoft\Validator\DataSet\ArrayDataSet;
12
use Yiisoft\Validator\DataSet\ObjectDataSet;
13
use Yiisoft\Validator\DataSet\SingleValueDataSet;
14
use Yiisoft\Validator\DataSetInterface;
15
use Yiisoft\Validator\EmptyCriteria\WhenEmpty;
16
use Yiisoft\Validator\EmptyCriteria\WhenNull;
17
use Yiisoft\Validator\Error;
18
use Yiisoft\Validator\Exception\RuleHandlerInterfaceNotImplementedException;
19
use Yiisoft\Validator\Exception\RuleHandlerNotFoundException;
20
use Yiisoft\Validator\Result;
21
use Yiisoft\Validator\Rule\BooleanValue;
22
use Yiisoft\Validator\Rule\CompareTo;
23
use Yiisoft\Validator\Rule\HasLength;
24
use Yiisoft\Validator\Rule\In;
25
use Yiisoft\Validator\Rule\TrueValue;
26
use Yiisoft\Validator\Rule\Number;
27
use Yiisoft\Validator\Rule\Required;
28
use Yiisoft\Validator\RuleInterface;
29
use Yiisoft\Validator\RulesProviderInterface;
30
use Yiisoft\Validator\Tests\Rule\RuleWithBuiltInHandler;
31
use Yiisoft\Validator\Tests\Support\Data\EachNestedObjects\Foo;
32
use Yiisoft\Validator\Tests\Support\Data\IteratorWithBooleanKey;
33
use Yiisoft\Validator\Tests\Support\Data\ObjectWithAttributesOnly;
34
use Yiisoft\Validator\Tests\Support\Data\ObjectWithDataSet;
35
use Yiisoft\Validator\Tests\Support\Data\ObjectWithDataSetAndRulesProvider;
36
use Yiisoft\Validator\Tests\Support\Data\ObjectWithDifferentPropertyVisibility;
37
use Yiisoft\Validator\Tests\Support\Data\ObjectWithPostValidationHook;
38
use Yiisoft\Validator\Tests\Support\Data\ObjectWithRulesProvider;
39
use Yiisoft\Validator\Tests\Support\Data\SimpleDto;
40
use Yiisoft\Validator\Tests\Support\Data\SimpleForm;
41
use Yiisoft\Validator\Tests\Support\Rule\NotNullRule\NotNull;
42
use Yiisoft\Validator\Tests\Support\Rule\StubRule\StubRuleWithOptions;
43
use Yiisoft\Validator\ValidationContext;
44
use Yiisoft\Validator\Validator;
45
use Yiisoft\Validator\ValidatorInterface;
46
47
class ValidatorTest extends TestCase
48
{
49
    public function setUp(): void
50
    {
51
        ObjectWithPostValidationHook::$hookCalled = false;
52
    }
53
54
    public function testBase(): void
55
    {
56
        $validator = new Validator();
57
58
        $result = $validator->validate(new ObjectWithAttributesOnly());
59
60
        $this->assertFalse($result->isValid());
61
        $this->assertSame(
62
            ['name' => ['This value must contain at least 5 characters.']],
63
            $result->getErrorMessagesIndexedByPath()
64
        );
65
    }
66
67
    public function dataDataAndRulesCombinations(): array
68
    {
69
        return [
70
            'pure-object-and-array-of-rules' => [
71
                [
72
                    'number' => ['Value must be no less than 77.'],
73
                ],
74
                new ObjectWithDifferentPropertyVisibility(),
75
                [
76
                    'age' => new Number(max: 100),
77
                    'number' => new Number(min: 77),
78
                ],
79
            ],
80
            'pure-object-and-no-rules' => [
81
                [
82
                    'name' => ['Value cannot be blank.'],
83
                    'age' => ['Value must be no less than 21.'],
84
                ],
85
                new ObjectWithDifferentPropertyVisibility(),
86
                null,
87
            ],
88
            'dataset-object-and-array-of-rules' => [
89
                [
90
                    'key1' => ['Value must be no less than 21.'],
91
                ],
92
                new ObjectWithDataSet(),
93
                [
94
                    'key1' => new Number(min: 21),
95
                ],
96
            ],
97
            'dataset-object-and-no-rules' => [
98
                [],
99
                new ObjectWithDataSet(),
100
                null,
101
            ],
102
            'rules-provider-object-and-array-of-rules' => [
103
                [
104
                    'number' => ['Value must be no greater than 7.'],
105
                ],
106
                new ObjectWithRulesProvider(),
107
                [
108
                    'age' => new Number(max: 100),
109
                    'number' => new Number(max: 7),
110
                ],
111
            ],
112
            'rules-provider-object-and-no-rules' => [
113
                [
114
                    'age' => ['Value must be equal to "25".'],
115
                ],
116
                new ObjectWithRulesProvider(),
117
                null,
118
            ],
119
            'rules-provider-and-dataset-object-and-array-of-rules' => [
120
                [
121
                    'key2' => ['Value must be no greater than 7.'],
122
                ],
123
                new ObjectWithDataSetAndRulesProvider(),
124
                [
125
                    'key2' => new Number(max: 7),
126
                ],
127
            ],
128
            'rules-provider-and-dataset-object-and-no-rules' => [
129
                [
130
                    'key2' => ['Value must be equal to "99".'],
131
                ],
132
                new ObjectWithDataSetAndRulesProvider(),
133
                null,
134
            ],
135
            'array-and-array-of-rules' => [
136
                [
137
                    'key2' => ['Value must be no greater than 7.'],
138
                ],
139
                ['key1' => 15, 'key2' => 99],
140
                [
141
                    'key1' => new Number(max: 100),
142
                    'key2' => new Number(max: 7),
143
                ],
144
            ],
145
            'array-and-no-rules' => [
146
                [],
147
                ['key1' => 15, 'key2' => 99],
148
                null,
149
            ],
150
            'scalar-and-array-of-rules' => [
151
                [
152
                    '' => ['Value must be no greater than 7.'],
153
                ],
154
                42,
155
                [
156
                    new Number(max: 7),
157
                ],
158
            ],
159
            'scalar-and-no-rules' => [
160
                [],
161
                42,
162
                null,
163
            ],
164
            'array-and-rules-provider' => [
165
                [
166
                    'age' => ['Value must be no less than 18.'],
167
                ],
168
                [
169
                    'age' => 17,
170
                ],
171
                new class () implements RulesProviderInterface {
172
                    public function getRules(): iterable
173
                    {
174
                        return [
175
                            'age' => [new Number(min: 18)],
176
                        ];
177
                    }
178
                },
179
            ],
180
            'array-and-object' => [
181
                [
182
                    'name' => ['Value not passed.'],
183
                    'bars' => ['Value must be array or iterable.'],
184
                ],
185
                [],
186
                new Foo(),
187
            ],
188
            'array-and-callable' => [
189
                ['' => ['test message']],
190
                [],
191
                static fn (): Result => (new Result())->addError('test message'),
192
            ],
193
        ];
194
    }
195
196
    /**
197
     * @dataProvider dataDataAndRulesCombinations
198
     */
199
    public function testDataAndRulesCombinations(
200
        array $expectedErrorMessages,
201
        mixed $data,
202
        iterable|object|callable|null $rules,
203
    ): void {
204
        $validator = new Validator();
205
        $result = $validator->validate($data, $rules);
206
        $this->assertSame($expectedErrorMessages, $result->getErrorMessagesIndexedByAttribute());
207
    }
208
209
    public function dataWithEmptyArrayOfRules(): array
210
    {
211
        return [
212
            'pure-object-and-no-rules' => [new ObjectWithDifferentPropertyVisibility()],
213
            'dataset-object-and-no-rules' => [new ObjectWithDataSet()],
214
            'rules-provider-object' => [new ObjectWithRulesProvider()],
215
            'rules-provider-and-dataset-object' => [new ObjectWithDataSetAndRulesProvider()],
216
            'array' => [['key1' => 15, 'key2' => 99]],
217
            'scalar' => [42],
218
        ];
219
    }
220
221
    /**
222
     * @dataProvider dataWithEmptyArrayOfRules
223
     */
224
    public function testWithEmptyArrayOfRules(mixed $data): void
225
    {
226
        $validator = new Validator();
227
        $result = $validator->validate($data, []);
228
229
        $this->assertTrue($result->isValid());
230
    }
231
232
    public function testAddingRulesViaConstructor(): void
233
    {
234
        $dataObject = new ArrayDataSet(['bool' => true, 'int' => 41]);
235
        $validator = new Validator();
236
        $result = $validator->validate($dataObject, [
237
            'bool' => [new BooleanValue()],
238
            'int' => [
239
                new Number(integerOnly: true),
240
                new Number(integerOnly: true, min: 44),
241
                static function (mixed $value): Result {
242
                    $result = new Result();
243
                    if ($value !== 42) {
244
                        $result->addError('Value should be 42!', ['int']);
245
                    }
246
247
                    return $result;
248
                },
249
            ],
250
        ]);
251
252
        $this->assertTrue($result->isAttributeValid('bool'));
253
        $this->assertFalse($result->isAttributeValid('int'));
254
    }
255
256
    public function diverseTypesDataProvider(): array
257
    {
258
        $class = new stdClass();
259
        $class->property = true;
260
261
        return [
262
            'object' => [new ObjectDataSet($class, useCache: false)],
263
            'true' => [true],
264
            'non-empty-string' => ['true'],
265
            'integer' => [12345],
266
            'float' => [12.345],
267
            'false' => [false],
268
        ];
269
    }
270
271
    /**
272
     * @dataProvider diverseTypesDataProvider
273
     */
274
    public function testDiverseTypes($dataSet): void
275
    {
276
        $validator = new Validator();
277
        $result = $validator->validate($dataSet, [new Required()]);
278
279
        $this->assertTrue($result->isValid());
280
    }
281
282
    public function testNullAsDataSet(): void
283
    {
284
        $validator = new Validator();
285
        $result = $validator->validate(null, ['property' => [new CompareTo(null)]]);
286
287
        $this->assertTrue($result->isValid());
288
    }
289
290
    public function testPreValidation(): void
291
    {
292
        $validator = new Validator();
293
        $result = $validator->validate(
294
            new ArrayDataSet(['property' => '']),
295
            ['property' => [new Required(when: static fn (mixed $value, ?ValidationContext $context): bool => false)]],
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

295
            ['property' => [new Required(when: static fn (/** @scrutinizer ignore-unused */ mixed $value, ?ValidationContext $context): bool => false)]],

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

295
            ['property' => [new Required(when: static fn (mixed $value, /** @scrutinizer ignore-unused */ ?ValidationContext $context): bool => false)]],

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
296
        );
297
298
        $this->assertTrue($result->isValid());
299
    }
300
301
    public function testRuleHandlerWithoutImplement(): void
302
    {
303
        $ruleHandler = new class () {
304
        };
305
        $validator = new Validator();
306
307
        $this->expectException(RuleHandlerInterfaceNotImplementedException::class);
308
        $validator->validate(new ArrayDataSet(['property' => '']), [
309
            'property' => [
310
                new class ($ruleHandler) implements RuleInterface {
311
                    public function __construct(private $ruleHandler)
312
                    {
313
                    }
314
315
                    public function getName(): string
316
                    {
317
                        return 'test';
318
                    }
319
320
                    public function getHandler(): string
321
                    {
322
                        return $this->ruleHandler::class;
323
                    }
324
                },
325
            ],
326
        ]);
327
    }
328
329
    public function testRuleWithoutHandler(): void
330
    {
331
        $this->expectException(RuleHandlerNotFoundException::class);
332
333
        $validator = new Validator();
334
        $validator->validate(new ArrayDataSet(['property' => '']), [
335
            'property' => [
336
                new class () implements RuleInterface {
337
                    public function getName(): string
338
                    {
339
                        return 'test';
340
                    }
341
342
                    public function getHandler(): string
343
                    {
344
                        return 'NonExistClass';
345
                    }
346
                },
347
            ],
348
        ]);
349
    }
350
351
    public function requiredDataProvider(): array
352
    {
353
        $strictRules = [
354
            'orderBy' => [new Required()],
355
            'sort' => [
356
                new In(
357
                    ['asc', 'desc'],
358
                    skipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $isAttributeMissing
359
                ),
360
            ],
361
        ];
362
        $notStrictRules = [
363
            'orderBy' => [new Required()],
364
            'sort' => [
365
                new In(
366
                    ['asc', 'desc'],
367
                    skipOnEmpty: static fn (
368
                        mixed $value,
369
                        bool $isAttributeMissing
370
                    ): bool => $isAttributeMissing || $value === ''
371
                ),
372
            ],
373
        ];
374
375
        return [
376
            [
377
                ['merchantId' => [new Required(), new Number(integerOnly: true)]],
378
                new ArrayDataSet(['merchantId' => null]),
379
                [
380
                    new Error(
381
                        'Value cannot be blank.',
382
                        ['attribute' => 'merchantId'],
383
                        ['merchantId']
384
                    ),
385
                    new Error(
386
                        'The allowed types are integer, float and string.',
387
                        ['attribute' => 'merchantId', 'type' => 'null'],
388
                        ['merchantId']
389
                    ),
390
                ],
391
            ],
392
            [
393
                ['merchantId' => [new Required(), new Number(integerOnly: true, skipOnError: true)]],
394
                new ArrayDataSet(['merchantId' => null]),
395
                [new Error('Value cannot be blank.', [ 'attribute' => 'merchantId'], ['merchantId'])],
396
            ],
397
            [
398
                ['merchantId' => [new Required(), new Number(integerOnly: true, skipOnError: true)]],
399
                new ArrayDataSet(['merchantIdd' => 1]),
400
                [new Error('Value not passed.', ['attribute' => 'merchantId'], ['merchantId'])],
401
            ],
402
403
            [
404
                $strictRules,
405
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'asc']),
406
                [],
407
            ],
408
            [
409
                $notStrictRules,
410
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'asc']),
411
                [],
412
            ],
413
414
            [
415
                $strictRules,
416
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'desc']),
417
                [],
418
            ],
419
            [
420
                $notStrictRules,
421
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'desc']),
422
                [],
423
            ],
424
425
            [
426
                $strictRules,
427
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'up']),
428
                [new Error('This value is not in the list of acceptable values.', ['attribute' => 'sort'], ['sort'])],
429
            ],
430
            [
431
                $notStrictRules,
432
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'up']),
433
                [new Error('This value is not in the list of acceptable values.', ['attribute' => 'sort'], ['sort'])],
434
            ],
435
436
            [
437
                $strictRules,
438
                new ArrayDataSet(['orderBy' => 'name', 'sort' => '']),
439
                [new Error('This value is not in the list of acceptable values.', ['attribute' => 'sort'], ['sort'])],
440
            ],
441
            [
442
                $notStrictRules,
443
                new ArrayDataSet(['orderBy' => 'name', 'sort' => '']),
444
                [],
445
            ],
446
447
            [
448
                $strictRules,
449
                new ArrayDataSet(['orderBy' => 'name']),
450
                [],
451
            ],
452
            [
453
                $notStrictRules,
454
                new ArrayDataSet(['orderBy' => 'name']),
455
                [],
456
            ],
457
458
            [
459
                $strictRules,
460
                new ArrayDataSet(['orderBy' => '']),
461
                [new Error('Value cannot be blank.', ['attribute' => 'orderBy'], ['orderBy'])],
462
            ],
463
            [
464
                $notStrictRules,
465
                new ArrayDataSet(['orderBy' => '']),
466
                [new Error('Value cannot be blank.', ['attribute' => 'orderBy'], ['orderBy'])],
467
            ],
468
469
            [
470
                $strictRules,
471
                new ArrayDataSet([]),
472
                [new Error('Value not passed.', ['attribute' => 'orderBy'], ['orderBy'])],
473
            ],
474
            [
475
                $notStrictRules,
476
                new ArrayDataSet([]),
477
                [new Error('Value not passed.', ['attribute' => 'orderBy'], ['orderBy'])],
478
            ],
479
            [
480
                [
481
                    'name' => [new Required(), new HasLength(min: 3, skipOnError: true)],
482
                    'description' => [new Required(), new HasLength(min: 5, skipOnError: true)],
483
                ],
484
                new ObjectDataSet(
485
                    new class () {
486
                        private string $title = '';
0 ignored issues
show
introduced by
The private property $title is not used, and could be removed.
Loading history...
487
                        private string $description = 'abc123';
0 ignored issues
show
introduced by
The private property $description is not used, and could be removed.
Loading history...
488
                    }
489
                ),
490
                [new Error('Value not passed.', ['attribute' => 'name'], ['name'])],
491
            ],
492
            [
493
                null,
494
                new ObjectDataSet(new ObjectWithDataSet()),
495
                [],
496
            ],
497
        ];
498
    }
499
500
    /**
501
     * @link https://github.com/yiisoft/validator/issues/173
502
     * @link https://github.com/yiisoft/validator/issues/289
503
     * @dataProvider requiredDataProvider
504
     */
505
    public function testRequired(array|null $rules, DataSetInterface $dataSet, array $expectedErrors): void
506
    {
507
        $validator = new Validator();
508
        $result = $validator->validate($dataSet, $rules);
509
        $this->assertEquals($expectedErrors, $result->getErrors());
510
    }
511
512
    public function skipOnEmptyDataProvider(): array
513
    {
514
        $validator = new Validator();
515
        $rules = [
516
            'name' => [new HasLength(min: 8)],
517
            'age' => [new Number(integerOnly: true, min: 18)],
518
        ];
519
        $stringLessThanMinMessage = 'This value must contain at least 8 characters.';
520
        $incorrectNumberMessage = 'The allowed types are integer, float and string.';
521
        $intMessage = 'Value must be an integer.';
522
        $intLessThanMinMessage = 'Value must be no less than 18.';
523
524
        return [
525
            'rule / validator, skipOnEmpty: false, value not passed' => [
526
                $validator,
527
                new ArrayDataSet([
528
                    'name' => 'Dmitriy',
529
                ]),
530
                $rules,
531
                [
532
                    new Error($stringLessThanMinMessage, [
533
                        'min' => 8,
534
                        'attribute' => 'name',
535
                        'number' => 7,
536
                    ], ['name']),
537
                    new Error($incorrectNumberMessage, [
538
                        'attribute' => 'age',
539
                        'type' => 'null',
540
                    ], ['age']),
541
                ],
542
            ],
543
            'rule / validator, skipOnEmpty: false, value is empty' => [
544
                $validator,
545
                new ArrayDataSet([
546
                    'name' => 'Dmitriy',
547
                    'age' => null,
548
                ]),
549
                $rules,
550
                [
551
                    new Error($stringLessThanMinMessage, [
552
                        'min' => 8,
553
                        'attribute' => 'name',
554
                        'number' => 7,
555
                    ], ['name']),
556
                    new Error($incorrectNumberMessage, [
557
                        'attribute' => 'age',
558
                        'type' => 'null',
559
                    ], ['age']),
560
                ],
561
            ],
562
            'rule / validator, skipOnEmpty: false, value is not empty' => [
563
                $validator,
564
                new ArrayDataSet([
565
                    'name' => 'Dmitriy',
566
                    'age' => 17,
567
                ]),
568
                $rules,
569
                [
570
                    new Error($stringLessThanMinMessage, [
571
                        'min' => 8,
572
                        'attribute' => 'name',
573
                        'number' => 7,
574
                    ], ['name']),
575
                    new Error($intLessThanMinMessage, [
576
                        'min' => 18,
577
                        'attribute' => 'age',
578
                        'value' => 17,
579
                    ], ['age']),
580
                ],
581
            ],
582
583
            'rule, skipOnEmpty: true, value not passed' => [
584
                $validator,
585
                new ArrayDataSet([
586
                    'name' => 'Dmitriy',
587
                ]),
588
                [
589
                    'name' => [new HasLength(min: 8)],
590
                    'age' => [new Number(integerOnly: true, min: 18, skipOnEmpty: true)],
591
                ],
592
                [
593
                    new Error($stringLessThanMinMessage, [
594
                        'min' => 8,
595
                        'attribute' => 'name',
596
                        'number' => 7,
597
                    ], ['name']),
598
                ],
599
            ],
600
            'rule, skipOnEmpty: true, value is empty (null)' => [
601
                $validator,
602
                new ArrayDataSet([
603
                    'name' => 'Dmitriy',
604
                    'age' => null,
605
                ]),
606
                [
607
                    'name' => [new HasLength(min: 8)],
608
                    'age' => [new Number(integerOnly: true, min: 18, skipOnEmpty: true)],
609
                ],
610
                [
611
                    new Error($stringLessThanMinMessage, [
612
                        'min' => 8,
613
                        'attribute' => 'name',
614
                        'number' => 7,
615
                    ], ['name']),
616
                ],
617
            ],
618
            'rule, skipOnEmpty: true, value is empty (empty string after trimming), trimString is false' => [
619
                $validator,
620
                new ArrayDataSet([
621
                    'name' => ' ',
622
                    'age' => 17,
623
                ]),
624
                [
625
                    'name' => [new HasLength(min: 8, skipOnEmpty: true)],
626
                    'age' => [new Number(integerOnly: true, min: 18)],
627
                ],
628
                [
629
                    new Error($stringLessThanMinMessage, [
630
                        'min' => 8,
631
                        'attribute' => 'name',
632
                        'number' => 1,
633
                    ], ['name']),
634
                    new Error($intLessThanMinMessage, [
635
                        'min' => 18,
636
                        'attribute' => 'age',
637
                        'value' => 17,
638
                    ], ['age']),
639
                ],
640
            ],
641
            'rule, skipOnEmpty: SkipOnEmpty, value is empty (empty string after trimming), trimString is true' => [
642
                $validator,
643
                new ArrayDataSet([
644
                    'name' => ' ',
645
                    'age' => 17,
646
                ]),
647
                [
648
                    'name' => [new HasLength(min: 8, skipOnEmpty: new WhenEmpty(trimString: true))],
649
                    'age' => [new Number(integerOnly: true, min: 18)],
650
                ],
651
                [
652
                    new Error($intLessThanMinMessage, [
653
                        'min' => 18,
654
                        'attribute' => 'age',
655
                        'value' => 17,
656
                    ], ['age']),
657
                ],
658
            ],
659
            'rule, skipOnEmpty: true, value is not empty' => [
660
                $validator,
661
                new ArrayDataSet([
662
                    'name' => 'Dmitriy',
663
                    'age' => 17,
664
                ]),
665
                [
666
                    'name' => [new HasLength(min: 8)],
667
                    'age' => [new Number(integerOnly: true, min: 18, skipOnEmpty: true)],
668
                ],
669
                [
670
                    new Error($stringLessThanMinMessage, [
671
                        'min' => 8,
672
                        'attribute' => 'name',
673
                        'number' => 7,
674
                    ], ['name']),
675
                    new Error($intLessThanMinMessage, [
676
                        'min' => 18,
677
                        'attribute' => 'age',
678
                        'value' => 17,
679
                    ], ['age']),
680
                ],
681
            ],
682
683
            'rule, skipOnEmpty: SkipOnNull, value not passed' => [
684
                $validator,
685
                new ArrayDataSet([
686
                    'name' => 'Dmitriy',
687
                ]),
688
                [
689
                    'name' => [new HasLength(min: 8)],
690
                    'age' => [new Number(integerOnly: true, min: 18, skipOnEmpty: new WhenNull())],
691
                ],
692
                [
693
                    new Error($stringLessThanMinMessage, [
694
                        'min' => 8,
695
                        'attribute' => 'name',
696
                        'number' => 7,
697
                    ], ['name']),
698
                ],
699
            ],
700
            'rule, skipOnEmpty: SkipOnNull, value is empty' => [
701
                $validator,
702
                new ArrayDataSet([
703
                    'name' => 'Dmitriy',
704
                    'age' => null,
705
                ]),
706
                [
707
                    'name' => [new HasLength(min: 8)],
708
                    'age' => [new Number(integerOnly: true, min: 18, skipOnEmpty: new WhenNull())],
709
                ],
710
                [
711
                    new Error($stringLessThanMinMessage, [
712
                        'min' => 8,
713
                        'attribute' => 'name',
714
                        'number' => 7,
715
                    ], ['name']),
716
                ],
717
            ],
718
            'rule, skipOnEmpty: SkipOnNull, value is not empty' => [
719
                $validator,
720
                new ArrayDataSet([
721
                    'name' => 'Dmitriy',
722
                    'age' => 17,
723
                ]),
724
                [
725
                    'name' => [new HasLength(min: 8)],
726
                    'age' => [new Number(integerOnly: true, min: 18, skipOnEmpty: new WhenNull())],
727
                ],
728
                [
729
                    new Error($stringLessThanMinMessage, [
730
                        'min' => 8,
731
                        'attribute' => 'name',
732
                        'number' => 7,
733
                    ], ['name']),
734
                    new Error($intLessThanMinMessage, [
735
                        'min' => 18,
736
                        'attribute' => 'age',
737
                        'value' => 17,
738
                    ], ['age']),
739
                ],
740
            ],
741
            'rule, skipOnEmpty: SkipOnNull, value is not empty (empty string)' => [
742
                $validator,
743
                new ArrayDataSet([
744
                    'name' => 'Dmitriy',
745
                    'age' => '',
746
                ]),
747
                [
748
                    'name' => [new HasLength(min: 8)],
749
                    'age' => [new Number(integerOnly: true, min: 18, skipOnEmpty: new WhenNull())],
750
                ],
751
                [
752
                    new Error($stringLessThanMinMessage, [
753
                        'min' => 8,
754
                        'attribute' => 'name',
755
                        'number' => 7,
756
                    ], ['name']),
757
                    new Error($intMessage, [
758
                        'attribute' => 'age',
759
                        'value' => '',
760
                    ], ['age']),
761
                ],
762
            ],
763
764
            'rule, skipOnEmpty: custom callback, value not passed' => [
765
                $validator,
766
                new ArrayDataSet([
767
                    'name' => 'Dmitriy',
768
                ]),
769
                [
770
                    'name' => [new HasLength(min: 8)],
771
                    'age' => [
772
                        new Number(
773
                            integerOnly: true,
774
                            min: 18,
775
                            skipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

775
                            skipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
776
                        ),
777
                    ],
778
                ],
779
                [
780
                    new Error($stringLessThanMinMessage, [
781
                        'min' => 8,
782
                        'attribute' => 'name',
783
                        'number' => 7,
784
                    ], ['name']),
785
                    new Error($incorrectNumberMessage, [
786
                        'attribute' => 'age',
787
                        'type' => 'null',
788
                    ], ['age']),
789
                ],
790
            ],
791
            'rule, skipOnEmpty: custom callback, value is empty' => [
792
                $validator,
793
                new ArrayDataSet([
794
                    'name' => 'Dmitriy',
795
                    'age' => 0,
796
                ]),
797
                [
798
                    'name' => [new HasLength(min: 8)],
799
                    'age' => [
800
                        new Number(
801
                            integerOnly: true,
802
                            min: 18,
803
                            skipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

803
                            skipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
804
                        ),
805
                    ],
806
                ],
807
                [
808
                    new Error($stringLessThanMinMessage, [
809
                        'min' => 8,
810
                        'attribute' => 'name',
811
                        'number' => 7,
812
                    ], ['name']),
813
                ],
814
            ],
815
            'rule, skipOnEmpty, custom callback, value is not empty' => [
816
                $validator,
817
                new ArrayDataSet([
818
                    'name' => 'Dmitriy',
819
                    'age' => 17,
820
                ]),
821
                [
822
                    'name' => [new HasLength(min: 8)],
823
                    'age' => [
824
                        new Number(
825
                            integerOnly: true,
826
                            min: 18,
827
                            skipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

827
                            skipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
828
                        ),
829
                    ],
830
                ],
831
                [
832
                    new Error($stringLessThanMinMessage, [
833
                        'min' => 8,
834
                        'attribute' => 'name',
835
                        'number' => 7,
836
                    ], ['name']),
837
                    new Error($intLessThanMinMessage, [
838
                        'min' => 18,
839
                        'attribute' => 'age',
840
                        'value' => 17,
841
                    ], ['age']),
842
                ],
843
            ],
844
            'rule, skipOnEmpty, custom callback, value is not empty (null)' => [
845
                $validator,
846
                new ArrayDataSet([
847
                    'name' => 'Dmitriy',
848
                    'age' => null,
849
                ]),
850
                [
851
                    'name' => [new HasLength(min: 8)],
852
                    'age' => [
853
                        new Number(
854
                            integerOnly: true,
855
                            min: 18,
856
                            skipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

856
                            skipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
857
                        ),
858
                    ],
859
                ],
860
                [
861
                    new Error($stringLessThanMinMessage, [
862
                        'min' => 8,
863
                        'attribute' => 'name',
864
                        'number' => 7,
865
                    ], ['name']),
866
                    new Error($incorrectNumberMessage, [
867
                        'attribute' => 'age',
868
                        'type' => 'null',
869
                    ], ['age']),
870
                ],
871
            ],
872
873
            'validator, skipOnEmpty: true, value not passed' => [
874
                new Validator(defaultSkipOnEmpty: true),
875
                new ArrayDataSet([
876
                    'name' => 'Dmitriy',
877
                ]),
878
                $rules,
879
                [
880
                    new Error($stringLessThanMinMessage, [
881
                        'min' => 8,
882
                        'attribute' => 'name',
883
                        'number' => 7,
884
                    ], ['name']),
885
                ],
886
            ],
887
            'validator, skipOnEmpty: true, value is empty' => [
888
                new Validator(defaultSkipOnEmpty: true),
889
                new ArrayDataSet([
890
                    'name' => 'Dmitriy',
891
                    'age' => null,
892
                ]),
893
                $rules,
894
                [
895
                    new Error($stringLessThanMinMessage, [
896
                        'min' => 8,
897
                        'attribute' => 'name',
898
                        'number' => 7,
899
                    ], ['name']),
900
                ],
901
            ],
902
            'validator, skipOnEmpty: true, value is not empty' => [
903
                new Validator(defaultSkipOnEmpty: true),
904
                new ArrayDataSet([
905
                    'name' => 'Dmitriy',
906
                    'age' => 17,
907
                ]),
908
                $rules,
909
                [
910
                    new Error($stringLessThanMinMessage, [
911
                        'min' => 8,
912
                        'attribute' => 'name',
913
                        'number' => 7,
914
                    ], ['name']),
915
                    new Error($intLessThanMinMessage, [
916
                        'min' => 18,
917
                        'attribute' => 'age',
918
                        'value' => 17,
919
                    ], ['age']),
920
                ],
921
            ],
922
923
            'validator, skipOnEmpty: SkipOnNull, value not passed' => [
924
                new Validator(defaultSkipOnEmpty: new WhenNull()),
925
                new ArrayDataSet([
926
                    'name' => 'Dmitriy',
927
                ]),
928
                $rules,
929
                [
930
                    new Error($stringLessThanMinMessage, [
931
                        'min' => 8,
932
                        'attribute' => 'name',
933
                        'number' => 7,
934
                    ], ['name']),
935
                ],
936
            ],
937
            'validator, skipOnEmpty: SkipOnNull, value is empty' => [
938
                new Validator(defaultSkipOnEmpty: new WhenNull()),
939
                new ArrayDataSet([
940
                    'name' => 'Dmitriy',
941
                    'age' => null,
942
                ]),
943
                $rules,
944
                [
945
                    new Error($stringLessThanMinMessage, [
946
                        'min' => 8,
947
                        'attribute' => 'name',
948
                        'number' => 7,
949
                    ], ['name']),
950
                ],
951
            ],
952
            'validator, skipOnEmpty: SkipOnNull, value is not empty' => [
953
                new Validator(defaultSkipOnEmpty: new WhenNull()),
954
                new ArrayDataSet([
955
                    'name' => 'Dmitriy',
956
                    'age' => 17,
957
                ]),
958
                $rules,
959
                [
960
                    new Error($stringLessThanMinMessage, [
961
                        'min' => 8,
962
                        'attribute' => 'name',
963
                        'number' => 7,
964
                    ], ['name']),
965
                    new Error($intLessThanMinMessage, [
966
                        'min' => 18,
967
                        'attribute' => 'age',
968
                        'value' => 17,
969
                    ], ['age']),
970
                ],
971
            ],
972
            'validator, skipOnEmpty: SkipOnNull, value is not empty (empty string)' => [
973
                new Validator(defaultSkipOnEmpty: new WhenNull()),
974
                new ArrayDataSet([
975
                    'name' => 'Dmitriy',
976
                    'age' => '',
977
                ]),
978
                $rules,
979
                [
980
                    new Error($stringLessThanMinMessage, [
981
                        'min' => 8,
982
                        'attribute' => 'name',
983
                        'number' => 7,
984
                    ], ['name']),
985
                    new Error($intMessage, [
986
                        'attribute' => 'age',
987
                        'value' => '',
988
                    ], ['age']),
989
                ],
990
            ],
991
992
            'validator, skipOnEmpty: custom callback, value not passed' => [
993
                new Validator(
994
                    defaultSkipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

994
                    defaultSkipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
995
                ),
996
                new ArrayDataSet([
997
                    'name' => 'Dmitriy',
998
                ]),
999
                $rules,
1000
                [
1001
                    new Error($stringLessThanMinMessage, [
1002
                        'min' => 8,
1003
                        'attribute' => 'name',
1004
                        'number' => 7,
1005
                    ], ['name']),
1006
                    new Error($incorrectNumberMessage, [
1007
                        'attribute' => 'age',
1008
                        'type' => 'null',
1009
                    ], ['age']),
1010
                ],
1011
            ],
1012
            'validator, skipOnEmpty: custom callback, value is empty' => [
1013
                new Validator(
1014
                    defaultSkipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1014
                    defaultSkipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1015
                ),
1016
                new ArrayDataSet([
1017
                    'name' => 'Dmitriy',
1018
                    'age' => 0,
1019
                ]),
1020
                $rules,
1021
                [
1022
                    new Error($stringLessThanMinMessage, [
1023
                        'min' => 8,
1024
                        'attribute' => 'name',
1025
                        'number' => 7,
1026
                    ], ['name']),
1027
                ],
1028
            ],
1029
            'validator, skipOnEmpty: custom callback, value is not empty' => [
1030
                new Validator(
1031
                    defaultSkipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1031
                    defaultSkipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1032
                ),
1033
                new ArrayDataSet([
1034
                    'name' => 'Dmitriy',
1035
                    'age' => 17,
1036
                ]),
1037
                $rules,
1038
                [
1039
                    new Error($stringLessThanMinMessage, [
1040
                        'min' => 8,
1041
                        'attribute' => 'name',
1042
                        'number' => 7,
1043
                    ], ['name']),
1044
                    new Error($intLessThanMinMessage, [
1045
                        'min' => 18,
1046
                        'attribute' => 'age',
1047
                        'value' => 17,
1048
                    ], ['age']),
1049
                ],
1050
            ],
1051
            'validator, skipOnEmpty: custom callback, value is not empty (null)' => [
1052
                new Validator(
1053
                    defaultSkipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1053
                    defaultSkipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1054
                ),
1055
                new ArrayDataSet([
1056
                    'name' => 'Dmitriy',
1057
                    'age' => null,
1058
                ]),
1059
                $rules,
1060
                [
1061
                    new Error($stringLessThanMinMessage, [
1062
                        'min' => 8,
1063
                        'attribute' => 'name',
1064
                        'number' => 7,
1065
                    ], ['name']),
1066
                    new Error($incorrectNumberMessage, [
1067
                        'attribute' => 'age',
1068
                        'type' => 'null',
1069
                    ], ['age']),
1070
                ],
1071
            ],
1072
        ];
1073
    }
1074
1075
    /**
1076
     * @param StubRuleWithOptions[] $rules
1077
     * @param Error[] $expectedErrors
1078
     *
1079
     * @dataProvider skipOnEmptyDataProvider
1080
     */
1081
    public function testSkipOnEmpty(Validator $validator, ArrayDataSet $data, array $rules, array $expectedErrors): void
1082
    {
1083
        $result = $validator->validate($data, $rules);
1084
        $this->assertEquals($expectedErrors, $result->getErrors());
1085
    }
1086
1087
    public function initSkipOnEmptyDataProvider(): array
1088
    {
1089
        return [
1090
            'null' => [
1091
                null,
1092
                new class () {
1093
                    #[Number]
1094
                    public ?string $name = null;
1095
                },
1096
                false,
1097
            ],
1098
            'false' => [
1099
                false,
1100
                new class () {
1101
                    #[Number]
1102
                    public ?string $name = null;
1103
                },
1104
                false,
1105
            ],
1106
            'true' => [
1107
                true,
1108
                new class () {
1109
                    #[Number]
1110
                    public ?string $name = null;
1111
                },
1112
                true,
1113
            ],
1114
            'callable' => [
1115
                new WhenNull(),
1116
                new class () {
1117
                    #[Number]
1118
                    public ?string $name = null;
1119
                },
1120
                true,
1121
            ],
1122
            'do-not-override-rule' => [
1123
                false,
1124
                new class () {
1125
                    #[Number(skipOnEmpty: true)]
1126
                    public string $name = '';
1127
                },
1128
                true,
1129
            ],
1130
        ];
1131
    }
1132
1133
    /**
1134
     * @dataProvider initSkipOnEmptyDataProvider
1135
     */
1136
    public function testInitSkipOnEmpty(
1137
        bool|callable|null $skipOnEmpty,
1138
        mixed $data,
1139
        bool $expectedResult,
1140
    ): void {
1141
        $validator = new Validator(defaultSkipOnEmpty: $skipOnEmpty);
1142
1143
        $result = $validator->validate($data);
1144
1145
        $this->assertSame($expectedResult, $result->isValid());
1146
    }
1147
1148
    public function testObjectWithAttributesOnly(): void
1149
    {
1150
        $object = new ObjectWithAttributesOnly();
1151
1152
        $validator = new Validator();
1153
1154
        $result = $validator->validate($object);
1155
1156
        $this->assertFalse($result->isValid());
1157
        $this->assertCount(1, $result->getErrorMessages());
1158
        $this->assertStringStartsWith('This value must contain at least', $result->getErrorMessages()[0]);
1159
    }
1160
1161
    public function testRuleWithoutSkipOnEmpty(): void
1162
    {
1163
        $validator = new Validator(defaultSkipOnEmpty: new WhenNull());
1164
1165
        $data = new class () {
1166
            #[NotNull]
1167
            public ?string $name = null;
1168
        };
1169
1170
        $result = $validator->validate($data);
1171
1172
        $this->assertFalse($result->isValid());
1173
    }
1174
1175
    public function testValidateWithSingleRule(): void
1176
    {
1177
        $result = (new Validator())->validate(3, new Number(min: 5));
1178
1179
        $this->assertFalse($result->isValid());
1180
        $this->assertSame(
1181
            ['' => ['Value must be no less than 5.']],
1182
            $result->getErrorMessagesIndexedByPath(),
1183
        );
1184
    }
1185
1186
    public function testComposition(): void
1187
    {
1188
        $validator = new class () implements ValidatorInterface {
1189
            private Validator $validator;
1190
1191
            public function __construct()
1192
            {
1193
                $this->validator = new Validator();
1194
            }
1195
1196
            public function validate(
1197
                mixed $data,
1198
                callable|iterable|object|string|null $rules = null,
1199
                ?ValidationContext $context = null
1200
            ): Result {
1201
                $context ??= new ValidationContext();
1202
1203
                $result = $this->validator->validate($data, $rules, $context);
1204
1205
                return $context->getParameter('forceSuccess') === true ? new Result() : $result;
1206
            }
1207
        };
1208
1209
        $rules = [
1210
            static function ($value, $rule, ValidationContext $context) {
1211
                $context->setParameter('forceSuccess', true);
1212
                return (new Result())->addError('fail');
1213
            },
1214
        ];
1215
1216
        $result = $validator->validate([], $rules);
1217
1218
        $this->assertTrue($result->isValid());
1219
    }
1220
1221
    public function testRulesWithWrongKey(): void
1222
    {
1223
        $validator = new Validator();
1224
1225
        $this->expectException(InvalidArgumentException::class);
1226
        $this->expectExceptionMessage('An attribute can only have an integer or a string type. bool given.');
1227
        $validator->validate([], new IteratorWithBooleanKey());
1228
    }
1229
1230
    public function testRulesWithWrongRule(): void
1231
    {
1232
        $validator = new Validator();
1233
1234
        $this->expectException(InvalidArgumentException::class);
1235
        $message = 'Rule should be either an instance of Yiisoft\Validator\RuleInterface or a callable, int given.';
1236
        $this->expectExceptionMessage($message);
1237
        $validator->validate([], [new BooleanValue(), 1]);
1238
    }
1239
1240
    public function testRulesAsObjectNameWithRuleAttributes(): void
1241
    {
1242
        $validator = new Validator();
1243
        $result = $validator->validate(['name' => 'Test name'], ObjectWithAttributesOnly::class);
1244
        $this->assertTrue($result->isValid());
1245
    }
1246
1247
    public function testRulesAsObjectWithRuleAttributes(): void
1248
    {
1249
        $validator = new Validator();
1250
        $result = $validator->validate(['name' => 'Test name'], new ObjectWithAttributesOnly());
1251
        $this->assertTrue($result->isValid());
1252
    }
1253
1254
    public function testDataWithPostValidationHook(): void
1255
    {
1256
        $validator = new Validator();
1257
        $this->assertFalse(ObjectWithPostValidationHook::$hookCalled);
1258
1259
        $result = $validator->validate(new ObjectWithPostValidationHook(), ['called' => new BooleanValue()]);
1260
        $this->assertFalse($result->isValid());
1261
        $this->assertTrue(ObjectWithPostValidationHook::$hookCalled);
1262
    }
1263
1264
    public function testSkippingRuleInPreValidate(): void
1265
    {
1266
        $data = ['agree' => false, 'viewsCount' => -1];
1267
        $rules = [
1268
            'agree' => [new BooleanValue(skipOnEmpty: static fn (): bool => true), new TrueValue()],
1269
            'viewsCount' => [new Number(integerOnly: true, min: 0)],
1270
        ];
1271
        $validator = new Validator();
1272
1273
        $result = $validator->validate($data, $rules);
1274
        $this->assertSame(
1275
            [
1276
                'agree' => ['The value must be "1".'],
1277
                'viewsCount' => ['Value must be no less than 0.'],
1278
            ],
1279
            $result->getErrorMessagesIndexedByPath(),
1280
        );
1281
    }
1282
1283
    public function testDefaultTranslatorWithIntl(): void
1284
    {
1285
        $data = ['number' => 3];
1286
        $rules = [
1287
            'number' => new Number(
1288
                integerOnly: true,
1289
                max: 2,
1290
                tooBigMessage: '{value, selectordinal, one{#-one} two{#-two} few{#-few} other{#-other}}',
1291
            ),
1292
        ];
1293
        $validator = new Validator();
1294
1295
        $result = $validator->validate($data, $rules);
1296
        $this->assertSame(['number' => ['3-few']], $result->getErrorMessagesIndexedByPath());
1297
    }
1298
1299
    public function dataSimpleForm(): array
1300
    {
1301
        return [
1302
            [
1303
                [
1304
                    'name' => [
1305
                        'Имя плохое.',
1306
                    ],
1307
                    'mail' => [
1308
                        'This value is not a valid email address.',
1309
                    ],
1310
                ],
1311
                null,
1312
            ],
1313
            [
1314
                [
1315
                    'name' => [
1316
                        'name плохое.',
1317
                    ],
1318
                    'mail' => [
1319
                        'This value is not a valid email address.',
1320
                    ],
1321
                ],
1322
                new ValidationContext(attributeTranslator: new NullAttributeTranslator()),
1323
            ],
1324
        ];
1325
    }
1326
1327
    /**
1328
     * @dataProvider dataSimpleForm
1329
     */
1330
    public function testSimpleForm(array $expectedMessages, ?ValidationContext $validationContext): void
1331
    {
1332
        $form = new SimpleForm();
1333
1334
        $result = (new Validator())->validate($form, context: $validationContext);
1335
1336
        $this->assertSame(
1337
            $expectedMessages,
1338
            $result->getErrorMessagesIndexedByPath()
1339
        );
1340
    }
1341
1342
    public function dataOriginalValueUsage(): array
1343
    {
1344
        $data = [
1345
            'null' => [null, null],
1346
            'string' => ['hello', 'hello'],
1347
            'integer' => [42, 42],
1348
            'array' => [['param' => 7], ['param' => 7]],
1349
            'array-data-set' => [['param' => 42], new ArrayDataSet(['param' => 42])],
1350
            'single-value-data-set' => [7, new SingleValueDataSet(7)],
1351
        ];
1352
1353
        $object = new stdClass();
1354
        $data['object'] = [$object, $object];
1355
1356
        $simpleDto = new SimpleDto();
1357
        $data['object-data-set'] = [$simpleDto, new ObjectDataSet($simpleDto)];
1358
1359
        return $data;
1360
    }
1361
1362
    /**
1363
     * @dataProvider dataOriginalValueUsage
1364
     */
1365
    public function testOriginalValueUsage(mixed $expectedValue, mixed $value): void
1366
    {
1367
        $valueHandled = false;
1368
        $valueInHandler = null;
1369
1370
        (new Validator())->validate(
1371
            $value,
1372
            static function ($value) use (&$valueHandled, &$valueInHandler): Result {
1373
                $valueHandled = true;
1374
                $valueInHandler = $value;
1375
                return new Result();
1376
            },
1377
        );
1378
1379
        $this->assertTrue($valueHandled);
1380
        $this->assertSame($expectedValue, $valueInHandler);
1381
    }
1382
1383
    public function testRuleWithBuiltInHandler(): void
1384
    {
1385
        $rule = new RuleWithBuiltInHandler();
1386
1387
        $result = (new Validator())->validate(19, $rule);
1388
1389
        $this->assertSame(
1390
            ['' => ['Value must be 42.']],
1391
            $result->getErrorMessagesIndexedByPath()
1392
        );
1393
    }
1394
}
1395