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

CompareTest.php$3 ➔ dataValidationPassed()   B

Complexity

Conditions 1

Size

Total Lines 158

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 158
rs 8
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A CompareTest.php$3 ➔ __toString() 0 3 1
A CompareTest.php$2 ➔ __toString() 0 3 1
A CompareTest.php$1 ➔ __toString() 0 3 1
A CompareTest.php$0 ➔ __toString() 0 3 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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;
0 ignored issues
show
Coding Style introduced by
Header blocks must not contain blank lines
Loading history...
21
22
use Yiisoft\Validator\Tests\Support\Data\CompareObject;
0 ignored issues
show
Bug introduced by
The type Yiisoft\Validator\Tests\Support\Data\CompareObject was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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