Passed
Pull Request — master (#521)
by Alexander
06:25 queued 02:59
created

CompareTest.php$2 ➔ __toString()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Tests\Rule;
6
7
use DateTime;
8
use InvalidArgumentException;
9
use RuntimeException;
10
use stdClass;
11
use Stringable;
12
use Yiisoft\Validator\DataSetInterface;
13
use Yiisoft\Validator\DataWrapperInterface;
14
use Yiisoft\Validator\Rule\Compare;
15
use Yiisoft\Validator\Rule\CompareType;
16
use Yiisoft\Validator\RuleInterface;
17
use Yiisoft\Validator\Tests\Rule\Base\RuleTestCase;
18
use Yiisoft\Validator\Tests\Rule\Base\RuleWithOptionsTestTrait;
19
use Yiisoft\Validator\Tests\Rule\Base\SkipOnErrorTestTrait;
20
use Yiisoft\Validator\Tests\Rule\Base\WhenTestTrait;
21
22
use function is_string;
23
24
final class CompareTest extends RuleTestCase
25
{
26
    use RuleWithOptionsTestTrait;
27
    use SkipOnErrorTestTrait;
28
    use WhenTestTrait;
29
30
    public function testInitWithWrongType(): void
31
    {
32
        $this->expectException(InvalidArgumentException::class);
33
        $message = 'Type "float" is not supported. The valid types are: "original", "string", "number".';
34
        $this->expectExceptionMessage($message);
35
36
        new Compare(type: 'float');
37
    }
38
39
    public function testInitWithWrongOperator(): void
40
    {
41
        $this->expectException(InvalidArgumentException::class);
42
        $message = 'Operator "=" is not supported. The valid operators are: "==", "===", "!=", "!==", ">", ">=", ' .
43
            '"<", "<=".';
44
        $this->expectExceptionMessage($message);
45
46
        new Compare(1, operator: '=');
47
    }
48
49
    public function testGetName(): void
50
    {
51
        $rule = new Compare();
52
        $this->assertSame('compare', $rule->getName());
53
    }
54
55
    public function dataOptions(): array
56
    {
57
        return [
58
            [
59
                new Compare(1),
60
                [
61
                    'targetValue' => 1,
62
                    'targetAttribute' => null,
63
                    'incorrectInputMessage' => [
64
                        'template' => 'The allowed types are integer, float, string, boolean and null.',
65
                        'parameters' => [
66
                            'targetValue' => 1,
67
                            'targetAttribute' => null,
68
                            'targetValueOrAttribute' => 1,
69
                        ],
70
                    ],
71
                    'incorrectDataSetTypeMessage' => [
72
                        'template' => 'The attribute value returned from a custom data set must have a scalar type or be null.',
73
                        'parameters' => [
74
                            'targetValue' => 1,
75
                            'targetAttribute' => null,
76
                            'targetValueOrAttribute' => 1,
77
                        ],
78
                    ],
79
                    'message' => [
80
                        'template' => 'Value must be equal to "{targetValueOrAttribute}".',
81
                        'parameters' => [
82
                            'targetValue' => 1,
83
                            'targetAttribute' => null,
84
                            'targetValueOrAttribute' => 1,
85
                        ],
86
                    ],
87
                    'type' => 'string',
88
                    'operator' => '==',
89
                    'skipOnEmpty' => false,
90
                    'skipOnError' => false,
91
                ],
92
            ],
93
            [
94
                new Compare(1, type: CompareType::NUMBER),
95
                [
96
                    'targetValue' => 1,
97
                    'targetAttribute' => null,
98
                    'incorrectInputMessage' => [
99
                        'template' => 'The allowed types are integer, float, string, boolean and null.',
100
                        'parameters' => [
101
                            'targetValue' => 1,
102
                            'targetAttribute' => null,
103
                            'targetValueOrAttribute' => 1,
104
                        ],
105
                    ],
106
                    'incorrectDataSetTypeMessage' => [
107
                        'template' => 'The attribute value returned from a custom data set must have a scalar type or be null.',
108
                        'parameters' => [
109
                            'targetValue' => 1,
110
                            'targetAttribute' => null,
111
                            'targetValueOrAttribute' => 1,
112
                        ],
113
                    ],
114
                    'message' => [
115
                        'template' => 'Value must be equal to "{targetValueOrAttribute}".',
116
                        'parameters' => [
117
                            'targetValue' => 1,
118
                            'targetAttribute' => null,
119
                            'targetValueOrAttribute' => 1,
120
                        ],
121
                    ],
122
                    'type' => 'number',
123
                    'operator' => '==',
124
                    'skipOnEmpty' => false,
125
                    'skipOnError' => false,
126
                ],
127
            ],
128
            [
129
                new Compare(1, type: CompareType::NUMBER, operator: '>='),
130
                [
131
                    'targetValue' => 1,
132
                    'targetAttribute' => null,
133
                    'incorrectInputMessage' => [
134
                        'template' => 'The allowed types are integer, float, string, boolean and null.',
135
                        'parameters' => [
136
                            'targetValue' => 1,
137
                            'targetAttribute' => null,
138
                            'targetValueOrAttribute' => 1,
139
                        ],
140
                    ],
141
                    'incorrectDataSetTypeMessage' => [
142
                        'template' => 'The attribute value returned from a custom data set must have a scalar type or be null.',
143
                        'parameters' => [
144
                            'targetValue' => 1,
145
                            'targetAttribute' => null,
146
                            'targetValueOrAttribute' => 1,
147
                        ],
148
                    ],
149
                    'message' => [
150
                        'template' => 'Value must be greater than or equal to "{targetValueOrAttribute}".',
151
                        'parameters' => [
152
                            'targetValue' => 1,
153
                            'targetAttribute' => null,
154
                            'targetValueOrAttribute' => 1,
155
                        ],
156
                    ],
157
                    'type' => 'number',
158
                    'operator' => '>=',
159
                    'skipOnEmpty' => false,
160
                    'skipOnError' => false,
161
                ],
162
            ],
163
            [
164
                new Compare('YES'),
165
                [
166
                    'targetValue' => 'YES',
167
                    'targetAttribute' => null,
168
                    'incorrectInputMessage' => [
169
                        'template' => 'The allowed types are integer, float, string, boolean and null.',
170
                        'parameters' => [
171
                            'targetValue' => 'YES',
172
                            'targetAttribute' => null,
173
                            'targetValueOrAttribute' => 'YES',
174
                        ],
175
                    ],
176
                    'incorrectDataSetTypeMessage' => [
177
                        'template' => 'The attribute value returned from a custom data set must have a scalar type or be null.',
178
                        'parameters' => [
179
                            'targetValue' => 'YES',
180
                            'targetAttribute' => null,
181
                            'targetValueOrAttribute' => 'YES',
182
                        ],
183
                    ],
184
                    'message' => [
185
                        'template' => 'Value must be equal to "{targetValueOrAttribute}".',
186
                        'parameters' => [
187
                            'targetValue' => 'YES',
188
                            'targetAttribute' => null,
189
                            'targetValueOrAttribute' => 'YES',
190
                        ],
191
                    ],
192
                    'type' => 'string',
193
                    'operator' => '==',
194
                    'skipOnEmpty' => false,
195
                    'skipOnError' => false,
196
                ],
197
            ],
198
            [
199
                new Compare('YES', skipOnEmpty: true),
200
                [
201
                    'targetValue' => 'YES',
202
                    'targetAttribute' => null,
203
                    'incorrectInputMessage' => [
204
                        'template' => 'The allowed types are integer, float, string, boolean and null.',
205
                        'parameters' => [
206
                            'targetValue' => 'YES',
207
                            'targetAttribute' => null,
208
                            'targetValueOrAttribute' => 'YES',
209
                        ],
210
                    ],
211
                    'incorrectDataSetTypeMessage' => [
212
                        'template' => 'The attribute value returned from a custom data set must have a scalar type or be null.',
213
                        'parameters' => [
214
                            'targetValue' => 'YES',
215
                            'targetAttribute' => null,
216
                            'targetValueOrAttribute' => 'YES',
217
                        ],
218
                    ],
219
                    'message' => [
220
                        'template' => 'Value must be equal to "{targetValueOrAttribute}".',
221
                        'parameters' => [
222
                            'targetValue' => 'YES',
223
                            'targetAttribute' => null,
224
                            'targetValueOrAttribute' => 'YES',
225
                        ],
226
                    ],
227
                    'type' => 'string',
228
                    'operator' => '==',
229
                    'skipOnEmpty' => true,
230
                    'skipOnError' => false,
231
                ],
232
            ],
233
            [
234
                new Compare('YES', operator: '!=='),
235
                [
236
                    'targetValue' => 'YES',
237
                    'targetAttribute' => null,
238
                    'incorrectInputMessage' => [
239
                        'template' => 'The allowed types are integer, float, string, boolean and null.',
240
                        'parameters' => [
241
                            'targetValue' => 'YES',
242
                            'targetAttribute' => null,
243
                            'targetValueOrAttribute' => 'YES',
244
                        ],
245
                    ],
246
                    'incorrectDataSetTypeMessage' => [
247
                        'template' => 'The attribute value returned from a custom data set must have a scalar type or be null.',
248
                        'parameters' => [
249
                            'targetValue' => 'YES',
250
                            'targetAttribute' => null,
251
                            'targetValueOrAttribute' => 'YES',
252
                        ],
253
                    ],
254
                    'message' => [
255
                        'template' => 'Value must not be equal to "{targetValueOrAttribute}".',
256
                        'parameters' => [
257
                            'targetValue' => 'YES',
258
                            'targetAttribute' => null,
259
                            'targetValueOrAttribute' => 'YES',
260
                        ],
261
                    ],
262
                    'type' => 'string',
263
                    'operator' => '!==',
264
                    'skipOnEmpty' => false,
265
                    'skipOnError' => false,
266
                ],
267
            ],
268
            [
269
                new Compare('YES', message: 'Custom message for {targetValueOrAttribute}.'),
270
                [
271
                    'targetValue' => 'YES',
272
                    'targetAttribute' => null,
273
                    'incorrectInputMessage' => [
274
                        'template' => 'The allowed types are integer, float, string, boolean and null.',
275
                        'parameters' => [
276
                            'targetValue' => 'YES',
277
                            'targetAttribute' => null,
278
                            'targetValueOrAttribute' => 'YES',
279
                        ],
280
                    ],
281
                    'incorrectDataSetTypeMessage' => [
282
                        'template' => 'The attribute value returned from a custom data set must have a scalar type or be null.',
283
                        'parameters' => [
284
                            'targetValue' => 'YES',
285
                            'targetAttribute' => null,
286
                            'targetValueOrAttribute' => 'YES',
287
                        ],
288
                    ],
289
                    'message' => [
290
                        'template' => 'Custom message for {targetValueOrAttribute}.',
291
                        'parameters' => [
292
                            'targetValue' => 'YES',
293
                            'targetAttribute' => null,
294
                            'targetValueOrAttribute' => 'YES',
295
                        ],
296
                    ],
297
                    'type' => 'string',
298
                    'operator' => '==',
299
                    'skipOnEmpty' => false,
300
                    'skipOnError' => false,
301
                ],
302
            ],
303
            [
304
                new Compare(null, 'test'),
305
                [
306
                    'targetValue' => null,
307
                    'targetAttribute' => 'test',
308
                    'incorrectInputMessage' => [
309
                        'template' => 'The allowed types are integer, float, string, boolean and null.',
310
                        'parameters' => [
311
                            'targetValue' => null,
312
                            'targetAttribute' => 'test',
313
                            'targetValueOrAttribute' => 'test',
314
                        ],
315
                    ],
316
                    'incorrectDataSetTypeMessage' => [
317
                        'template' => 'The attribute value returned from a custom data set must have a scalar type or be null.',
318
                        'parameters' => [
319
                            'targetValue' => null,
320
                            'targetAttribute' => 'test',
321
                            'targetValueOrAttribute' => 'test',
322
                        ],
323
                    ],
324
                    'message' => [
325
                        'template' => 'Value must be equal to "{targetValueOrAttribute}".',
326
                        'parameters' => [
327
                            'targetValue' => null,
328
                            'targetAttribute' => 'test',
329
                            'targetValueOrAttribute' => 'test',
330
                        ],
331
                    ],
332
                    'type' => 'string',
333
                    'operator' => '==',
334
                    'skipOnEmpty' => false,
335
                    'skipOnError' => false,
336
                ],
337
            ],
338
            [
339
                new Compare(
340
                    null,
341
                    'test',
342
                    incorrectInputMessage: 'Custom message 1.',
343
                    incorrectDataSetTypeMessage: 'Custom message 2.',
344
                    message: 'Custom message 3.',
345
                ),
346
                [
347
                    'targetValue' => null,
348
                    'targetAttribute' => 'test',
349
                    'incorrectInputMessage' => [
350
                        'template' => 'Custom message 1.',
351
                        'parameters' => [
352
                            'targetValue' => null,
353
                            'targetAttribute' => 'test',
354
                            'targetValueOrAttribute' => 'test',
355
                        ],
356
                    ],
357
                    'incorrectDataSetTypeMessage' => [
358
                        'template' => 'Custom message 2.',
359
                        'parameters' => [
360
                            'targetValue' => null,
361
                            'targetAttribute' => 'test',
362
                            'targetValueOrAttribute' => 'test',
363
                        ],
364
                    ],
365
                    'message' => [
366
                        'template' => 'Custom message 3.',
367
                        'parameters' => [
368
                            'targetValue' => null,
369
                            'targetAttribute' => 'test',
370
                            'targetValueOrAttribute' => 'test',
371
                        ],
372
                    ],
373
                    'type' => 'string',
374
                    'operator' => '==',
375
                    'skipOnEmpty' => false,
376
                    'skipOnError' => false,
377
                ],
378
            ],
379
            [
380
                new Compare(1, 'test'),
381
                [
382
                    'targetValue' => 1,
383
                    'targetAttribute' => 'test',
384
                    'incorrectInputMessage' => [
385
                        'template' => 'The allowed types are integer, float, string, boolean and null.',
386
                        'parameters' => [
387
                            'targetValue' => 1,
388
                            'targetAttribute' => 'test',
389
                            'targetValueOrAttribute' => 1,
390
                        ],
391
                    ],
392
                    'incorrectDataSetTypeMessage' => [
393
                        'template' => 'The attribute value returned from a custom data set must have a scalar type or be null.',
394
                        'parameters' => [
395
                            'targetValue' => 1,
396
                            'targetAttribute' => 'test',
397
                            'targetValueOrAttribute' => 1,
398
                        ],
399
                    ],
400
                    'message' => [
401
                        'template' => 'Value must be equal to "{targetValueOrAttribute}".',
402
                        'parameters' => [
403
                            'targetValue' => 1,
404
                            'targetAttribute' => 'test',
405
                            'targetValueOrAttribute' => 1,
406
                        ],
407
                    ],
408
                    'type' => 'string',
409
                    'operator' => '==',
410
                    'skipOnEmpty' => false,
411
                    'skipOnError' => false,
412
                ],
413
            ],
414
        ];
415
    }
416
417
    public function dataValidationPassed(): array
418
    {
419
        $targetStringableFloat = new class () implements Stringable {
420
            public function __toString(): string
421
            {
422
                return '100.5';
423
            }
424
        };
425
        $stringableFloat = new class () implements Stringable {
426
            public function __toString(): string
427
            {
428
                return '100.50';
429
            }
430
        };
431
        $targetStringableUuid = new class () implements Stringable {
432
            public function __toString(): string
433
            {
434
                return '3b98a689-7d49-48bb-8741-7e27f220b69a';
435
            }
436
        };
437
        $stringableUuid = new class () implements Stringable {
438
            public function __toString(): string
439
            {
440
                return 'd62f2b3f-707f-451a-8819-046ff8436a4f';
441
            }
442
        };
443
444
        return [
445
            // Number specific, expressions
446
447
            'target value: float, value: float with the same value as expression result, type: number, operator: ==' => [
448
                1 - 0.83,
449
                [new Compare(0.17, type: CompareType::NUMBER)],
450
            ],
451
            'target value: float, value: float with the same value as expression result, type: number, operator: ===' => [
452
                1 - 0.83,
453
                [new Compare(0.17, type: CompareType::NUMBER, operator: '===')],
454
            ],
455
            'target value: float, value: float with the same value as expression result, type: number, operator: >=' => [
456
                1 - 0.83,
457
                [new Compare(0.17, type: CompareType::NUMBER, operator: '>=')],
458
            ],
459
460
            // Number / original specific, decimal places, directly provided values
461
462
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: number, operator: ==' => [
463
                '100.50',
464
                [new Compare('100.5', type: CompareType::NUMBER)],
465
            ],
466
            'target value: float, value: string float with the same value, but extra decimal place (0), type: number, operator: ==' => [
467
                '100.50',
468
                [new Compare(100.5, type: CompareType::NUMBER)],
469
            ],
470
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: number, operator: ===' => [
471
                '100.50',
472
                [new Compare('100.5', type: CompareType::NUMBER, operator: '===')],
473
            ],
474
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: original, operator: ==' => [
475
                '100.50', [new Compare('100.5', type: CompareType::ORIGINAL)], ['' => ['Value must be equal to "100.5".']],
476
            ],
477
478
            // Number / original specific, decimal places, values provided via stringable objects
479
480
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: number, operator: ==' => [
481
                $stringableFloat,
482
                [new Compare($targetStringableFloat, type: CompareType::NUMBER)],
483
            ],
484
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: number, operator: >=' => [
485
                $stringableFloat,
486
                [new Compare($targetStringableFloat, type: CompareType::NUMBER, operator: '>=')],
487
            ],
488
489
            // String / original specific, character order, directly provided values
490
491
            'target value: uuidv4, value: greater uuidv4, type: string, operator: >' => [
492
                'd62f2b3f-707f-451a-8819-046ff8436a4f',
493
                [new Compare('3b98a689-7d49-48bb-8741-7e27f220b69a', operator: '>')],
494
            ],
495
            'target value: character, value: character located further within alphabet, type: string, operator: ==' => [
496
                'b',
497
                [new Compare('a', operator: '>')],
498
            ],
499
500
            // String / original specific, character order, values provided via stringable objects
501
502
            'target value: stringable uuidv4, value: greater stringable uuidv4, type: string, operator: >' => [
503
                $stringableUuid,
504
                [new Compare($targetStringableUuid, operator: '>')],
505
            ],
506
507
            // Original specific, datetime objects
508
509
            'target value: DateTime object, value: DateTime object with the same value, type: original, operator: ==' => [
510
                new DateTime('2023-02-01 12:57:12'),
511
                [new Compare(new DateTime('2023-02-01 12:57:12'), type: CompareType::ORIGINAL)],
512
            ],
513
            'target value: DateTime object, value: DateTime object with the same value, type: original, operator: !==' => [
514
                new DateTime('2023-02-01 12:57:12'),
515
                [new Compare(new DateTime('2023-02-01 12:57:12'), type: CompareType::ORIGINAL, operator: '!==')],
516
            ],
517
            'target value: DateTime object, value: DateTime object with the same value, type: original, operator: >=' => [
518
                new DateTime('2023-02-01 12:57:12'),
519
                [new Compare(new DateTime('2023-02-01 12:57:12'), type: CompareType::ORIGINAL, operator: '>=')],
520
            ],
521
        ];
522
    }
523
524
    public function dataValidationPassedWithDifferentTypes(): array
525
    {
526
        $customDataSet = new class () implements DataSetInterface {
527
            public function getAttributeValue(string $attribute): mixed
528
            {
529
                return 100;
530
            }
531
532
            public function getData(): ?array
533
            {
534
                return null;
535
            }
536
537
            public function hasAttribute(string $attribute): bool
538
            {
539
                return true;
540
            }
541
        };
542
        $initialData = [
543
            // Basic
544
545
            'target value: integer, value: integer with the same value, type: string, operator: ==' => [
546
                100,
547
                [new Compare(100)],
548
            ],
549
            'target value: integer, value: integer with the same value, type: string, operator: ===' => [
550
                100,
551
                [new Compare(100, operator: '===')],
552
            ],
553
            'target value: integer, value: lower integer, type: string, operator: !=' => [
554
                99,
555
                [new Compare(100, operator: '!=')],
556
            ],
557
            'target value: integer, value: greater integer, type: string, operator: !=' => [
558
                101,
559
                [new Compare(100, operator: '!=')],
560
            ],
561
            'target value: integer, value: lower integer, type: string, operator: !==' => [
562
                101,
563
                [new Compare(100, operator: '!==')],
564
            ],
565
            'target value: integer, value: greater integer, type: string, operator: !==' => [
566
                101,
567
                [new Compare(100, operator: '!==')],
568
            ],
569
            'target value: integer, value: greater integer, type: string, operator: >' => [
570
                101,
571
                [new Compare(100, operator: '>')],
572
            ],
573
            'target value: integer, value: integer with the same value, type: string, operator: >=' => [
574
                100,
575
                [new Compare(100, operator: '>=')],
576
            ],
577
            'target value: integer, value: greater integer, type: string, operator: >=' => [
578
                101,
579
                [new Compare(100, operator: '>=')],
580
            ],
581
            'target value: integer, value: lower integer, type: string, operator: <' => [
582
                99,
583
                [new Compare(100, operator: '<')],
584
            ],
585
            'target value: integer, value: integer with the same value, type: string, operator: <=' => [
586
                100,
587
                [new Compare(100, operator: '<=')],
588
            ],
589
            'target value: integer, value: lower integer, type: string, operator: <=' => [
590
                99,
591
                [new Compare(100, operator: '<=')],
592
            ],
593
594
            // Boolean
595
596
            'target value: boolean (false), value: boolean (true), type: string, operator: >=' => [
597
                true,
598
                [new Compare(false, operator: '>=')],
599
            ],
600
601
            // Different types for non-strict equality
602
603
            'target value: empty string, value: null, type: string, operator: ==' => [
604
                null,
605
                [new Compare('')],
606
            ],
607
            'target value: integer, value: string integer with the same value, type: string, operator: ==' => [
608
                '100',
609
                [new Compare(100)],
610
            ],
611
612
            // Different types for non-strict inequality
613
614
            'target value: integer, value: float, type: string, operator: !=' => [
615
                100.00001,
616
                [new Compare(100, operator: '!=')],
617
            ],
618
            'target value: integer, value: boolean, type: string, operator: !=' => [
619
                false,
620
                [new Compare(100, operator: '!=')],
621
            ],
622
623
            // Different types for strict inequality
624
625
            'target value: integer, value: boolean, type: string, operator: !==' => [
626
                false,
627
                [new Compare(100, operator: '!==')],
628
            ],
629
            'target value: integer, value: string integer with the same value, type: string, operator: !==' => [
630
                '100',
631
                [new Compare(100, operator: '!==')],
632
            ],
633
            'target value: integer, value: float with the same value, but extra decimal place (0), type: string, operator: !==' => [
634
                100.0,
635
                [new Compare(100, operator: '!==')],
636
            ],
637
638
            // Target attribute
639
640
            'target attribute: array key, target attribute value: integer, attribute value: integer with the same value, type: string, operator: ==' => [
641
                ['attribute' => 100, 'number' => 100],
642
                ['number' => new Compare(targetAttribute: 'attribute')],
643
            ],
644
            'target attribute: array key, target attribute value: integer, attribute value: lower integer, type: string, operator: <=' => [
645
                ['attribute' => 100, 'number' => 99],
646
                ['number' => new Compare(targetAttribute: 'attribute', operator: '<=')],
647
            ],
648
            'target attribute: object property, target attribute value: integer, attribute value: integer with the same value, type: string, operator: ==' => [
649
                new class () {
650
                    public int $attribute = 100;
651
                    public int $number = 100;
652
                },
653
                ['number' => new Compare(targetAttribute: 'attribute', operator: '<=')],
654
            ],
655
            'target attribute: custom data set attribute, target attribute value: integer, attribute value: integer with the same value, type: string, operator: ==' => [
656
                $customDataSet,
657
                ['number' => new Compare(targetAttribute: 'attribute', operator: '<=')],
658
            ],
659
        ];
660
661
        return $this->extendDataWithDifferentTypes($initialData);
662
    }
663
664
    /**
665
     * @dataProvider dataValidationPassed
666
     * @dataProvider dataValidationPassedWithDifferentTypes
667
     */
668
    public function testValidationPassed(mixed $data, ?array $rules = null): void
669
    {
670
        parent::testValidationPassed($data, $rules);
671
    }
672
673
    public function dataValidationFailed(): array
674
    {
675
        $incorrectDataSet = new class () implements DataWrapperInterface {
676
            public function getAttributeValue(string $attribute): mixed
677
            {
678
                return new stdClass();
679
            }
680
681
            public function getData(): ?array
682
            {
683
                return null;
684
            }
685
686
            public function getSource(): mixed
687
            {
688
                return false;
689
            }
690
691
            public function hasAttribute(string $attribute): bool
692
            {
693
                return false;
694
            }
695
        };
696
        $targetStringableFloat = new class () implements Stringable {
697
            public function __toString(): string
698
            {
699
                return '100.5';
700
            }
701
        };
702
        $stringableFloat = new class () implements Stringable {
703
            public function __toString(): string
704
            {
705
                return '100.50';
706
            }
707
        };
708
709
        return [
710
            // Incorrect input
711
712
            'incorrect input' => [
713
                [],
714
                [new Compare(false)],
715
                ['' => ['The allowed types are integer, float, string, boolean and null.']],
716
            ],
717
            'custom incorrect input message' => [
718
                [],
719
                [new Compare(false, incorrectInputMessage: 'Custom incorrect input message.')],
720
                ['' => ['Custom incorrect input message.']],
721
            ],
722
            'custom incorrect input message with parameters' => [
723
                [],
724
                [new Compare(false, incorrectInputMessage: 'Attribute - {attribute}, type - {type}.')],
725
                ['' => ['Attribute - , type - array.']],
726
            ],
727
            'custom incorrect input message with parameters, attribute set' => [
728
                ['data' => []],
729
                ['data' => new Compare(false, incorrectInputMessage: 'Attribute - {attribute}, type - {type}.')],
730
                ['data' => ['Attribute - data, type - array.']],
731
            ],
732
733
            // Incorrect data set input
734
735
            'incorrect data set type' => [
736
                $incorrectDataSet,
737
                [new Compare(targetAttribute: 'test')],
738
                ['' => ['The attribute value returned from a custom data set must have a scalar type or be null.']],
739
            ],
740
            'custom incorrect data set type message' => [
741
                $incorrectDataSet,
742
                [
743
                    new Compare(
744
                        targetAttribute: 'test',
745
                        incorrectDataSetTypeMessage: 'Custom incorrect data set type message.',
746
                    ),
747
                ],
748
                ['' => ['Custom incorrect data set type message.']],
749
            ],
750
            'custom incorrect data set type message with parameters' => [
751
                $incorrectDataSet,
752
                [
753
                    new Compare(
754
                        targetAttribute: 'test',
755
                        incorrectDataSetTypeMessage: 'Type - {type}.',
756
                    ),
757
                ],
758
                ['' => ['Type - stdClass.']],
759
            ],
760
761
            // Custom message
762
763
            'custom message' => [101, [new Compare(100, message: 'Custom message.')], ['' => ['Custom message.']]],
764
            'custom message with parameters, target value set' => [
765
                101,
766
                [
767
                    new Compare(
768
                        100,
769
                        message: 'Attribute - {attribute}, target value - {targetValue}, target attribute - ' .
770
                        '{targetAttribute}, target value or attribute - {targetValueOrAttribute}, value - {value}.',
771
                    ),
772
                ],
773
                [
774
                    '' => [
775
                        'Attribute - , target value - 100, target attribute - , target value or attribute - 100, ' .
776
                        'value - 101.',
777
                    ],
778
                ],
779
            ],
780
            'custom message with parameters, attribute and target attribute set' => [
781
                ['attribute' => 100, 'number' => 101],
782
                [
783
                    'number' => new Compare(
784
                        null,
785
                        'attribute',
786
                        message: 'Attribute - {attribute}, target value - {targetValue}, target attribute - ' .
787
                        '{targetAttribute}, target value or attribute - {targetValueOrAttribute}, value - {value}.',
788
                        operator: '===',
789
                    ),
790
                ],
791
                [
792
                    'number' => [
793
                        'Attribute - number, target value - , target attribute - attribute, target value or ' .
794
                        'attribute - 100, value - 101.',
795
                    ],
796
                ],
797
            ],
798
799
            // String / original specific, falsy values
800
801
            'target value: integer (0), value: null, type: string, operator: ==' => [
802
                null,
803
                [new Compare(0)],
804
                ['' => ['Value must be equal to "0".']],
805
            ],
806
807
            // Number / original specific, decimal places, directly provided values
808
809
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: string, operator: ==' => [
810
                '100.50', [new Compare('100.5')], ['' => ['Value must be equal to "100.5".']],
811
            ],
812
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: string, operator: ===' => [
813
                '100.50', [new Compare('100.5', operator: '===')], ['' => ['Value must be equal to "100.5".']],
814
            ],
815
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: original, operator: ===' => [
816
                '100.50', [new Compare('100.5', type: CompareType::ORIGINAL, operator: '===')], ['' => ['Value must be equal to "100.5".']],
817
            ],
818
819
            // Number / original specific, decimal places, values provided via stringable objects
820
821
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: string, operator: ==' => [
822
                $stringableFloat,
823
                [new Compare($targetStringableFloat)],
824
                ['' => ['Value must be equal to "100.5".']],
825
            ],
826
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: string, operator: ===' => [
827
                $stringableFloat,
828
                [new Compare($targetStringableFloat, operator: '===')],
829
                ['' => ['Value must be equal to "100.5".']],
830
            ],
831
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: original, operator: ==' => [
832
                $stringableFloat,
833
                [new Compare($targetStringableFloat, type: CompareType::ORIGINAL)],
834
                ['' => ['Value must be equal to "100.5".']],
835
            ],
836
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: original, operator: ===' => [
837
                $stringableFloat,
838
                [new Compare($targetStringableFloat, type: CompareType::ORIGINAL, operator: '===')],
839
                ['' => ['Value must be equal to "100.5".']],
840
            ],
841
        ];
842
    }
843
844
    public function dataValidationFailedWithDifferentTypes(): array
845
    {
846
        $messageEqual = 'Value must be equal to "100".';
847
        $messageNotEqual = 'Value must not be equal to "100".';
848
        $messageGreaterThan = 'Value must be greater than "100".';
849
        $messageGreaterOrEqualThan = 'Value must be greater than or equal to "100".';
850
        $messageLessThan = 'Value must be less than "100".';
851
        $messageLessOrEqualThan = 'Value must be less than or equal to "100".';
852
        $initialData = [
853
            // Basic
854
855
            'target value: integer, value: lower integer, type: string, operator: ==' => [
856
                99,
857
                [new Compare(100)],
858
                ['' => [$messageEqual]],
859
            ],
860
            'target value: integer, value: greater integer, type: string, operator: ==' => [
861
                101,
862
                [new Compare(100)],
863
                ['' => [$messageEqual]],
864
            ],
865
            'target value: integer, value: lower integer, type: string, operator: ===' => [
866
                99,
867
                [new Compare(100, operator: '===')],
868
                ['' => [$messageEqual]],
869
            ],
870
            'target value: integer, value: greater integer, type: string, operator: ===' => [
871
                101,
872
                [new Compare(100, operator: '===')],
873
                ['' => [$messageEqual]],
874
            ],
875
            'target value: integer, value: integer with the same value, type: string, operator: !=' => [
876
                100,
877
                [new Compare(100, operator: '!=')],
878
                ['' => [$messageNotEqual]],
879
            ],
880
            'target value: integer, value: integer with the same value, type: string, operator: !==' => [
881
                100,
882
                [new Compare(100, operator: '!==')],
883
                ['' => [$messageNotEqual]],
884
            ],
885
            'target value: integer, value: integer with the same value, type: string, operator: >' => [
886
                100,
887
                [new Compare(100, operator: '>')],
888
                ['' => [$messageGreaterThan]],
889
            ],
890
            'target value: integer, value: lower integer, type: string, operator: >' => [
891
                99,
892
                [new Compare(100, operator: '>')],
893
                ['' => [$messageGreaterThan]],
894
            ],
895
            'target value: integer, value: lower integer, type: string, operator: >=' => [
896
                99,
897
                [new Compare(100, operator: '>=')],
898
                ['' => [$messageGreaterOrEqualThan]],
899
            ],
900
            'target value: integer, value: integer with the same value, type: string, operator: <' => [
901
                100,
902
                [new Compare(100, operator: '<')],
903
                ['' => [$messageLessThan]],
904
            ],
905
            'target value: integer, value: greater integer, type: string, operator: <' => [
906
                101,
907
                [new Compare(100, operator: '<')],
908
                ['' => [$messageLessThan]],
909
            ],
910
            'target value: integer, value: greater integer, type: string, operator: <=' => [
911
                101,
912
                [new Compare(100, operator: '<=')],
913
                ['' => [$messageLessOrEqualThan]],
914
            ],
915
916
            // Different types for strict equality
917
918
            'target value: empty string, value: null, type: string, operator: ===' => [
919
                null,
920
                [new Compare('', operator: '===')],
921
                ['' => ['Value must be equal to "".']],
922
            ],
923
            'target value: integer, value: string integer with the same value, type: string, operator: ===' => [
924
                '100',
925
                [new Compare(100, operator: '===')],
926
                ['' => [$messageEqual]],
927
            ],
928
            'target value: integer, value: float with the same value, but extra decimal place (0), type: string, operator: ===' => [
929
                100.0,
930
                [new Compare(100, operator: '===')],
931
                ['' => [$messageEqual]],
932
            ],
933
934
            // Different types for non-strict inequality
935
936
            'target value: integer, value: string integer with the same value, type: string, operator: !=' => [
937
                '100',
938
                [new Compare(100, operator: '!=')],
939
                ['' => [$messageNotEqual]],
940
            ],
941
            'target value: integer, value: float with the same value, but extra decimal place (0), type: string, operator: !=' => [
942
                100.0,
943
                [new Compare(100, operator: '!=')],
944
                ['' => [$messageNotEqual]],
945
            ],
946
947
            // Target attribute
948
949
            'target attribute: array key, target attribute value: string integer, attribute value: integer with the same value, type: string, operator: ===' => [
950
                ['attribute' => '100', 'number' => 100],
951
                ['number' => new Compare(null, 'attribute', operator: '===')],
952
                ['number' => [$messageEqual]],
953
            ],
954
            'target attribute: array key, target attribute value: integer, attribute value: greater integer, type: string, operator: <=' => [
955
                ['attribute' => 100, 'number' => 101],
956
                ['number' => new Compare(null, 'attribute', operator: '<=')],
957
                ['number' => [$messageLessOrEqualThan]],
958
            ],
959
        ];
960
961
        return $this->extendDataWithDifferentTypes($initialData);
962
    }
963
964
    /**
965
     * @dataProvider dataValidationFailed
966
     * @dataProvider dataValidationFailedWithDifferentTypes
967
     */
968
    public function testValidationFailed(
969
        mixed $data,
970
        array|RuleInterface|null $rules,
971
        array $errorMessagesIndexedByPath,
972
    ): void {
973
        parent::testValidationFailed($data, $rules, $errorMessagesIndexedByPath);
974
    }
975
976
    private function extendDataWithDifferentTypes(array $initialData): array
977
    {
978
        $dynamicData = [];
979
        $mainType = CompareType::STRING;
980
        $remainingTypes = [CompareType::ORIGINAL, CompareType::NUMBER];
981
        foreach ($remainingTypes as $type) {
982
            foreach ($initialData as $key => $item) {
983
                $rules = [];
984
                foreach ($item[1] as $attribute => $rule) {
985
                    if (!$rule instanceof Compare) {
986
                        throw new RuntimeException('Wrong format for rule.');
987
                    }
988
989
                    $rules[$attribute] = new Compare(
990
                        targetValue: $rule->getTargetValue(),
991
                        targetAttribute: $rule->getTargetAttribute(),
992
                        type: $type,
993
                        operator: $rule->getOperator(),
994
                    );
995
                }
996
997
                if (!is_string($key)) {
998
                    throw new RuntimeException('Data set must have a string name.');
999
                }
1000
1001
                $newKey = str_replace(", type: $mainType,", ", type: $type,", $key);
1002
                if ($key === $newKey) {
1003
                    throw new RuntimeException('Wrong format for type.');
1004
                }
1005
1006
                $itemData = [$item[0], $rules];
1007
                if (isset($item[2])) {
1008
                    $itemData[] = $item[2];
1009
                }
1010
1011
                $dynamicData[$newKey] = $itemData;
1012
            }
1013
        }
1014
1015
        return array_merge($initialData, $dynamicData);
1016
    }
1017
1018
    public function testSkipOnError(): void
1019
    {
1020
        $this->testSkipOnErrorInternal(new Compare(), new Compare(skipOnError: true));
1021
    }
1022
1023
    public function testWhen(): void
1024
    {
1025
        $when = static fn (mixed $value): bool => $value !== null;
1026
        $this->testWhenInternal(new Compare(), new Compare(when: $when));
1027
    }
1028
}
1029