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

CompareTest.php$3 ➔ __toString()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 2 Features 0
Metric Value
cc 1
c 3
b 2
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 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 strictly 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 strictly 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 strictly 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 strictly 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
        $messageStrictlyEqual = 'Value must be strictly equal to "100".';
584
        $messageNotEqual = 'Value must not be equal to "100".';
585
        $messageNotStrictlyEqual = 'Value must not be strictly equal to "100".';
586
        $messageGreaterThan = 'Value must be greater than "100".';
587
        $messageGreaterOrEqualThan = 'Value must be greater than or equal to "100".';
588
        $messageLessThan = 'Value must be less than "100".';
589
        $messageLessOrEqualThan = 'Value must be less than or equal to "100".';
590
        $initialData = [
591
            // Basic
592
593
            'target value: integer, value: lower integer, type: number, operator: ==' => [
594
                99,
595
                [new Compare(100)],
596
                ['' => [$messageEqual]],
597
            ],
598
            'target value: integer, value: greater integer, type: number, operator: ==' => [
599
                101,
600
                [new Compare(100)],
601
                ['' => [$messageEqual]],
602
            ],
603
            'target value: integer, value: lower integer, type: number, operator: ===' => [
604
                99,
605
                [new Compare(100, operator: '===')],
606
                ['' => [$messageStrictlyEqual]],
607
            ],
608
            'target value: integer, value: greater integer, type: number, operator: ===' => [
609
                101,
610
                [new Compare(100, operator: '===')],
611
                ['' => [$messageStrictlyEqual]],
612
            ],
613
            'target value: integer, value: integer with the same value, type: number, operator: !=' => [
614
                100,
615
                [new Compare(100, operator: '!=')],
616
                ['' => [$messageNotEqual]],
617
            ],
618
            'target value: integer, value: integer with the same value, type: number, operator: !==' => [
619
                100,
620
                [new Compare(100, operator: '!==')],
621
                ['' => [$messageNotStrictlyEqual]],
622
            ],
623
            'target value: integer, value: integer with the same value, type: number, operator: >' => [
624
                100,
625
                [new Compare(100, operator: '>')],
626
                ['' => [$messageGreaterThan]],
627
            ],
628
            'target value: integer, value: lower integer, type: number, operator: >' => [
629
                99,
630
                [new Compare(100, operator: '>')],
631
                ['' => [$messageGreaterThan]],
632
            ],
633
            'target value: integer, value: lower integer, type: number, operator: >=' => [
634
                99,
635
                [new Compare(100, operator: '>=')],
636
                ['' => [$messageGreaterOrEqualThan]],
637
            ],
638
            'target value: integer, value: integer with the same value, type: number, operator: <' => [
639
                100,
640
                [new Compare(100, operator: '<')],
641
                ['' => [$messageLessThan]],
642
            ],
643
            'target value: integer, value: greater integer, type: number, operator: <' => [
644
                101,
645
                [new Compare(100, operator: '<')],
646
                ['' => [$messageLessThan]],
647
            ],
648
            'target value: integer, value: greater integer, type: number, operator: <=' => [
649
                101,
650
                [new Compare(100, operator: '<=')],
651
                ['' => [$messageLessOrEqualThan]],
652
            ],
653
654
            // Different types for strict equality
655
656
            'target value: empty string, value: null, type: number, operator: ===' => [
657
                null,
658
                [new Compare('', operator: '===')],
659
                ['' => ['Value must be strictly equal to "".']],
660
            ],
661
            'target value: integer, value: string integer with the same value, type: number, operator: ===' => [
662
                '100',
663
                [new Compare(100, operator: '===')],
664
                ['' => [$messageStrictlyEqual]],
665
            ],
666
            'target value: integer, value: float with the same value, but extra decimal place (0), type: number, operator: ===' => [
667
                100.0,
668
                [new Compare(100, operator: '===')],
669
                ['' => [$messageStrictlyEqual]],
670
            ],
671
672
            // Different types for non-strict inequality
673
674
            'target value: integer, value: string integer with the same value, type: number, operator: !=' => [
675
                '100',
676
                [new Compare(100, operator: '!=')],
677
                ['' => [$messageNotEqual]],
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
                ['' => [$messageNotEqual]],
683
            ],
684
685
            // Target attribute
686
687
            'target attribute: array key, target attribute value: string integer, attribute value: integer with the same value, type: number, operator: ===' => [
688
                ['attribute' => '100', 'number' => 100],
689
                ['number' => new Compare(targetAttribute: 'attribute', operator: '===')],
690
                ['number' => ['Value must be strictly equal to "attribute".']],
691
            ],
692
            'target attribute: array key, target attribute value: integer, attribute value: greater integer, type: number, operator: <=' => [
693
                ['attribute' => 100, 'number' => 101],
694
                ['number' => new Compare(targetAttribute: 'attribute', operator: '<=')],
695
                ['number' => ['Value must be less than or equal to "attribute".']],
696
            ],
697
        ];
698
699
        return $this->extendDataWithDifferentTypes($initialData);
700
    }
701
702
    /**
703
     * @dataProvider dataValidationFailed
704
     * @dataProvider dataValidationFailedWithDifferentTypes
705
     */
706
    public function testValidationFailed(
707
        mixed $data,
708
        array|RuleInterface|null $rules,
709
        array $errorMessagesIndexedByPath,
710
    ): void {
711
        parent::testValidationFailed($data, $rules, $errorMessagesIndexedByPath);
712
    }
713
714
    private function extendDataWithDifferentTypes(array $initialData): array
715
    {
716
        $dynamicData = [];
717
        $mainType = CompareType::NUMBER;
718
        $remainingTypes = [CompareType::ORIGINAL, CompareType::STRING];
719
        foreach ($remainingTypes as $type) {
720
            foreach ($initialData as $key => $item) {
721
                $rules = [];
722
                foreach ($item[1] as $attribute => $rule) {
723
                    if (!$rule instanceof Compare) {
724
                        throw new RuntimeException('Wrong format for rule.');
725
                    }
726
727
                    $rules[$attribute] = new Compare(
728
                        targetValue: $rule->getTargetValue(),
729
                        targetAttribute: $rule->getTargetAttribute(),
730
                        type: $type,
731
                        operator: $rule->getOperator(),
732
                    );
733
                }
734
735
                if (!is_string($key)) {
736
                    throw new RuntimeException('Data set must have a string name.');
737
                }
738
739
                $newKey = str_replace(", type: $mainType,", ", type: $type,", $key);
740
                if ($key === $newKey) {
741
                    throw new RuntimeException('Wrong format for type.');
742
                }
743
744
                $itemData = [$item[0], $rules];
745
                if (isset($item[2])) {
746
                    $itemData[] = $item[2];
747
                }
748
749
                $dynamicData[$newKey] = $itemData;
750
            }
751
        }
752
753
        return array_merge($initialData, $dynamicData);
754
    }
755
756
    public function testSkipOnError(): void
757
    {
758
        $this->testSkipOnErrorInternal(new Compare(), new Compare(skipOnError: true));
759
    }
760
761
    public function testWhen(): void
762
    {
763
        $when = static fn (mixed $value): bool => $value !== null;
764
        $this->testWhenInternal(new Compare(), new Compare(when: $when));
765
    }
766
}
767