Passed
Pull Request — master (#521)
by Alexander
02:52
created

CompareTest.php$10 ➔ dataValidationFailed()   B

Complexity

Conditions 1

Size

Total Lines 273

Duplication

Lines 0
Ratio 0 %

Importance

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