Passed
Pull Request — master (#521)
by
unknown
03:22
created

anonymous//tests/Rule/CompareTest.php$6   A

Complexity

Total Complexity 4

Size/Duplication

Total Lines 19
Duplicated Lines 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
dl 0
loc 19
rs 10
c 1
b 1
f 0
wmc 4
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' => 'number',
88
                    'operator' => '==',
89
                    'skipOnEmpty' => false,
90
                    'skipOnError' => false,
91
                ],
92
            ],
93
            [
94
                new Compare(
95
                    new DateTime('2023-02-07 12:57:12'),
96
                    targetAttribute: 'test',
97
                    incorrectInputMessage: 'Custom message 1.',
98
                    incorrectDataSetTypeMessage: 'Custom message 2.',
99
                    message: 'Custom message 3.',
100
                    type: CompareType::ORIGINAL,
101
                    operator: '>=',
102
                    skipOnEmpty: true,
103
                    skipOnError: true,
104
                    when: static fn (): bool => true,
105
                ),
106
                [
107
                    'targetAttribute' => 'test',
108
                    'incorrectInputMessage' => [
109
                        'template' => 'Custom message 1.',
110
                        'parameters' => [
111
                            'targetAttribute' => 'test',
112
                        ],
113
                    ],
114
                    'incorrectDataSetTypeMessage' => [
115
                        'template' => 'Custom message 2.',
116
                        'parameters' => [
117
                            'targetAttribute' => 'test',
118
                        ],
119
                    ],
120
                    'message' => [
121
                        'template' => 'Custom message 3.',
122
                        'parameters' => [
123
                            'targetAttribute' => 'test',
124
                        ],
125
                    ],
126
                    'type' => 'original',
127
                    'operator' => '>=',
128
                    'skipOnEmpty' => true,
129
                    'skipOnError' => true,
130
                ],
131
            ],
132
        ];
133
    }
134
135
    public function dataValidationPassed(): array
136
    {
137
        $targetStringableFloat = new class () implements Stringable {
138
            public function __toString(): string
139
            {
140
                return '100.5';
141
            }
142
        };
143
        $stringableFloat = new class () implements Stringable {
144
            public function __toString(): string
145
            {
146
                return '100.50';
147
            }
148
        };
149
        $targetStringableUuid = new class () implements Stringable {
150
            public function __toString(): string
151
            {
152
                return '3b98a689-7d49-48bb-8741-7e27f220b69a';
153
            }
154
        };
155
        $stringableUuid = new class () implements Stringable {
156
            public function __toString(): string
157
            {
158
                return 'd62f2b3f-707f-451a-8819-046ff8436a4f';
159
            }
160
        };
161
        $dateTime = new DateTime('2023-02-07 12:57:12');
162
163
        return [
164
            // Number specific, expressions
165
166
            'target value: float, value: float with the same value as expression result, type: number, operator: ==' => [
167
                1 - 0.83,
168
                [new Compare(0.17)],
169
            ],
170
            'target value: float, value: float with the same value as expression result, type: number, operator: ===' => [
171
                1 - 0.83,
172
                [new Compare(0.17, operator: '===')],
173
            ],
174
            'target value: float, value: float with the same value as expression result, type: number, operator: >=' => [
175
                1 - 0.83,
176
                [new Compare(0.17, operator: '>=')],
177
            ],
178
179
            // Number / original specific, decimal places, directly provided values
180
181
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: number, operator: ==' => [
182
                '100.50',
183
                [new Compare('100.5')],
184
            ],
185
            'target value: float, value: string float with the same value, but extra decimal place (0), type: number, operator: ==' => [
186
                '100.50',
187
                [new Compare(100.5)],
188
            ],
189
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: number, operator: ===' => [
190
                '100.50',
191
                [new Compare('100.5', operator: '===')],
192
            ],
193
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: original, operator: ==' => [
194
                '100.50', [new Compare('100.5', type: CompareType::ORIGINAL)], ['' => ['Value must be equal to "100.5".']],
195
            ],
196
197
            // Number / original specific, decimal places, values provided via stringable objects
198
199
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: number, operator: ==' => [
200
                $stringableFloat,
201
                [new Compare($targetStringableFloat)],
202
            ],
203
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: number, operator: >=' => [
204
                $stringableFloat,
205
                [new Compare($targetStringableFloat, operator: '>=')],
206
            ],
207
208
            // String / original specific, character order, directly provided values
209
210
            'target value: uuidv4, value: greater uuidv4, type: string, operator: >' => [
211
                'd62f2b3f-707f-451a-8819-046ff8436a4f',
212
                [new Compare('3b98a689-7d49-48bb-8741-7e27f220b69a', type: CompareType::STRING, operator: '>')],
213
            ],
214
            'target value: character, value: character located further within alphabet, type: string, operator: ==' => [
215
                'b',
216
                [new Compare('a', type: CompareType::STRING, operator: '>')],
217
            ],
218
219
            // String / original specific, character order, values provided via stringable objects
220
221
            'target value: stringable uuidv4, value: greater stringable uuidv4, type: string, operator: >' => [
222
                $stringableUuid,
223
                [new Compare($targetStringableUuid, type: CompareType::STRING, operator: '>')],
224
            ],
225
226
            // Original specific, datetime
227
228
            'target value: DateTime object, value: DateTime object with the same value, type: original, operator: ==' => [
229
                new DateTime('2023-02-07 12:57:12'),
230
                [new Compare(new DateTime('2023-02-07 12:57:12'), type: CompareType::ORIGINAL)],
231
            ],
232
            'target value: DateTime object, value: the same DateTime object, type: original, operator: ===' => [
233
                $dateTime,
234
                [new Compare($dateTime, type: CompareType::ORIGINAL)],
235
            ],
236
            'target value: DateTime object, value: DateTime object with the same value, type: original, operator: !==' => [
237
                new DateTime('2023-02-07 12:57:12'),
238
                [new Compare(new DateTime('2023-02-07 12:57:12'), type: CompareType::ORIGINAL, operator: '!==')],
239
            ],
240
            'target value: DateTime object, value: DateTime object with the same value, type: original, operator: >=' => [
241
                new DateTime('2023-02-07 12:57:12'),
242
                [new Compare(new DateTime('2023-02-07 12:57:12'), type: CompareType::ORIGINAL, operator: '>=')],
243
            ],
244
            'target value: human-readable DateTime object, value: greater DateTime object, type: original, operator: >' => [
245
                new DateTime('2022-06-03'),
246
                [new Compare(new DateTime('June 2nd, 2022'), type: CompareType::ORIGINAL, operator: '>')],
247
            ],
248
        ];
249
    }
250
251
    public function dataValidationPassedWithDifferentTypes(): array
252
    {
253
        $customDataSet = new class () implements DataSetInterface {
254
            public function getAttributeValue(string $attribute): mixed
255
            {
256
                return 100;
257
            }
258
259
            public function getData(): ?array
260
            {
261
                return null;
262
            }
263
264
            public function hasAttribute(string $attribute): bool
265
            {
266
                return true;
267
            }
268
        };
269
        $initialData = [
270
            // Basic
271
272
            'target value: integer, value: integer with the same value, type: number, operator: ==' => [
273
                100,
274
                [new Compare(100)],
275
            ],
276
            'target value: integer, value: integer with the same value, type: number, operator: ===' => [
277
                100,
278
                [new Compare(100, operator: '===')],
279
            ],
280
            'target value: integer, value: lower integer, type: number, operator: !=' => [
281
                99,
282
                [new Compare(100, operator: '!=')],
283
            ],
284
            'target value: integer, value: greater integer, type: number, operator: !=' => [
285
                101,
286
                [new Compare(100, operator: '!=')],
287
            ],
288
            'target value: integer, value: lower integer, type: number, operator: !==' => [
289
                101,
290
                [new Compare(100, operator: '!==')],
291
            ],
292
            'target value: integer, value: greater integer, type: number, operator: !==' => [
293
                101,
294
                [new Compare(100, operator: '!==')],
295
            ],
296
            'target value: integer, value: greater integer, type: number, operator: >' => [
297
                101,
298
                [new Compare(100, operator: '>')],
299
            ],
300
            'target value: integer, value: integer with the same value, type: number, operator: >=' => [
301
                100,
302
                [new Compare(100, operator: '>=')],
303
            ],
304
            'target value: integer, value: greater integer, type: number, operator: >=' => [
305
                101,
306
                [new Compare(100, operator: '>=')],
307
            ],
308
            'target value: integer, value: lower integer, type: number, operator: <' => [
309
                99,
310
                [new Compare(100, operator: '<')],
311
            ],
312
            'target value: integer, value: integer with the same value, type: number, operator: <=' => [
313
                100,
314
                [new Compare(100, operator: '<=')],
315
            ],
316
            'target value: integer, value: lower integer, type: number, operator: <=' => [
317
                99,
318
                [new Compare(100, operator: '<=')],
319
            ],
320
321
            // Boolean
322
323
            'target value: boolean (false), value: boolean (true), type: number, operator: >=' => [
324
                true,
325
                [new Compare(false, operator: '>=')],
326
            ],
327
328
            // Different types for non-strict equality
329
330
            'target value: empty string, value: null, type: number, operator: ==' => [
331
                null,
332
                [new Compare('')],
333
            ],
334
            'target value: integer, value: string integer with the same value, type: number, operator: ==' => [
335
                '100',
336
                [new Compare(100)],
337
            ],
338
339
            // Different types for non-strict inequality
340
341
            'target value: integer, value: float, type: number, operator: !=' => [
342
                100.00001,
343
                [new Compare(100, operator: '!=')],
344
            ],
345
            'target value: integer, value: boolean, type: number, operator: !=' => [
346
                false,
347
                [new Compare(100, operator: '!=')],
348
            ],
349
350
            // Different types for strict inequality
351
352
            'target value: integer, value: boolean, type: number, operator: !==' => [
353
                false,
354
                [new Compare(100, operator: '!==')],
355
            ],
356
            'target value: integer, value: string integer with the same value, type: number, operator: !==' => [
357
                '100',
358
                [new Compare(100, operator: '!==')],
359
            ],
360
            'target value: integer, value: float with the same value, but extra decimal place (0), type: number, operator: !==' => [
361
                100.0,
362
                [new Compare(100, operator: '!==')],
363
            ],
364
365
            // Target attribute
366
367
            'target attribute: array key, target attribute value: integer, attribute value: integer with the same value, type: number, operator: ==' => [
368
                ['attribute' => 100, 'number' => 100],
369
                ['number' => new Compare(targetAttribute: 'attribute')],
370
            ],
371
            'target attribute: array key, target attribute value: integer, attribute value: lower integer, type: number, operator: <=' => [
372
                ['attribute' => 100, 'number' => 99],
373
                ['number' => new Compare(targetAttribute: 'attribute', operator: '<=')],
374
            ],
375
            'target attribute: object property, target attribute value: integer, attribute value: integer with the same value, type: number, operator: ==' => [
376
                new class () {
377
                    public int $attribute = 100;
378
                    public int $number = 100;
379
                },
380
                ['number' => new Compare(targetAttribute: 'attribute', operator: '<=')],
381
            ],
382
            'target attribute: custom data set attribute, target attribute value: integer, attribute value: integer with the same value, type: number, operator: ==' => [
383
                $customDataSet,
384
                ['number' => new Compare(targetAttribute: 'attribute', operator: '<=')],
385
            ],
386
        ];
387
388
        return $this->extendDataWithDifferentTypes($initialData);
389
    }
390
391
    /**
392
     * @dataProvider dataValidationPassed
393
     * @dataProvider dataValidationPassedWithDifferentTypes
394
     */
395
    public function testValidationPassed(mixed $data, ?array $rules = null): void
396
    {
397
        parent::testValidationPassed($data, $rules);
398
    }
399
400
    public function dataValidationFailed(): array
401
    {
402
        $incorrectDataSet = new class () implements DataWrapperInterface {
403
            public function getAttributeValue(string $attribute): mixed
404
            {
405
                return new stdClass();
406
            }
407
408
            public function getData(): ?array
409
            {
410
                return null;
411
            }
412
413
            public function getSource(): mixed
414
            {
415
                return false;
416
            }
417
418
            public function hasAttribute(string $attribute): bool
419
            {
420
                return false;
421
            }
422
        };
423
        $targetStringableFloat = new class () implements Stringable {
424
            public function __toString(): string
425
            {
426
                return '100.5';
427
            }
428
        };
429
        $stringableFloat = new class () implements Stringable {
430
            public function __toString(): string
431
            {
432
                return '100.50';
433
            }
434
        };
435
436
        return [
437
            // Incorrect input
438
439
            'incorrect input' => [
440
                [],
441
                [new Compare(false)],
442
                ['' => ['The allowed types are integer, float, string, boolean and null.']],
443
            ],
444
            'custom incorrect input message' => [
445
                [],
446
                [new Compare(false, incorrectInputMessage: 'Custom incorrect input message.')],
447
                ['' => ['Custom incorrect input message.']],
448
            ],
449
            'custom incorrect input message with parameters' => [
450
                [],
451
                [new Compare(false, incorrectInputMessage: 'Attribute - {attribute}, type - {type}.')],
452
                ['' => ['Attribute - , type - array.']],
453
            ],
454
            'custom incorrect input message with parameters, attribute set' => [
455
                ['data' => []],
456
                ['data' => new Compare(false, incorrectInputMessage: 'Attribute - {attribute}, type - {type}.')],
457
                ['data' => ['Attribute - data, type - array.']],
458
            ],
459
460
            // Incorrect data set input
461
462
            'incorrect data set type' => [
463
                $incorrectDataSet,
464
                [new Compare(targetAttribute: 'test')],
465
                ['' => ['The attribute value returned from a custom data set must have a scalar type or be null.']],
466
            ],
467
            'custom incorrect data set type message' => [
468
                $incorrectDataSet,
469
                [
470
                    new Compare(
471
                        targetAttribute: 'test',
472
                        incorrectDataSetTypeMessage: 'Custom incorrect data set type message.',
473
                    ),
474
                ],
475
                ['' => ['Custom incorrect data set type message.']],
476
            ],
477
            'custom incorrect data set type message with parameters' => [
478
                $incorrectDataSet,
479
                [
480
                    new Compare(
481
                        targetAttribute: 'test',
482
                        incorrectDataSetTypeMessage: 'Type - {type}.',
483
                    ),
484
                ],
485
                ['' => ['Type - stdClass.']],
486
            ],
487
488
            // Custom message
489
490
            'custom message' => [101, [new Compare(100, message: 'Custom message.')], ['' => ['Custom message.']]],
491
            'custom message with parameters, target value set' => [
492
                101,
493
                [
494
                    new Compare(
495
                        100,
496
                        message: 'Attribute - {attribute}, target value - {targetValue}, target attribute - ' .
497
                        '{targetAttribute}, target value or attribute - {targetValueOrAttribute}, value - {value}.',
498
                    ),
499
                ],
500
                [
501
                    '' => [
502
                        'Attribute - , target value - 100, target attribute - , target value or attribute - 100, ' .
503
                        'value - 101.',
504
                    ],
505
                ],
506
            ],
507
            'custom message with parameters, attribute and target attribute set' => [
508
                ['attribute' => 100, 'number' => 101],
509
                [
510
                    'number' => new Compare(
511
                        null,
512
                        'attribute',
513
                        message: 'Attribute - {attribute}, target value - {targetValue}, target attribute - ' .
514
                        '{targetAttribute}, target attribute value - {targetAttributeValue}, target value or ' .
515
                        'attribute - {targetValueOrAttribute}, value - {value}.',
516
                        operator: '===',
517
                    ),
518
                ],
519
                [
520
                    'number' => [
521
                        'Attribute - number, target value - , target attribute - attribute, target attribute value ' .
522
                        '- 100, target value or attribute - attribute, value - 101.',
523
                    ],
524
                ],
525
            ],
526
527
            // String / original specific, falsy values
528
529
            'target value: integer (0), value: null, type: string, operator: ==' => [
530
                null,
531
                [new Compare(0, type: CompareType::STRING)],
532
                ['' => ['Value must be equal to "0".']],
533
            ],
534
535
            // Number / original specific, decimal places, directly provided values
536
537
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: string, operator: ==' => [
538
                '100.50', [new Compare('100.5', type: CompareType::STRING)], ['' => ['Value must be equal to "100.5".']],
539
            ],
540
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: string, operator: ===' => [
541
                '100.50', [new Compare('100.5', type: CompareType::STRING, operator: '===')], ['' => ['Value must be equal to "100.5".']],
542
            ],
543
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: original, operator: ===' => [
544
                '100.50', [new Compare('100.5', type: CompareType::ORIGINAL, operator: '===')], ['' => ['Value must be equal to "100.5".']],
545
            ],
546
547
            // Number / original specific, decimal places, values provided via stringable objects
548
549
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: string, operator: ==' => [
550
                $stringableFloat,
551
                [new Compare($targetStringableFloat, type: CompareType::STRING)],
552
                ['' => ['Value must be equal to "100.5".']],
553
            ],
554
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: string, operator: ===' => [
555
                $stringableFloat,
556
                [new Compare($targetStringableFloat, type: CompareType::STRING, operator: '===')],
557
                ['' => ['Value must be equal to "100.5".']],
558
            ],
559
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: original, operator: ==' => [
560
                $stringableFloat,
561
                [new Compare($targetStringableFloat, type: CompareType::ORIGINAL)],
562
                ['' => ['Value must be equal to "100.5".']],
563
            ],
564
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: original, operator: ===' => [
565
                $stringableFloat,
566
                [new Compare($targetStringableFloat, type: CompareType::ORIGINAL, operator: '===')],
567
                ['' => ['Value must be equal to "100.5".']],
568
            ],
569
570
            // Original specific, datetime
571
572
            'target value: human-readable DateTime string, value: greater DateTime string, type: string, operator: >' => [
573
                '2022-06-03',
574
                [new Compare('June 2nd, 2022', type: CompareType::STRING, operator: '>')],
575
                ['' => ['Value must be greater than "June 2nd, 2022".']],
576
            ],
577
        ];
578
    }
579
580
    public function dataValidationFailedWithDifferentTypes(): array
581
    {
582
        $messageEqual = 'Value must be equal to "100".';
583
        $messageNotEqual = 'Value must not be equal to "100".';
584
        $messageGreaterThan = 'Value must be greater than "100".';
585
        $messageGreaterOrEqualThan = 'Value must be greater than or equal to "100".';
586
        $messageLessThan = 'Value must be less than "100".';
587
        $messageLessOrEqualThan = 'Value must be less than or equal to "100".';
588
        $initialData = [
589
            // Basic
590
591
            'target value: integer, value: lower integer, type: number, operator: ==' => [
592
                99,
593
                [new Compare(100)],
594
                ['' => [$messageEqual]],
595
            ],
596
            'target value: integer, value: greater integer, type: number, operator: ==' => [
597
                101,
598
                [new Compare(100)],
599
                ['' => [$messageEqual]],
600
            ],
601
            'target value: integer, value: lower integer, type: number, operator: ===' => [
602
                99,
603
                [new Compare(100, operator: '===')],
604
                ['' => [$messageEqual]],
605
            ],
606
            'target value: integer, value: greater integer, type: number, operator: ===' => [
607
                101,
608
                [new Compare(100, operator: '===')],
609
                ['' => [$messageEqual]],
610
            ],
611
            'target value: integer, value: integer with the same value, type: number, operator: !=' => [
612
                100,
613
                [new Compare(100, operator: '!=')],
614
                ['' => [$messageNotEqual]],
615
            ],
616
            'target value: integer, value: integer with the same value, type: number, operator: !==' => [
617
                100,
618
                [new Compare(100, operator: '!==')],
619
                ['' => [$messageNotEqual]],
620
            ],
621
            'target value: integer, value: integer with the same value, type: number, operator: >' => [
622
                100,
623
                [new Compare(100, operator: '>')],
624
                ['' => [$messageGreaterThan]],
625
            ],
626
            'target value: integer, value: lower integer, type: number, operator: >' => [
627
                99,
628
                [new Compare(100, operator: '>')],
629
                ['' => [$messageGreaterThan]],
630
            ],
631
            'target value: integer, value: lower integer, type: number, operator: >=' => [
632
                99,
633
                [new Compare(100, operator: '>=')],
634
                ['' => [$messageGreaterOrEqualThan]],
635
            ],
636
            'target value: integer, value: integer with the same value, type: number, operator: <' => [
637
                100,
638
                [new Compare(100, operator: '<')],
639
                ['' => [$messageLessThan]],
640
            ],
641
            'target value: integer, value: greater integer, type: number, operator: <' => [
642
                101,
643
                [new Compare(100, operator: '<')],
644
                ['' => [$messageLessThan]],
645
            ],
646
            'target value: integer, value: greater integer, type: number, operator: <=' => [
647
                101,
648
                [new Compare(100, operator: '<=')],
649
                ['' => [$messageLessOrEqualThan]],
650
            ],
651
652
            // Different types for strict equality
653
654
            'target value: empty string, value: null, type: number, operator: ===' => [
655
                null,
656
                [new Compare('', operator: '===')],
657
                ['' => ['Value must be equal to "".']],
658
            ],
659
            'target value: integer, value: string integer with the same value, type: number, operator: ===' => [
660
                '100',
661
                [new Compare(100, operator: '===')],
662
                ['' => [$messageEqual]],
663
            ],
664
            'target value: integer, value: float with the same value, but extra decimal place (0), type: number, operator: ===' => [
665
                100.0,
666
                [new Compare(100, operator: '===')],
667
                ['' => [$messageEqual]],
668
            ],
669
670
            // Different types for non-strict inequality
671
672
            'target value: integer, value: string integer with the same value, type: number, operator: !=' => [
673
                '100',
674
                [new Compare(100, operator: '!=')],
675
                ['' => [$messageNotEqual]],
676
            ],
677
            'target value: integer, value: float with the same value, but extra decimal place (0), type: number, operator: !=' => [
678
                100.0,
679
                [new Compare(100, operator: '!=')],
680
                ['' => [$messageNotEqual]],
681
            ],
682
683
            // Target attribute
684
685
            'target attribute: array key, target attribute value: string integer, attribute value: integer with the same value, type: number, operator: ===' => [
686
                ['attribute' => '100', 'number' => 100],
687
                ['number' => new Compare(null, 'attribute', operator: '===')],
688
                ['number' => ['Value must be equal to "attribute".']],
689
            ],
690
            'target attribute: array key, target attribute value: integer, attribute value: greater integer, type: number, operator: <=' => [
691
                ['attribute' => 100, 'number' => 101],
692
                ['number' => new Compare(null, 'attribute', operator: '<=')],
693
                ['number' => ['Value must be less than or equal to "attribute".']],
694
            ],
695
        ];
696
697
        return $this->extendDataWithDifferentTypes($initialData);
698
    }
699
700
    /**
701
     * @dataProvider dataValidationFailed
702
     * @dataProvider dataValidationFailedWithDifferentTypes
703
     */
704
    public function testValidationFailed(
705
        mixed $data,
706
        array|RuleInterface|null $rules,
707
        array $errorMessagesIndexedByPath,
708
    ): void {
709
        parent::testValidationFailed($data, $rules, $errorMessagesIndexedByPath);
710
    }
711
712
    private function extendDataWithDifferentTypes(array $initialData): array
713
    {
714
        $dynamicData = [];
715
        $mainType = CompareType::NUMBER;
716
        $remainingTypes = [CompareType::ORIGINAL, CompareType::STRING];
717
        foreach ($remainingTypes as $type) {
718
            foreach ($initialData as $key => $item) {
719
                $rules = [];
720
                foreach ($item[1] as $attribute => $rule) {
721
                    if (!$rule instanceof Compare) {
722
                        throw new RuntimeException('Wrong format for rule.');
723
                    }
724
725
                    $rules[$attribute] = new Compare(
726
                        targetValue: $rule->getTargetValue(),
727
                        targetAttribute: $rule->getTargetAttribute(),
728
                        type: $type,
729
                        operator: $rule->getOperator(),
730
                    );
731
                }
732
733
                if (!is_string($key)) {
734
                    throw new RuntimeException('Data set must have a string name.');
735
                }
736
737
                $newKey = str_replace(", type: $mainType,", ", type: $type,", $key);
738
                if ($key === $newKey) {
739
                    throw new RuntimeException('Wrong format for type.');
740
                }
741
742
                $itemData = [$item[0], $rules];
743
                if (isset($item[2])) {
744
                    $itemData[] = $item[2];
745
                }
746
747
                $dynamicData[$newKey] = $itemData;
748
            }
749
        }
750
751
        return array_merge($initialData, $dynamicData);
752
    }
753
754
    public function testSkipOnError(): void
755
    {
756
        $this->testSkipOnErrorInternal(new Compare(), new Compare(skipOnError: true));
757
    }
758
759
    public function testWhen(): void
760
    {
761
        $when = static fn (mixed $value): bool => $value !== null;
762
        $this->testWhenInternal(new Compare(), new Compare(when: $when));
763
    }
764
}
765