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

CompareTest::dataValidationFailed()

Size

Total Lines 282
Code Lines 152

Duplication

Lines 0
Ratio 0 %

Importance

Changes 13
Bugs 5 Features 0
Metric Value
dl 0
loc 282
c 13
b 5
f 0
eloc 152
nc 1
nop 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A CompareTest.php$6 ➔ getData() 0 3 1
A CompareTest.php$8 ➔ __toString() 0 3 1
A CompareTest.php$6 ➔ hasAttribute() 0 3 1
A CompareTest.php$6 ➔ getAttributeValue() 0 3 1
A CompareTest.php$6 ➔ getSource() 0 3 1
A CompareTest.php$10 ➔ __toString() 0 3 1
A CompareTest.php$9 ➔ __toString() 0 3 1
A CompareTest.php$7 ➔ __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;
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 or \DateTimeInterface.',
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 or \DateTimeInterface.',
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
            // Number / string specific, DateTime object and Unix Timestamp
267
268
            'target value: Unix Timestamp string, value: DateTime object with the same value, type: number, operator: ==' => [
269
                $dateTime->format('U'),
270
                [new Compare($dateTime)],
271
            ],
272
            'target value: Unix Timestamp string, value: DateTime object with the same value, type: string, operator: ==' => [
273
                $dateTime->format('U'),
274
                [new Compare($dateTime, type: CompareType::STRING)],
275
            ],
276
277
            // Original specific, objects
278
279
            'target value: object, value: similar object in a different instance, type: original, operator: ==' => [
280
                new stdClass(),
281
                [new Compare(new stdClass(), type: CompareType::ORIGINAL)],
282
            ],
283
            'target value: object, value: the same object, type: original, operator: ===' => [
284
                $object,
285
                [new Compare($object, type: CompareType::ORIGINAL, operator: '===')],
286
            ],
287
            'target value: object, value: similar object but with different property type, type: original, operator: ===' => [
288
                $objectWithDifferentPropertyType,
289
                [new Compare($object, type: CompareType::ORIGINAL)],
290
            ],
291
292
            // Original specific, arrays
293
294
            'target value: array, value: similar array declared separately, type: original, operator: ==' => [
295
                [1, 2],
296
                [new Compare([1, 2], type: CompareType::ORIGINAL)],
297
            ],
298
            'target value: array, value: similar array declared separately, type: original, operator: ===' => [
299
                [1, 2],
300
                [new Compare([1, 2], type: CompareType::ORIGINAL, operator: '===')],
301
            ],
302
            'target value: array, value: similar array but with different item type, type: original, operator: ==' => [
303
                [1, 2],
304
                [new Compare([1, '2'], type: CompareType::ORIGINAL)],
305
            ],
306
            'target value: array, value: the same array, type: original, operator: ===' => [
307
                $array,
308
                [new Compare($array, type: CompareType::ORIGINAL)],
309
            ],
310
        ];
311
    }
312
313
    public function dataValidationPassedWithDifferentTypes(): array
314
    {
315
        $customDataSet = new class () implements DataSetInterface {
316
            public function getAttributeValue(string $attribute): mixed
317
            {
318
                return 100;
319
            }
320
321
            public function getData(): ?array
322
            {
323
                return null;
324
            }
325
326
            public function hasAttribute(string $attribute): bool
327
            {
328
                return true;
329
            }
330
        };
331
        $subFloatFromInt = static fn(int $value1, float $value2): int => $value1 - (int) $value2;
332
        $initialData = [
333
            // Basic
334
335
            'target value: integer, value: integer with the same value, type: number, operator: ==' => [
336
                100,
337
                [new Compare(100)],
338
            ],
339
            'target value: integer, value: integer with the same value, type: number, operator: ===' => [
340
                100,
341
                [new Compare(100, operator: '===')],
342
            ],
343
            'target value: integer, value: lower integer, type: number, operator: !=' => [
344
                99,
345
                [new Compare(100, operator: '!=')],
346
            ],
347
            'target value: integer, value: greater integer, type: number, operator: !=' => [
348
                101,
349
                [new Compare(100, operator: '!=')],
350
            ],
351
            'target value: integer, value: lower integer, type: number, operator: !==' => [
352
                101,
353
                [new Compare(100, operator: '!==')],
354
            ],
355
            'target value: integer, value: greater integer, type: number, operator: !==' => [
356
                101,
357
                [new Compare(100, operator: '!==')],
358
            ],
359
            'target value: integer, value: greater integer, type: number, operator: >' => [
360
                101,
361
                [new Compare(100, operator: '>')],
362
            ],
363
            'target value: integer, value: integer with the same value, type: number, operator: >=' => [
364
                100,
365
                [new Compare(100, operator: '>=')],
366
            ],
367
            'target value: integer, value: greater integer, type: number, operator: >=' => [
368
                101,
369
                [new Compare(100, operator: '>=')],
370
            ],
371
            'target value: integer, value: lower integer, type: number, operator: <' => [
372
                99,
373
                [new Compare(100, operator: '<')],
374
            ],
375
            'target value: integer, value: integer with the same value, type: number, operator: <=' => [
376
                100,
377
                [new Compare(100, operator: '<=')],
378
            ],
379
            'target value: integer, value: lower integer, type: number, operator: <=' => [
380
                99,
381
                [new Compare(100, operator: '<=')],
382
            ],
383
384
            // Boolean
385
386
            'target value: boolean (false), value: boolean (true), type: number, operator: >=' => [
387
                true,
388
                [new Compare(false, operator: '>=')],
389
            ],
390
391
            // Different types for non-strict equality
392
393
            'target value: empty string, value: null, type: number, operator: ==' => [
394
                null,
395
                [new Compare('')],
396
            ],
397
            'target value: integer, value: string integer with the same value, type: number, operator: ==' => [
398
                '100',
399
                [new Compare(100)],
400
            ],
401
402
            // Different types for non-strict inequality
403
404
            'target value: integer, value: float, type: number, operator: !=' => [
405
                100.00001,
406
                [new Compare(100, operator: '!=')],
407
            ],
408
            'target value: integer, value: boolean, type: number, operator: !=' => [
409
                false,
410
                [new Compare(100, operator: '!=')],
411
            ],
412
413
            // Different types for strict inequality
414
415
            'target value: integer, value: boolean, type: number, operator: !==' => [
416
                false,
417
                [new Compare(100, operator: '!==')],
418
            ],
419
            'target value: integer, value: string integer with the same value, type: number, operator: !==' => [
420
                '100',
421
                [new Compare(100, operator: '!==')],
422
            ],
423
            'target value: integer, value: float with the same value, but extra decimal place (0), type: number, operator: !==' => [
424
                100.0,
425
                [new Compare(100, operator: '!==')],
426
            ],
427
428
            // Large integers
429
430
            'target value: string with large integer, value: string with the same integer, type: number, operator: ===' => [
431
                PHP_INT_MAX . '0',
432
                [new Compare(PHP_INT_MAX . '0', operator: '===')],
433
            ],
434
            'target value: string with large integer, value: string with greater integer, type: number, operator: >' => [
435
                PHP_INT_MAX . '0',
436
                [new Compare('-' . PHP_INT_MAX . '12', operator: '>')],
437
            ],
438
            'target value: large integer in scientific notation, value: greater integer, type: number, operator: ===' => [
439
                4.5e19,
440
                [new Compare(4.5e19, operator: '===')],
441
            ],
442
            'target value: large integer in scientific notation, value: greater integer, type: number, operator: >' => [
443
                4.5e20,
444
                [new Compare(-4.5e19, operator: '>')],
445
            ],
446
            'target value: integer, value: the same integer as expression result, type: number, operator: ===' => [
447
                $subFloatFromInt(1_234_567_890, 1_234_567_890),
0 ignored issues
show
Bug introduced by
The constant Yiisoft\Validator\Tests\Rule\1_234_567_890 was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
448
                [new Compare(0, operator: '===')],
449
            ],
450
451
            // Target attribute
452
453
            'target attribute: array key, target attribute value: integer, attribute value: integer with the same value, type: number, operator: ==' => [
454
                ['attribute' => 100, 'number' => 100],
455
                ['number' => new Compare(targetAttribute: 'attribute')],
456
            ],
457
            'target attribute: array key, target attribute value: integer, attribute value: lower integer, type: number, operator: <=' => [
458
                ['attribute' => 100, 'number' => 99],
459
                ['number' => new Compare(targetAttribute: 'attribute', operator: '<=')],
460
            ],
461
            'target attribute: object property, target attribute value: integer, attribute value: integer with the same value, type: number, operator: ==' => [
462
                new class () {
463
                    public int $attribute = 100;
464
                    public int $number = 100;
465
                },
466
                ['number' => new Compare(targetAttribute: 'attribute', operator: '<=')],
467
            ],
468
            'target attribute: custom data set attribute, target attribute value: integer, attribute value: integer with the same value, type: number, operator: ==' => [
469
                $customDataSet,
470
                ['number' => new Compare(targetAttribute: 'attribute', operator: '<=')],
471
            ],
472
        ];
473
474
        return $this->extendDataWithDifferentTypes($initialData);
475
    }
476
477
    /**
478
     * @dataProvider dataValidationPassed
479
     * @dataProvider dataValidationPassedWithDifferentTypes
480
     */
481
    public function testValidationPassed(mixed $data, ?array $rules = null): void
482
    {
483
        parent::testValidationPassed($data, $rules);
484
    }
485
486
    public function dataValidationFailed(): array
487
    {
488
        $incorrectDataSet = new class () implements DataWrapperInterface {
489
            public function getAttributeValue(string $attribute): mixed
490
            {
491
                return new stdClass();
492
            }
493
494
            public function getData(): ?array
495
            {
496
                return null;
497
            }
498
499
            public function getSource(): mixed
500
            {
501
                return false;
502
            }
503
504
            public function hasAttribute(string $attribute): bool
505
            {
506
                return false;
507
            }
508
        };
509
        $targetStringableFloat = new class () implements Stringable {
510
            public function __toString(): string
511
            {
512
                return '100.5';
513
            }
514
        };
515
        $stringableFloat = new class () implements Stringable {
516
            public function __toString(): string
517
            {
518
                return '100.50';
519
            }
520
        };
521
        $targetStringableUuid = new class () implements Stringable {
522
            public function __toString(): string
523
            {
524
                return '3b98a689-7d49-48bb-8741-7e27f220b69a';
525
            }
526
        };
527
        $stringableUuid = new class () implements Stringable {
528
            public function __toString(): string
529
            {
530
                return 'd62f2b3f-707f-451a-8819-046ff8436a4f';
531
            }
532
        };
533
        $dateTime = new DateTime('2023-02-07 12:57:12');
534
        $object = new CompareObject(a: 1, b: 2);
535
        $objectWithDifferentPropertyValue = new CompareObject(a: 1, b: 3);
536
        $objectWithDifferentPropertyType = new CompareObject(a: 1, b: '2');
537
        $array = [1, 2];
538
        $reversedArray = [2, 1];
539
540
        return [
541
            // Incorrect input
542
543
            'incorrect input' => [
544
                [],
545
                [new Compare(false)],
546
                [
547
                    '' => [
548
                        'The allowed types are integer, float, string, boolean, null and object implementing ' .
549
                        '\Stringable interface or \DateTimeInterface.',
550
                    ],
551
                ],
552
            ],
553
            'custom incorrect input message' => [
554
                [],
555
                [new Compare(false, incorrectInputMessage: 'Custom incorrect input message.')],
556
                ['' => ['Custom incorrect input message.']],
557
            ],
558
            'custom incorrect input message with parameters' => [
559
                [],
560
                [new Compare(false, incorrectInputMessage: 'Attribute - {attribute}, type - {type}.')],
561
                ['' => ['Attribute - , type - array.']],
562
            ],
563
            'custom incorrect input message with parameters, attribute set' => [
564
                ['data' => []],
565
                ['data' => new Compare(false, incorrectInputMessage: 'Attribute - {attribute}, type - {type}.')],
566
                ['data' => ['Attribute - data, type - array.']],
567
            ],
568
569
            // Incorrect data set input
570
571
            'incorrect data set type' => [
572
                $incorrectDataSet,
573
                [new Compare(targetAttribute: 'test')],
574
                [
575
                    '' => [
576
                        'The attribute value returned from a custom data set must have one of the following types: ' .
577
                            'integer, float, string, boolean, null or an object implementing \Stringable interface ' .
578
                            'or \DateTimeInterface.',
579
                    ],
580
                ],
581
            ],
582
            'custom incorrect data set type message' => [
583
                $incorrectDataSet,
584
                [
585
                    new Compare(
586
                        targetAttribute: 'test',
587
                        incorrectDataSetTypeMessage: 'Custom incorrect data set type message.',
588
                    ),
589
                ],
590
                ['' => ['Custom incorrect data set type message.']],
591
            ],
592
            'custom incorrect data set type message with parameters' => [
593
                $incorrectDataSet,
594
                [
595
                    new Compare(
596
                        targetAttribute: 'test',
597
                        incorrectDataSetTypeMessage: 'Type - {type}.',
598
                    ),
599
                ],
600
                ['' => ['Type - stdClass.']],
601
            ],
602
603
            // Custom message
604
605
            'custom message' => [101, [new Compare(100, message: 'Custom message.')], ['' => ['Custom message.']]],
606
            'custom message with parameters, target value set' => [
607
                101,
608
                [
609
                    new Compare(
610
                        100,
611
                        message: 'Attribute - {attribute}, target value - {targetValue}, target attribute - ' .
612
                        '{targetAttribute}, target value or attribute - {targetValueOrAttribute}, value - {value}.',
613
                    ),
614
                ],
615
                [
616
                    '' => [
617
                        'Attribute - , target value - 100, target attribute - , target value or attribute - 100, ' .
618
                        'value - 101.',
619
                    ],
620
                ],
621
            ],
622
            'custom message with parameters, attribute and target attribute set' => [
623
                ['attribute' => 100, 'number' => 101],
624
                [
625
                    'number' => new Compare(
626
                        targetAttribute: 'attribute',
627
                        message: 'Attribute - {attribute}, target value - {targetValue}, target attribute - ' .
628
                        '{targetAttribute}, target attribute value - {targetAttributeValue}, target value or ' .
629
                        'attribute - {targetValueOrAttribute}, value - {value}.',
630
                        operator: '===',
631
                    ),
632
                ],
633
                [
634
                    'number' => [
635
                        'Attribute - number, target value - , target attribute - attribute, target attribute value ' .
636
                        '- 100, target value or attribute - attribute, value - 101.',
637
                    ],
638
                ],
639
            ],
640
641
            // String / original specific, falsy values
642
643
            'target value: integer (0), value: null, type: string, operator: ==' => [
644
                null,
645
                [new Compare(0, type: CompareType::STRING)],
646
                ['' => ['Value must be equal to "0".']],
647
            ],
648
649
            // Number / string specific, expressions
650
651
            'target value: float, value: float with the same value as expression result, type: original, operator: ==' => [
652
                1 - 0.83,
653
                [new Compare(0.17, type: CompareType::ORIGINAL)],
654
                ['' => ['Value must be equal to "0.17".']],
655
            ],
656
657
            // Number / original specific, decimal places, directly provided values
658
659
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: string, operator: ==' => [
660
                '100.50', [new Compare('100.5', type: CompareType::STRING)], ['' => ['Value must be equal to "100.5".']],
661
            ],
662
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: string, operator: ===' => [
663
                '100.50', [new Compare('100.5', type: CompareType::STRING, operator: '===')], ['' => ['Value must be strictly equal to "100.5".']],
664
            ],
665
            'target value: string float, value: string float with the same value, but extra decimal place (0), type: original, operator: ===' => [
666
                '100.50', [new Compare('100.5', type: CompareType::ORIGINAL, operator: '===')], ['' => ['Value must be strictly equal to "100.5".']],
667
            ],
668
669
            // Number / original specific, decimal places, values provided via stringable objects
670
671
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: string, operator: ==' => [
672
                $stringableFloat,
673
                [new Compare($targetStringableFloat, type: CompareType::STRING)],
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: string, operator: ===' => [
677
                $stringableFloat,
678
                [new Compare($targetStringableFloat, type: CompareType::STRING, operator: '===')],
679
                ['' => ['Value must be strictly equal to "100.5".']],
680
            ],
681
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: original, operator: ==' => [
682
                $stringableFloat,
683
                [new Compare($targetStringableFloat, type: CompareType::ORIGINAL)],
684
                ['' => ['Value must be equal to "100.5".']],
685
            ],
686
            'target value: stringable float, value: stringable float with the same value, but extra decimal place (0), type: original, operator: ===' => [
687
                $stringableFloat,
688
                [new Compare($targetStringableFloat, type: CompareType::ORIGINAL, operator: '===')],
689
                ['' => ['Value must be strictly equal to "100.5".']],
690
            ],
691
692
            // String / original specific, character order, directly provided values
693
694
            'target value: character, value: character located further within alphabet, type: number, operator: >' => [
695
                'b',
696
                [new Compare('a', type: CompareType::NUMBER, operator: '>')],
697
                ['' => ['Value must be greater than "a".']],
698
            ],
699
700
            // String specific, character order, values provided via stringable objects
701
702
            'target value: stringable uuidv4, value: greater stringable uuidv4, type: number, operator: >' => [
703
                $stringableUuid,
704
                [new Compare($targetStringableUuid, type: CompareType::NUMBER, operator: '>')],
705
                ['' => ['Value must be greater than "3b98a689-7d49-48bb-8741-7e27f220b69a".']],
706
            ],
707
            'target value: stringable uuidv4, value: greater stringable uuidv4, type: original, operator: >' => [
708
                $stringableUuid,
709
                [new Compare($targetStringableUuid, type: CompareType::ORIGINAL, operator: '>')],
710
                ['' => ['Value must be greater than "3b98a689-7d49-48bb-8741-7e27f220b69a".']],
711
            ],
712
713
            // Original specific, datetime
714
715
            'target value: human-readable DateTime string, value: greater DateTime string, type: string, operator: >' => [
716
                '2022-06-03',
717
                [new Compare('June 2nd, 2022', type: CompareType::STRING, operator: '>')],
718
                ['' => ['Value must be greater than "June 2nd, 2022".']],
719
            ],
720
721
            // Number / string specific, DateTime object and Unix Timestamp
722
723
            'target value: Unix Timestamp string, value: DateTime object with the same value, type: original, operator: ==' => [
724
                $dateTime->format('U'),
725
                [new Compare($dateTime, type: CompareType::ORIGINAL)],
726
                ['' => ['Value must be equal to "1675774632".']],
727
            ],
728
729
            // Original specific, objects
730
731
            'target value: object, value: similar object in a different instance, type: original, operator: ===' => [
732
                new stdClass(),
733
                [new Compare(new stdClass(), type: CompareType::ORIGINAL, operator: '===')],
734
                ['' => ['Value must be strictly equal to "stdClass".']],
735
            ],
736
            'target value: object, value: similar object with different property value, type: original, operator: ==' => [
737
                $objectWithDifferentPropertyValue,
738
                [new Compare($object, type: CompareType::ORIGINAL)],
739
                ['' => [sprintf('Value must be equal to "%s".', CompareObject::class)]],
740
            ],
741
            'target value: object, value: similar object with different property value, type: original, operator: ===' => [
742
                $objectWithDifferentPropertyValue,
743
                [new Compare($object, type: CompareType::ORIGINAL, operator: '===')],
744
                ['' => [sprintf('Value must be strictly equal to "%s".', CompareObject::class)]],
745
            ],
746
            'target value: object, value: similar object but with different property type, type: original, operator: ===' => [
747
                $objectWithDifferentPropertyType,
748
                [new Compare($object, type: CompareType::ORIGINAL, operator: '===')],
749
                ['' => [sprintf('Value must be strictly equal to "%s".', CompareObject::class)]],
750
            ],
751
752
            // Original specific, arrays
753
754
            'target value: array, value: similar array but with different item type, type: original, operator: ===' => [
755
                [1, 2],
756
                [new Compare([1, '2'], type: CompareType::ORIGINAL, operator: '===')],
757
                ['' => ['Value must be strictly equal to "array".']],
758
            ],
759
            'target value: array, value: similar array but with different items order, type: original, operator: ==' => [
760
                $reversedArray,
761
                [new Compare($array, type: CompareType::ORIGINAL)],
762
                ['' => ['Value must be equal to "array".']],
763
            ],
764
            'target value: array, value: similar array but reversed, type: original, operator: ===' => [
765
                $reversedArray,
766
                [new Compare($array, type: CompareType::ORIGINAL, operator: '===')],
767
                ['' => ['Value must be strictly equal to "array".']],
768
            ],
769
        ];
770
    }
771
772
    public function dataValidationFailedWithDifferentTypes(): array
773
    {
774
        $messageEqual = 'Value must be equal to "100".';
775
        $messageStrictlyEqual = 'Value must be strictly equal to "100".';
776
        $messageNotEqual = 'Value must not be equal to "100".';
777
        $messageNotStrictlyEqual = 'Value must not be strictly equal to "100".';
778
        $messageGreaterThan = 'Value must be greater than "100".';
779
        $messageGreaterOrEqualThan = 'Value must be greater than or equal to "100".';
780
        $messageLessThan = 'Value must be less than "100".';
781
        $messageLessOrEqualThan = 'Value must be less than or equal to "100".';
782
        $initialData = [
783
            // Basic
784
785
            'target value: integer, value: lower integer, type: number, operator: ==' => [
786
                99,
787
                [new Compare(100)],
788
                ['' => [$messageEqual]],
789
            ],
790
            'target value: integer, value: greater integer, type: number, operator: ==' => [
791
                101,
792
                [new Compare(100)],
793
                ['' => [$messageEqual]],
794
            ],
795
            'target value: integer, value: lower integer, type: number, operator: ===' => [
796
                99,
797
                [new Compare(100, operator: '===')],
798
                ['' => [$messageStrictlyEqual]],
799
            ],
800
            'target value: integer, value: greater integer, type: number, operator: ===' => [
801
                101,
802
                [new Compare(100, operator: '===')],
803
                ['' => [$messageStrictlyEqual]],
804
            ],
805
            'target value: integer, value: integer with the same value, type: number, operator: !=' => [
806
                100,
807
                [new Compare(100, operator: '!=')],
808
                ['' => [$messageNotEqual]],
809
            ],
810
            'target value: integer, value: integer with the same value, type: number, operator: !==' => [
811
                100,
812
                [new Compare(100, operator: '!==')],
813
                ['' => [$messageNotStrictlyEqual]],
814
            ],
815
            'target value: integer, value: integer with the same value, type: number, operator: >' => [
816
                100,
817
                [new Compare(100, operator: '>')],
818
                ['' => [$messageGreaterThan]],
819
            ],
820
            'target value: integer, value: lower integer, type: number, operator: >' => [
821
                99,
822
                [new Compare(100, operator: '>')],
823
                ['' => [$messageGreaterThan]],
824
            ],
825
            'target value: integer, value: lower integer, type: number, operator: >=' => [
826
                99,
827
                [new Compare(100, operator: '>=')],
828
                ['' => [$messageGreaterOrEqualThan]],
829
            ],
830
            'target value: integer, value: integer with the same value, type: number, operator: <' => [
831
                100,
832
                [new Compare(100, operator: '<')],
833
                ['' => [$messageLessThan]],
834
            ],
835
            'target value: integer, value: greater integer, type: number, operator: <' => [
836
                101,
837
                [new Compare(100, operator: '<')],
838
                ['' => [$messageLessThan]],
839
            ],
840
            'target value: integer, value: greater integer, type: number, operator: <=' => [
841
                101,
842
                [new Compare(100, operator: '<=')],
843
                ['' => [$messageLessOrEqualThan]],
844
            ],
845
846
            // Different types for strict equality
847
848
            'target value: empty string, value: null, type: number, operator: ===' => [
849
                null,
850
                [new Compare('', operator: '===')],
851
                ['' => ['Value must be strictly equal to "".']],
852
            ],
853
            'target value: integer, value: string integer with the same value, type: number, operator: ===' => [
854
                '100',
855
                [new Compare(100, operator: '===')],
856
                ['' => [$messageStrictlyEqual]],
857
            ],
858
            'target value: integer, value: float with the same value, but extra decimal place (0), type: number, operator: ===' => [
859
                100.0,
860
                [new Compare(100, operator: '===')],
861
                ['' => [$messageStrictlyEqual]],
862
            ],
863
864
            // Different types for non-strict inequality
865
866
            'target value: integer, value: string integer with the same value, type: number, operator: !=' => [
867
                '100',
868
                [new Compare(100, operator: '!=')],
869
                ['' => [$messageNotEqual]],
870
            ],
871
            'target value: integer, value: float with the same value, but extra decimal place (0), type: number, operator: !=' => [
872
                100.0,
873
                [new Compare(100, operator: '!=')],
874
                ['' => [$messageNotEqual]],
875
            ],
876
877
            // Target attribute
878
879
            'target attribute: array key, target attribute value: string integer, attribute value: integer with the same value, type: number, operator: ===' => [
880
                ['attribute' => '100', 'number' => 100],
881
                ['number' => new Compare(targetAttribute: 'attribute', operator: '===')],
882
                ['number' => ['Value must be strictly equal to "attribute".']],
883
            ],
884
            'target attribute: array key, target attribute value: integer, attribute value: greater integer, type: number, operator: <=' => [
885
                ['attribute' => 100, 'number' => 101],
886
                ['number' => new Compare(targetAttribute: 'attribute', operator: '<=')],
887
                ['number' => ['Value must be less than or equal to "attribute".']],
888
            ],
889
        ];
890
891
        return $this->extendDataWithDifferentTypes($initialData);
892
    }
893
894
    /**
895
     * @dataProvider dataValidationFailed
896
     * @dataProvider dataValidationFailedWithDifferentTypes
897
     */
898
    public function testValidationFailed(
899
        mixed $data,
900
        array|RuleInterface|null $rules,
901
        array $errorMessagesIndexedByPath,
902
    ): void {
903
        parent::testValidationFailed($data, $rules, $errorMessagesIndexedByPath);
904
    }
905
906
    private function extendDataWithDifferentTypes(array $initialData): array
907
    {
908
        $dynamicData = [];
909
        $mainType = CompareType::NUMBER;
910
        $remainingTypes = [CompareType::ORIGINAL, CompareType::STRING];
911
        foreach ($remainingTypes as $type) {
912
            foreach ($initialData as $key => $item) {
913
                $rules = [];
914
                foreach ($item[1] as $attribute => $rule) {
915
                    if (!$rule instanceof Compare) {
916
                        throw new RuntimeException('Wrong format for rule.');
917
                    }
918
919
                    $rules[$attribute] = new Compare(
920
                        targetValue: $rule->getTargetValue(),
921
                        targetAttribute: $rule->getTargetAttribute(),
922
                        type: $type,
923
                        operator: $rule->getOperator(),
924
                    );
925
                }
926
927
                if (!is_string($key)) {
928
                    throw new RuntimeException('Data set must have a string name.');
929
                }
930
931
                $newKey = str_replace(", type: $mainType,", ", type: $type,", $key);
932
                if ($key === $newKey) {
933
                    throw new RuntimeException('Wrong format for type.');
934
                }
935
936
                $itemData = [$item[0], $rules];
937
                if (isset($item[2])) {
938
                    $itemData[] = $item[2];
939
                }
940
941
                $dynamicData[$newKey] = $itemData;
942
            }
943
        }
944
945
        return array_merge($initialData, $dynamicData);
946
    }
947
948
    public function testSkipOnError(): void
949
    {
950
        $this->testSkipOnErrorInternal(new Compare(), new Compare(skipOnError: true));
951
    }
952
953
    public function testWhen(): void
954
    {
955
        $when = static fn (mixed $value): bool => $value !== null;
956
        $this->testWhenInternal(new Compare(), new Compare(when: $when));
957
    }
958
}
959