GroupTest::generateValueForProperty()   D
last analyzed

Complexity

Conditions 25
Paths 29

Size

Total Lines 62
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 25
eloc 36
c 1
b 0
f 1
nc 29
nop 3
dl 0
loc 62
rs 4.1666

How to fix   Long Method    Complexity   

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 MaxBeckers\AmazonAlexa\Test\Response\Directives\APL\AVGItem;
6
7
use MaxBeckers\AmazonAlexa\Response\Directives\APL\AVGItem\AVGItem;
8
use MaxBeckers\AmazonAlexa\Response\Directives\APL\AVGItem\Group;
9
use MaxBeckers\AmazonAlexa\Response\Directives\APL\Document\AVGItemType;
10
use MaxBeckers\AmazonAlexa\Response\Directives\APL\AVGFilter\AVGFilter;
11
use PHPUnit\Framework\TestCase;
12
13
class GroupTest extends TestCase
14
{
15
    public function testConstructorWithAllParameters(): void
16
    {
17
        $clipPath = 'circle(50%)';
18
        $data = ['key1' => 'value1', 'key2' => 'value2'];
19
        $item = $this->createMock(AVGItem::class);
20
        $items = [
21
            $this->createMock(AVGItem::class),
22
            $this->createMock(AVGItem::class),
23
        ];
24
        $opacity = 0.8;
25
        $transform = 'translate(10, 20)';
26
27
        $group = new Group($clipPath, $data, $item, $items, $opacity, $transform);
28
29
        $this->assertSame($clipPath, $group->clipPath);
30
        $this->assertSame($data, $group->data);
31
        $this->assertSame($item, $group->item);
32
        $this->assertSame($items, $group->items);
33
        $this->assertSame($opacity, $group->opacity);
34
        $this->assertSame($transform, $group->transform);
35
    }
36
37
    public function testConstructorWithDefaultParameters(): void
38
    {
39
        $group = new Group();
40
41
        $this->assertNull($group->clipPath);
42
        $this->assertNull($group->data);
43
        $this->assertNull($group->item);
44
        $this->assertNull($group->items);
45
        $this->assertNull($group->opacity);
46
        $this->assertNull($group->transform);
47
    }
48
49
    public function testJsonSerializeWithAllProperties(): void
50
    {
51
        $clipPath = 'rect(10%, 20%, 30%, 40%)';
52
        $data = ['width' => 100, 'height' => 200];
53
        $item = $this->createMock(AVGItem::class);
54
        $items = [
55
            $this->createMock(AVGItem::class),
56
            $this->createMock(AVGItem::class),
57
        ];
58
        $opacity = 0.5;
59
        $transform = 'scale(2, 2)';
60
61
        $group = new Group($clipPath, $data, $item, $items, $opacity, $transform);
62
        $result = $group->jsonSerialize();
63
64
        $this->assertSame(AVGItemType::GROUP->value, $result['type']);
65
        $this->assertSame($clipPath, $result['clipPath']);
66
        $this->assertSame($data, $result['data']);
67
        $this->assertSame($item, $result['item']);
68
        $this->assertSame($items, $result['items']);
69
        $this->assertSame($opacity, $result['opacity']);
70
        $this->assertSame($transform, $result['transform']);
71
    }
72
73
    public function testJsonSerializeWithNullValues(): void
74
    {
75
        $group = new Group();
76
        $result = $group->jsonSerialize();
77
78
        $this->assertSame(AVGItemType::GROUP->value, $result['type']);
79
        $this->assertArrayNotHasKey('clipPath', $result);
80
        $this->assertArrayNotHasKey('data', $result);
81
        $this->assertArrayNotHasKey('item', $result);
82
        $this->assertArrayNotHasKey('items', $result);
83
        $this->assertArrayNotHasKey('opacity', $result);
84
        $this->assertArrayNotHasKey('transform', $result);
85
    }
86
87
    public function testJsonSerializeWithEmptyData(): void
88
    {
89
        $group = new Group(data: []);
90
        $result = $group->jsonSerialize();
91
92
        $this->assertArrayNotHasKey('data', $result);
93
    }
94
95
    public function testJsonSerializeWithEmptyItems(): void
96
    {
97
        $group = new Group(items: []);
98
        $result = $group->jsonSerialize();
99
100
        $this->assertArrayNotHasKey('items', $result);
101
    }
102
103
    public function testJsonSerializeWithPartialProperties(): void
104
    {
105
        $group = new Group(
106
            clipPath: 'polygon(0% 0%, 100% 0%, 50% 100%)',
107
            opacity: 1.0
108
        );
109
        $result = $group->jsonSerialize();
110
111
        $this->assertSame(AVGItemType::GROUP->value, $result['type']);
112
        $this->assertSame('polygon(0% 0%, 100% 0%, 50% 100%)', $result['clipPath']);
113
        $this->assertSame(1.0, $result['opacity']);
114
        $this->assertArrayNotHasKey('data', $result);
115
        $this->assertArrayNotHasKey('item', $result);
116
        $this->assertArrayNotHasKey('items', $result);
117
        $this->assertArrayNotHasKey('transform', $result);
118
    }
119
120
    public function testJsonSerializeWithZeroOpacity(): void
121
    {
122
        $group = new Group(opacity: 0.0);
123
        $result = $group->jsonSerialize();
124
125
        $this->assertArrayHasKey('opacity', $result);
126
        $this->assertSame(0.0, $result['opacity']);
127
    }
128
129
    public function testTypeConstant(): void
130
    {
131
        $this->assertSame(AVGItemType::GROUP, Group::TYPE);
132
    }
133
134
    public function testExtendsAVGItem(): void
135
    {
136
        $group = new Group();
137
138
        $this->assertInstanceOf(AVGItem::class, $group);
139
    }
140
141
    public function testImplementsJsonSerializable(): void
142
    {
143
        $group = new Group();
144
145
        $this->assertInstanceOf(\JsonSerializable::class, $group);
146
    }
147
148
    /**
149
     * Ensure every public property inherited from AVGItem (and present on Group) is serialized
150
     * when assigned a non-default non-empty value, using type-aware assignments to avoid
151
     * invalid TypeErrors (e.g. assigning string to ?array).
152
     */
153
    public function testJsonSerializeIncludesAllAVGItemBaseProperties(): void
154
    {
155
        $group = new Group();
156
157
        $reflection  = new \ReflectionObject($group);
158
        $properties  = $reflection->getProperties(\ReflectionProperty::IS_PUBLIC);
159
        $skip        = ['type']; // type always handled by base
160
161
        $assigned = [];
162
163
        foreach ($properties as $prop) {
164
            $name = $prop->getName();
165
            if (in_array($name, $skip, true)) {
166
                continue;
167
            }
168
169
            // Special handling for typed AVGItem property (object mock).
170
            if ($name === 'item') {
171
                $value = $this->createMock(AVGItem::class);
172
                $group->$name = $value;
173
                $assigned[$name] = $value;
174
                continue;
175
            }
176
177
            // Special handling for AVGFilter-typed property.
178
            if ($name === 'filter') {
179
                $value = $this->createMock(AVGFilter::class);
180
                $group->$name = $value;
181
                $assigned[$name] = $value;
182
                continue;
183
            }
184
185
            $current = $group->$name ?? null;
186
            $value   = $this->generateValueForProperty($prop, $current, $name);
187
188
            // If generator decides to skip (returns a sentinel null and property already null) we move on.
189
            if ($value !== null) {
190
                $group->$name    = $value;
191
                $assigned[$name] = $value;
192
            }
193
        }
194
195
        $json = $group->jsonSerialize();
196
        $this->assertSame(AVGItemType::GROUP->value, $json['type']);
197
198
        foreach ($assigned as $prop => $value) {
199
            // Skip default-like values that component intentionally filters out
200
            if ($value === null) {
201
                continue;
202
            }
203
            if (is_array($value) && $value === []) {
204
                continue;
205
            }
206
            if ((is_int($value) && $value === 0) || (is_string($value) && $value === '') || (is_float($value) && $value === 0.0)) {
207
                continue;
208
            }
209
210
            $this->assertArrayHasKey($prop, $json, "Expected property '$prop' to be serialized after assignment.");
211
            if (array_key_exists($prop, $json)) {
212
                $this->assertSame($value, $json[$prop], "Serialized value mismatch for property '$prop'");
213
            }
214
        }
215
    }
216
217
    /**
218
     * Generate a type-compatible non-default value for the given property.
219
     *
220
     * @return mixed
221
     */
222
    private function generateValueForProperty(\ReflectionProperty $prop, mixed $current, string $name): mixed
223
    {
224
        $type = $prop->getType();
225
        if ($type instanceof \ReflectionUnionType) {
226
            // Fallback: leave as-is for union types to avoid complex branching.
227
            return $current ?? 'u-' . $name;
228
        }
229
230
        if ($type instanceof \ReflectionNamedType) {
231
            $typeName = $type->getName();
232
            $allowsNull = $type->allowsNull();
233
234
            // Handle scalar / array / object typed properties explicitly
235
            switch ($typeName) {
236
                case 'array':
237
                    // If already non-empty keep; else supply non-empty array
238
                    return (is_array($current) && $current !== []) ? $current : [$name => 'val'];
239
                case 'int':
240
                    return ($current !== null && $current !== 0) ? $current : 101;
241
                case 'float':
242
                    return ($current !== null && $current !== 0.0) ? $current : 0.77;
243
                case 'bool':
244
                    return ($current !== null) ? !$current : true;
245
                case 'string':
246
                    return ($current !== null && $current !== '') ? $current : 'str-' . $name;
247
                default:
248
                    // Object (other than AVGItem handled earlier) or untyped fallback
249
                    if ($current !== null) {
250
                        return $current;
251
                    }
252
                    // For nullable array-like properties (?array) etc
253
                    if ($typeName === 'array') {
254
                        return [$name => 'val'];
255
                    }
256
                    // If it's a class we can't easily mock generically—return dummy string if allowed
257
                    if ($allowsNull) {
258
                        return 'obj-' . $name;
259
                    }
260
261
                    return 'obj-' . $name;
262
            }
263
        }
264
265
        // No type info (legacy) -> attempt heuristic similar to earlier code
266
        if ($current !== null) {
267
            return $current;
268
        }
269
270
        if (preg_match('/(items?|children|data|array|values)/i', $name)) {
271
            return [$name => 'val'];
272
        }
273
        if (preg_match('/(opacity)/i', $name)) {
274
            return 0.42;
275
        }
276
        if (preg_match('/(width|height|radius|length|count|size|index)/i', $name)) {
277
            return 202;
278
        }
279
        if (preg_match('/(path|transform|clip|color|stroke|fill|text)/i', $name)) {
280
            return $name . '-value';
281
        }
282
283
        return 'x-' . $name;
284
    }
285
}
286