Passed
Pull Request — master (#521)
by
unknown
02:45
created

CompareTest.php$0 ➔ __toString()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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