Passed
Pull Request — master (#521)
by Alexander
05:27 queued 02:29
created

CompareTest.php$5 ➔ testValidationPassed()   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
        $dateTime = new DateTime('2023-02-07 12:57:12');
444
445
        return [
446
            // Number specific, expressions
447
448
            'target value: float, value: float with the same value as expression result, type: number, operator: ==' => [
449
                1 - 0.83,
450
                [new Compare(0.17, type: CompareType::NUMBER)],
451
            ],
452
            'target value: float, value: float with the same value as expression result, type: number, operator: ===' => [
453
                1 - 0.83,
454
                [new Compare(0.17, type: CompareType::NUMBER, operator: '===')],
455
            ],
456
            'target value: float, value: float with the same value as expression result, type: number, operator: >=' => [
457
                1 - 0.83,
458
                [new Compare(0.17, type: CompareType::NUMBER, operator: '>=')],
459
            ],
460
461
            // Number / original specific, decimal places, directly provided values
462
463
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: number, operator: ==' => [
464
                '100.50',
465
                [new Compare('100.5', type: CompareType::NUMBER)],
466
            ],
467
            'target value: float, value: string float with the same value, but extra decimal place (0), type: number, operator: ==' => [
468
                '100.50',
469
                [new Compare(100.5, type: CompareType::NUMBER)],
470
            ],
471
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: number, operator: ===' => [
472
                '100.50',
473
                [new Compare('100.5', type: CompareType::NUMBER, operator: '===')],
474
            ],
475
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: original, operator: ==' => [
476
                '100.50', [new Compare('100.5', type: CompareType::ORIGINAL)], ['' => ['Value must be equal to "100.5".']],
477
            ],
478
479
            // Number / original specific, decimal places, values provided via stringable objects
480
481
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: number, operator: ==' => [
482
                $stringableFloat,
483
                [new Compare($targetStringableFloat, type: CompareType::NUMBER)],
484
            ],
485
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: number, operator: >=' => [
486
                $stringableFloat,
487
                [new Compare($targetStringableFloat, type: CompareType::NUMBER, operator: '>=')],
488
            ],
489
490
            // String / original specific, character order, directly provided values
491
492
            'target value: uuidv4, value: greater uuidv4, type: string, operator: >' => [
493
                'd62f2b3f-707f-451a-8819-046ff8436a4f',
494
                [new Compare('3b98a689-7d49-48bb-8741-7e27f220b69a', operator: '>')],
495
            ],
496
            'target value: character, value: character located further within alphabet, type: string, operator: ==' => [
497
                'b',
498
                [new Compare('a', operator: '>')],
499
            ],
500
501
            // String / original specific, character order, values provided via stringable objects
502
503
            'target value: stringable uuidv4, value: greater stringable uuidv4, type: string, operator: >' => [
504
                $stringableUuid,
505
                [new Compare($targetStringableUuid, operator: '>')],
506
            ],
507
508
            // Original specific, datetime
509
510
            'target value: DateTime object, value: DateTime object with the same value, type: original, operator: ==' => [
511
                new DateTime('2023-02-07 12:57:12'),
512
                [new Compare(new DateTime('2023-02-07 12:57:12'), type: CompareType::ORIGINAL)],
513
            ],
514
            'target value: DateTime object, value: the same DateTime object, type: original, operator: ===' => [
515
                $dateTime,
516
                [new Compare($dateTime, type: CompareType::ORIGINAL)],
517
            ],
518
            'target value: DateTime object, value: DateTime object with the same value, type: original, operator: !==' => [
519
                new DateTime('2023-02-07 12:57:12'),
520
                [new Compare(new DateTime('2023-02-07 12:57:12'), type: CompareType::ORIGINAL, operator: '!==')],
521
            ],
522
            'target value: DateTime object, value: DateTime object with the same value, type: original, operator: >=' => [
523
                new DateTime('2023-02-07 12:57:12'),
524
                [new Compare(new DateTime('2023-02-07 12:57:12'), type: CompareType::ORIGINAL, operator: '>=')],
525
            ],
526
            'target value: human-readable DateTime object, value: greater DateTime object, type: original, operator: >' => [
527
                new DateTime('2022-06-03'),
528
                [new Compare(new DateTime('June 2nd, 2022'), type: CompareType::ORIGINAL, operator: '>')],
529
            ],
530
        ];
531
    }
532
533
    public function dataValidationPassedWithDifferentTypes(): array
534
    {
535
        $customDataSet = new class () implements DataSetInterface {
536
            public function getAttributeValue(string $attribute): mixed
537
            {
538
                return 100;
539
            }
540
541
            public function getData(): ?array
542
            {
543
                return null;
544
            }
545
546
            public function hasAttribute(string $attribute): bool
547
            {
548
                return true;
549
            }
550
        };
551
        $initialData = [
552
            // Basic
553
554
            'target value: integer, value: integer with the same value, type: string, operator: ==' => [
555
                100,
556
                [new Compare(100)],
557
            ],
558
            'target value: integer, value: integer with the same value, type: string, operator: ===' => [
559
                100,
560
                [new Compare(100, operator: '===')],
561
            ],
562
            'target value: integer, value: lower integer, type: string, operator: !=' => [
563
                99,
564
                [new Compare(100, operator: '!=')],
565
            ],
566
            'target value: integer, value: greater integer, type: string, operator: !=' => [
567
                101,
568
                [new Compare(100, operator: '!=')],
569
            ],
570
            'target value: integer, value: lower integer, type: string, operator: !==' => [
571
                101,
572
                [new Compare(100, operator: '!==')],
573
            ],
574
            'target value: integer, value: greater integer, type: string, operator: !==' => [
575
                101,
576
                [new Compare(100, operator: '!==')],
577
            ],
578
            'target value: integer, value: greater integer, type: string, operator: >' => [
579
                101,
580
                [new Compare(100, operator: '>')],
581
            ],
582
            'target value: integer, value: integer with the same value, type: string, operator: >=' => [
583
                100,
584
                [new Compare(100, operator: '>=')],
585
            ],
586
            'target value: integer, value: greater integer, type: string, operator: >=' => [
587
                101,
588
                [new Compare(100, operator: '>=')],
589
            ],
590
            'target value: integer, value: lower integer, type: string, operator: <' => [
591
                99,
592
                [new Compare(100, operator: '<')],
593
            ],
594
            'target value: integer, value: integer with the same value, type: string, operator: <=' => [
595
                100,
596
                [new Compare(100, operator: '<=')],
597
            ],
598
            'target value: integer, value: lower integer, type: string, operator: <=' => [
599
                99,
600
                [new Compare(100, operator: '<=')],
601
            ],
602
603
            // Boolean
604
605
            'target value: boolean (false), value: boolean (true), type: string, operator: >=' => [
606
                true,
607
                [new Compare(false, operator: '>=')],
608
            ],
609
610
            // Different types for non-strict equality
611
612
            'target value: empty string, value: null, type: string, operator: ==' => [
613
                null,
614
                [new Compare('')],
615
            ],
616
            'target value: integer, value: string integer with the same value, type: string, operator: ==' => [
617
                '100',
618
                [new Compare(100)],
619
            ],
620
621
            // Different types for non-strict inequality
622
623
            'target value: integer, value: float, type: string, operator: !=' => [
624
                100.00001,
625
                [new Compare(100, operator: '!=')],
626
            ],
627
            'target value: integer, value: boolean, type: string, operator: !=' => [
628
                false,
629
                [new Compare(100, operator: '!=')],
630
            ],
631
632
            // Different types for strict inequality
633
634
            'target value: integer, value: boolean, type: string, operator: !==' => [
635
                false,
636
                [new Compare(100, operator: '!==')],
637
            ],
638
            'target value: integer, value: string integer with the same value, type: string, operator: !==' => [
639
                '100',
640
                [new Compare(100, operator: '!==')],
641
            ],
642
            'target value: integer, value: float with the same value, but extra decimal place (0), type: string, operator: !==' => [
643
                100.0,
644
                [new Compare(100, operator: '!==')],
645
            ],
646
647
            // Target attribute
648
649
            'target attribute: array key, target attribute value: integer, attribute value: integer with the same value, type: string, operator: ==' => [
650
                ['attribute' => 100, 'number' => 100],
651
                ['number' => new Compare(targetAttribute: 'attribute')],
652
            ],
653
            'target attribute: array key, target attribute value: integer, attribute value: lower integer, type: string, operator: <=' => [
654
                ['attribute' => 100, 'number' => 99],
655
                ['number' => new Compare(targetAttribute: 'attribute', operator: '<=')],
656
            ],
657
            'target attribute: object property, target attribute value: integer, attribute value: integer with the same value, type: string, operator: ==' => [
658
                new class () {
659
                    public int $attribute = 100;
660
                    public int $number = 100;
661
                },
662
                ['number' => new Compare(targetAttribute: 'attribute', operator: '<=')],
663
            ],
664
            'target attribute: custom data set attribute, target attribute value: integer, attribute value: integer with the same value, type: string, operator: ==' => [
665
                $customDataSet,
666
                ['number' => new Compare(targetAttribute: 'attribute', operator: '<=')],
667
            ],
668
        ];
669
670
        return $this->extendDataWithDifferentTypes($initialData);
671
    }
672
673
    /**
674
     * @dataProvider dataValidationPassed
675
     * @dataProvider dataValidationPassedWithDifferentTypes
676
     */
677
    public function testValidationPassed(mixed $data, ?array $rules = null): void
678
    {
679
        parent::testValidationPassed($data, $rules);
680
    }
681
682
    public function dataValidationFailed(): array
683
    {
684
        $incorrectDataSet = new class () implements DataWrapperInterface {
685
            public function getAttributeValue(string $attribute): mixed
686
            {
687
                return new stdClass();
688
            }
689
690
            public function getData(): ?array
691
            {
692
                return null;
693
            }
694
695
            public function getSource(): mixed
696
            {
697
                return false;
698
            }
699
700
            public function hasAttribute(string $attribute): bool
701
            {
702
                return false;
703
            }
704
        };
705
        $targetStringableFloat = new class () implements Stringable {
706
            public function __toString(): string
707
            {
708
                return '100.5';
709
            }
710
        };
711
        $stringableFloat = new class () implements Stringable {
712
            public function __toString(): string
713
            {
714
                return '100.50';
715
            }
716
        };
717
718
        return [
719
            // Incorrect input
720
721
            'incorrect input' => [
722
                [],
723
                [new Compare(false)],
724
                ['' => ['The allowed types are integer, float, string, boolean and null.']],
725
            ],
726
            'custom incorrect input message' => [
727
                [],
728
                [new Compare(false, incorrectInputMessage: 'Custom incorrect input message.')],
729
                ['' => ['Custom incorrect input message.']],
730
            ],
731
            'custom incorrect input message with parameters' => [
732
                [],
733
                [new Compare(false, incorrectInputMessage: 'Attribute - {attribute}, type - {type}.')],
734
                ['' => ['Attribute - , type - array.']],
735
            ],
736
            'custom incorrect input message with parameters, attribute set' => [
737
                ['data' => []],
738
                ['data' => new Compare(false, incorrectInputMessage: 'Attribute - {attribute}, type - {type}.')],
739
                ['data' => ['Attribute - data, type - array.']],
740
            ],
741
742
            // Incorrect data set input
743
744
            'incorrect data set type' => [
745
                $incorrectDataSet,
746
                [new Compare(targetAttribute: 'test')],
747
                ['' => ['The attribute value returned from a custom data set must have a scalar type or be null.']],
748
            ],
749
            'custom incorrect data set type message' => [
750
                $incorrectDataSet,
751
                [
752
                    new Compare(
753
                        targetAttribute: 'test',
754
                        incorrectDataSetTypeMessage: 'Custom incorrect data set type message.',
755
                    ),
756
                ],
757
                ['' => ['Custom incorrect data set type message.']],
758
            ],
759
            'custom incorrect data set type message with parameters' => [
760
                $incorrectDataSet,
761
                [
762
                    new Compare(
763
                        targetAttribute: 'test',
764
                        incorrectDataSetTypeMessage: 'Type - {type}.',
765
                    ),
766
                ],
767
                ['' => ['Type - stdClass.']],
768
            ],
769
770
            // Custom message
771
772
            'custom message' => [101, [new Compare(100, message: 'Custom message.')], ['' => ['Custom message.']]],
773
            'custom message with parameters, target value set' => [
774
                101,
775
                [
776
                    new Compare(
777
                        100,
778
                        message: 'Attribute - {attribute}, target value - {targetValue}, target attribute - ' .
779
                        '{targetAttribute}, target value or attribute - {targetValueOrAttribute}, value - {value}.',
780
                    ),
781
                ],
782
                [
783
                    '' => [
784
                        'Attribute - , target value - 100, target attribute - , target value or attribute - 100, ' .
785
                        'value - 101.',
786
                    ],
787
                ],
788
            ],
789
            'custom message with parameters, attribute and target attribute set' => [
790
                ['attribute' => 100, 'number' => 101],
791
                [
792
                    'number' => new Compare(
793
                        null,
794
                        'attribute',
795
                        message: 'Attribute - {attribute}, target value - {targetValue}, target attribute - ' .
796
                        '{targetAttribute}, target attribute value - {targetAttributeValue}, target value or ' .
797
                        'attribute - {targetValueOrAttribute}, value - {value}.',
798
                        operator: '===',
799
                    ),
800
                ],
801
                [
802
                    'number' => [
803
                        'Attribute - number, target value - , target attribute - attribute, target attribute value ' .
804
                        '- 100, target value or attribute - attribute, value - 101.',
805
                    ],
806
                ],
807
            ],
808
809
            // String / original specific, falsy values
810
811
            'target value: integer (0), value: null, type: string, operator: ==' => [
812
                null,
813
                [new Compare(0)],
814
                ['' => ['Value must be equal to "0".']],
815
            ],
816
817
            // Number / original specific, decimal places, directly provided values
818
819
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: string, operator: ==' => [
820
                '100.50', [new Compare('100.5')], ['' => ['Value must be equal to "100.5".']],
821
            ],
822
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: string, operator: ===' => [
823
                '100.50', [new Compare('100.5', operator: '===')], ['' => ['Value must be equal to "100.5".']],
824
            ],
825
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: original, operator: ===' => [
826
                '100.50', [new Compare('100.5', type: CompareType::ORIGINAL, operator: '===')], ['' => ['Value must be equal to "100.5".']],
827
            ],
828
829
            // Number / original specific, decimal places, values provided via stringable objects
830
831
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: string, operator: ==' => [
832
                $stringableFloat,
833
                [new Compare($targetStringableFloat)],
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: string, operator: ===' => [
837
                $stringableFloat,
838
                [new Compare($targetStringableFloat, operator: '===')],
839
                ['' => ['Value must be equal to "100.5".']],
840
            ],
841
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: original, operator: ==' => [
842
                $stringableFloat,
843
                [new Compare($targetStringableFloat, type: CompareType::ORIGINAL)],
844
                ['' => ['Value must be equal to "100.5".']],
845
            ],
846
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: original, operator: ===' => [
847
                $stringableFloat,
848
                [new Compare($targetStringableFloat, type: CompareType::ORIGINAL, operator: '===')],
849
                ['' => ['Value must be equal to "100.5".']],
850
            ],
851
852
            // Original specific, datetime
853
854
            'target value: human-readable DateTime string, value: greater DateTime string, type: string, operator: >' => [
855
                '2022-06-03',
856
                [new Compare('June 2nd, 2022', operator: '>')],
857
                ['' => ['Value must be greater than "June 2nd, 2022".']],
858
            ],
859
        ];
860
    }
861
862
    public function dataValidationFailedWithDifferentTypes(): array
863
    {
864
        $messageEqual = 'Value must be equal to "100".';
865
        $messageNotEqual = 'Value must not be equal to "100".';
866
        $messageGreaterThan = 'Value must be greater than "100".';
867
        $messageGreaterOrEqualThan = 'Value must be greater than or equal to "100".';
868
        $messageLessThan = 'Value must be less than "100".';
869
        $messageLessOrEqualThan = 'Value must be less than or equal to "100".';
870
        $initialData = [
871
            // Basic
872
873
            'target value: integer, value: lower integer, type: string, operator: ==' => [
874
                99,
875
                [new Compare(100)],
876
                ['' => [$messageEqual]],
877
            ],
878
            'target value: integer, value: greater integer, type: string, operator: ==' => [
879
                101,
880
                [new Compare(100)],
881
                ['' => [$messageEqual]],
882
            ],
883
            'target value: integer, value: lower integer, type: string, operator: ===' => [
884
                99,
885
                [new Compare(100, operator: '===')],
886
                ['' => [$messageEqual]],
887
            ],
888
            'target value: integer, value: greater integer, type: string, operator: ===' => [
889
                101,
890
                [new Compare(100, operator: '===')],
891
                ['' => [$messageEqual]],
892
            ],
893
            'target value: integer, value: integer with the same value, type: string, operator: !=' => [
894
                100,
895
                [new Compare(100, operator: '!=')],
896
                ['' => [$messageNotEqual]],
897
            ],
898
            'target value: integer, value: integer with the same value, type: string, operator: !==' => [
899
                100,
900
                [new Compare(100, operator: '!==')],
901
                ['' => [$messageNotEqual]],
902
            ],
903
            'target value: integer, value: integer with the same value, type: string, operator: >' => [
904
                100,
905
                [new Compare(100, operator: '>')],
906
                ['' => [$messageGreaterThan]],
907
            ],
908
            'target value: integer, value: lower integer, type: string, operator: >' => [
909
                99,
910
                [new Compare(100, operator: '>')],
911
                ['' => [$messageGreaterThan]],
912
            ],
913
            'target value: integer, value: lower integer, type: string, operator: >=' => [
914
                99,
915
                [new Compare(100, operator: '>=')],
916
                ['' => [$messageGreaterOrEqualThan]],
917
            ],
918
            'target value: integer, value: integer with the same value, type: string, operator: <' => [
919
                100,
920
                [new Compare(100, operator: '<')],
921
                ['' => [$messageLessThan]],
922
            ],
923
            'target value: integer, value: greater integer, type: string, operator: <' => [
924
                101,
925
                [new Compare(100, operator: '<')],
926
                ['' => [$messageLessThan]],
927
            ],
928
            'target value: integer, value: greater integer, type: string, operator: <=' => [
929
                101,
930
                [new Compare(100, operator: '<=')],
931
                ['' => [$messageLessOrEqualThan]],
932
            ],
933
934
            // Different types for strict equality
935
936
            'target value: empty string, value: null, type: string, operator: ===' => [
937
                null,
938
                [new Compare('', operator: '===')],
939
                ['' => ['Value must be equal to "".']],
940
            ],
941
            'target value: integer, value: string integer with the same value, type: string, operator: ===' => [
942
                '100',
943
                [new Compare(100, operator: '===')],
944
                ['' => [$messageEqual]],
945
            ],
946
            'target value: integer, value: float with the same value, but extra decimal place (0), type: string, operator: ===' => [
947
                100.0,
948
                [new Compare(100, operator: '===')],
949
                ['' => [$messageEqual]],
950
            ],
951
952
            // Different types for non-strict inequality
953
954
            'target value: integer, value: string integer with the same value, type: string, operator: !=' => [
955
                '100',
956
                [new Compare(100, operator: '!=')],
957
                ['' => [$messageNotEqual]],
958
            ],
959
            'target value: integer, value: float with the same value, but extra decimal place (0), type: string, operator: !=' => [
960
                100.0,
961
                [new Compare(100, operator: '!=')],
962
                ['' => [$messageNotEqual]],
963
            ],
964
965
            // Target attribute
966
967
            'target attribute: array key, target attribute value: string integer, attribute value: integer with the same value, type: string, operator: ===' => [
968
                ['attribute' => '100', 'number' => 100],
969
                ['number' => new Compare(null, 'attribute', operator: '===')],
970
                ['number' => ['Value must be equal to "attribute".']],
971
            ],
972
            'target attribute: array key, target attribute value: integer, attribute value: greater integer, type: string, operator: <=' => [
973
                ['attribute' => 100, 'number' => 101],
974
                ['number' => new Compare(null, 'attribute', operator: '<=')],
975
                ['number' => ['Value must be less than or equal to "attribute".']],
976
            ],
977
        ];
978
979
        return $this->extendDataWithDifferentTypes($initialData);
980
    }
981
982
    /**
983
     * @dataProvider dataValidationFailed
984
     * @dataProvider dataValidationFailedWithDifferentTypes
985
     */
986
    public function testValidationFailed(
987
        mixed $data,
988
        array|RuleInterface|null $rules,
989
        array $errorMessagesIndexedByPath,
990
    ): void {
991
        parent::testValidationFailed($data, $rules, $errorMessagesIndexedByPath);
992
    }
993
994
    private function extendDataWithDifferentTypes(array $initialData): array
995
    {
996
        $dynamicData = [];
997
        $mainType = CompareType::STRING;
998
        $remainingTypes = [CompareType::ORIGINAL, CompareType::NUMBER];
999
        foreach ($remainingTypes as $type) {
1000
            foreach ($initialData as $key => $item) {
1001
                $rules = [];
1002
                foreach ($item[1] as $attribute => $rule) {
1003
                    if (!$rule instanceof Compare) {
1004
                        throw new RuntimeException('Wrong format for rule.');
1005
                    }
1006
1007
                    $rules[$attribute] = new Compare(
1008
                        targetValue: $rule->getTargetValue(),
1009
                        targetAttribute: $rule->getTargetAttribute(),
1010
                        type: $type,
1011
                        operator: $rule->getOperator(),
1012
                    );
1013
                }
1014
1015
                if (!is_string($key)) {
1016
                    throw new RuntimeException('Data set must have a string name.');
1017
                }
1018
1019
                $newKey = str_replace(", type: $mainType,", ", type: $type,", $key);
1020
                if ($key === $newKey) {
1021
                    throw new RuntimeException('Wrong format for type.');
1022
                }
1023
1024
                $itemData = [$item[0], $rules];
1025
                if (isset($item[2])) {
1026
                    $itemData[] = $item[2];
1027
                }
1028
1029
                $dynamicData[$newKey] = $itemData;
1030
            }
1031
        }
1032
1033
        return array_merge($initialData, $dynamicData);
1034
    }
1035
1036
    public function testSkipOnError(): void
1037
    {
1038
        $this->testSkipOnErrorInternal(new Compare(), new Compare(skipOnError: true));
1039
    }
1040
1041
    public function testWhen(): void
1042
    {
1043
        $when = static fn (mixed $value): bool => $value !== null;
1044
        $this->testWhenInternal(new Compare(), new Compare(when: $when));
1045
    }
1046
}
1047