Passed
Push — 4 ( cd0765...fc349d )
by
unknown
08:35
created

FormFieldTest   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 490
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 41
eloc 262
c 1
b 0
f 0
dl 0
loc 490
rs 9.1199

30 Methods

Rating   Name   Duplication   Size   Complexity  
A testAddExtraClass() 0 6 1
A testDefaultClasses() 0 56 1
A testDisabled() 0 7 1
B testEveryFieldTransformsReadonlyAsClone() 0 38 6
A testGetSchemaDataDefaults() 0 5 1
A testGetSchemaState() 0 6 1
A testGetSchemaDataDefaultsTitleTip() 0 8 1
A testSetSchemaData() 0 13 1
A testLinkWithForm() 0 7 1
A testGetAttributesEscapeHtml() 0 16 2
A testUpdateAttributes() 0 4 1
A testSetSchemaComponent() 0 6 1
A testAddManyExtraClasses() 0 14 1
A testSetSchemaState() 0 13 1
A testRemoveExtraClass() 0 8 1
A testAttributes() 0 8 1
A testRemoveManyExtraClasses() 0 21 1
A testAttributesHTML() 0 34 1
A testDebugEscapeHtml() 0 9 1
A testGetSchemaData() 0 10 1
A escapeHtmlDataProvider() 0 6 1
A testGetSchemaStateWithFormValidation() 0 10 1
A testHasClass() 0 9 1
A testReadonly() 0 7 1
A testHasExtraClass() 0 11 1
B testEveryFieldTransformsDisabledAsClone() 0 39 6
A testNameToLabel() 0 3 1
A testGetSetTitleTip() 0 7 1
A nameToLabelProvider() 0 11 1
A testLinkWithoutForm() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like FormFieldTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FormFieldTest, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\Forms\Tests;
4
5
use LogicException;
6
use ReflectionClass;
7
use SilverStripe\Core\ClassInfo;
8
use SilverStripe\Core\Config\Config;
9
use SilverStripe\Dev\SapphireTest;
10
use SilverStripe\Forms\CompositeField;
11
use SilverStripe\Forms\FieldList;
12
use SilverStripe\Forms\Form;
13
use SilverStripe\Forms\FormField;
14
use SilverStripe\Forms\NullableField;
15
use SilverStripe\Forms\RequiredFields;
16
use SilverStripe\Forms\Tests\FormFieldTest\TestExtension;
17
use SilverStripe\Forms\TextField;
18
use SilverStripe\Forms\Tip;
19
use SilverStripe\ORM\ValidationResult;
20
21
class FormFieldTest extends SapphireTest
22
{
23
24
    protected static $required_extensions = [
25
        FormField::class => [
26
            TestExtension::class,
27
        ],
28
    ];
29
30
    public function testDefaultClasses()
31
    {
32
        Config::nest();
33
34
        FormField::config()->update(
35
            'default_classes',
36
            [
37
                'class1',
38
            ]
39
        );
40
41
        $field = new FormField('MyField');
42
43
        $this->assertStringContainsString('class1', $field->extraClass(), 'Class list does not contain expected class');
44
45
        FormField::config()->update(
46
            'default_classes',
47
            [
48
                'class1',
49
                'class2',
50
            ]
51
        );
52
53
        $field = new FormField('MyField');
54
55
        $this->assertStringContainsString('class1 class2', $field->extraClass(), 'Class list does not contain expected class');
56
57
        FormField::config()->update(
58
            'default_classes',
59
            [
60
                'class3',
61
            ]
62
        );
63
64
        $field = new FormField('MyField');
65
66
        $this->assertStringContainsString('class3', $field->extraClass(), 'Class list does not contain expected class');
67
68
        $field->removeExtraClass('class3');
69
70
        $this->assertStringNotContainsString('class3', $field->extraClass(), 'Class list contains unexpected class');
71
72
        TextField::config()->update(
73
            'default_classes',
74
            [
75
                'textfield-class',
76
            ]
77
        );
78
79
        $field = new TextField('MyField');
80
81
        //check default classes inherit
82
        $this->assertStringContainsString('class3', $field->extraClass(), 'Class list does not contain inherited class');
83
        $this->assertStringContainsString('textfield-class', $field->extraClass(), 'Class list does not contain expected class');
84
85
        Config::unnest();
86
    }
87
88
    public function testAddExtraClass()
89
    {
90
        $field = new FormField('MyField');
91
        $field->addExtraClass('class1');
92
        $field->addExtraClass('class2');
93
        $this->assertStringEndsWith('class1 class2', $field->extraClass());
94
    }
95
96
    public function testHasExtraClass()
97
    {
98
        $field = new FormField('MyField');
99
        $field->addExtraClass('class1');
100
        $field->addExtraClass('class2');
101
        $this->assertTrue($field->hasExtraClass('class1'));
102
        $this->assertTrue($field->hasExtraClass('class2'));
103
        $this->assertTrue($field->hasExtraClass('class1 class2'));
104
        $this->assertTrue($field->hasExtraClass('class2 class1'));
105
        $this->assertFalse($field->hasExtraClass('class3'));
106
        $this->assertFalse($field->hasExtraClass('class2 class3'));
107
    }
108
109
    public function testRemoveExtraClass()
110
    {
111
        $field = new FormField('MyField');
112
        $field->addExtraClass('class1');
113
        $field->addExtraClass('class2');
114
        $this->assertStringEndsWith('class1 class2', $field->extraClass());
115
        $field->removeExtraClass('class1');
116
        $this->assertStringEndsWith('class2', $field->extraClass());
117
    }
118
119
    public function testAddManyExtraClasses()
120
    {
121
        $field = new FormField('MyField');
122
        //test we can split by a range of spaces and tabs
123
        $field->addExtraClass('class1 class2     class3	class4		class5');
124
        $this->assertStringEndsWith(
125
            'class1 class2 class3 class4 class5',
126
            $field->extraClass()
127
        );
128
        //test that duplicate classes don't get added
129
        $field->addExtraClass('class1 class2');
130
        $this->assertStringEndsWith(
131
            'class1 class2 class3 class4 class5',
132
            $field->extraClass()
133
        );
134
    }
135
136
    public function testRemoveManyExtraClasses()
137
    {
138
        $field = new FormField('MyField');
139
        $field->addExtraClass('class1 class2     class3	class4		class5');
140
        //test we can remove a single class we just added
141
        $field->removeExtraClass('class3');
142
        $this->assertStringEndsWith(
143
            'class1 class2 class4 class5',
144
            $field->extraClass()
145
        );
146
        //check we can remove many classes at once
147
        $field->removeExtraClass('class1 class5');
148
        $this->assertStringEndsWith(
149
            'class2 class4',
150
            $field->extraClass()
151
        );
152
        //check that removing a dud class is fine
153
        $field->removeExtraClass('dudClass');
154
        $this->assertStringEndsWith(
155
            'class2 class4',
156
            $field->extraClass()
157
        );
158
    }
159
160
    public function testAttributes()
161
    {
162
        $field = new FormField('MyField');
163
        $field->setAttribute('foo', 'bar');
164
        $this->assertEquals('bar', $field->getAttribute('foo'));
165
        $attrs = $field->getAttributes();
166
        $this->assertArrayHasKey('foo', $attrs);
167
        $this->assertEquals('bar', $attrs['foo']);
168
    }
169
170
    public function testAttributesHTML()
171
    {
172
        $field = new FormField('MyField');
173
174
        $field->setAttribute('foo', 'bar');
175
        $this->assertStringContainsString('foo="bar"', $field->getAttributesHTML());
176
177
        $field->setAttribute('foo', null);
178
        $this->assertStringNotContainsString('foo=', $field->getAttributesHTML());
179
180
        $field->setAttribute('foo', '');
181
        $this->assertStringNotContainsString('foo=', $field->getAttributesHTML());
182
183
        $field->setAttribute('foo', false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type string expected by parameter $value of SilverStripe\Forms\FormField::setAttribute(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

183
        $field->setAttribute('foo', /** @scrutinizer ignore-type */ false);
Loading history...
184
        $this->assertStringNotContainsString('foo=', $field->getAttributesHTML());
185
186
        $field->setAttribute('foo', true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $value of SilverStripe\Forms\FormField::setAttribute(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

186
        $field->setAttribute('foo', /** @scrutinizer ignore-type */ true);
Loading history...
187
        $this->assertStringContainsString('foo="foo"', $field->getAttributesHTML());
188
189
        $field->setAttribute('foo', 'false');
190
        $this->assertStringContainsString('foo="false"', $field->getAttributesHTML());
191
192
        $field->setAttribute('foo', 'true');
193
        $this->assertStringContainsString('foo="true"', $field->getAttributesHTML());
194
195
        $field->setAttribute('foo', 0);
196
        $this->assertStringContainsString('foo="0"', $field->getAttributesHTML());
197
198
        $field->setAttribute('one', 1);
199
        $field->setAttribute('two', 2);
200
        $field->setAttribute('three', 3);
201
        $this->assertStringNotContainsString('one="1"', $field->getAttributesHTML('one', 'two'));
202
        $this->assertStringNotContainsString('two="2"', $field->getAttributesHTML('one', 'two'));
203
        $this->assertStringContainsString('three="3"', $field->getAttributesHTML('one', 'two'));
204
    }
205
206
    /**
207
     * Covering all potential inputs for Convert::raw2xml
208
     */
209
    public function escapeHtmlDataProvider()
210
    {
211
        return [
212
            ['<html>'],
213
            [['<html>']],
214
            [['<html>' => '<html>']]
215
        ];
216
    }
217
218
    /**
219
     * @dataProvider escapeHtmlDataProvider
220
     **/
221
    public function testGetAttributesEscapeHtml($value)
222
    {
223
        $key = bin2hex(random_bytes(4));
224
225
        if (is_scalar($value)) {
226
            $field = new FormField('<html>', '<html>', '<html>');
227
            $field->setAttribute($value, $key);
228
            $html = $field->getAttributesHTML();
229
            $this->assertFalse(strpos($html, '<html>'));
230
        }
231
232
        $field = new FormField('<html>', '<html>', '<html>');
233
        $field->setAttribute($key, $value);
234
        $html = $field->getAttributesHTML();
235
236
        $this->assertFalse(strpos($html, '<html>'));
237
    }
238
239
    /**
240
     * @dataProvider escapeHtmlDataProvider
241
     */
242
    public function testDebugEscapeHtml($value)
243
    {
244
        $field = new FormField('<html>', '<html>', '<html>');
245
        $field->setAttribute('<html>', $value);
246
        $field->setMessage('<html>', null, ValidationResult::CAST_HTML);
247
248
        $html = $field->debug();
249
250
        $this->assertFalse(strpos($html, '<html>'));
251
    }
252
253
    public function testReadonly()
254
    {
255
        $field = new FormField('MyField');
256
        $field->setReadonly(true);
257
        $this->assertStringContainsString('readonly="readonly"', $field->getAttributesHTML());
258
        $field->setReadonly(false);
259
        $this->assertStringNotContainsString('readonly="readonly"', $field->getAttributesHTML());
260
    }
261
262
    public function testDisabled()
263
    {
264
        $field = new FormField('MyField');
265
        $field->setDisabled(true);
266
        $this->assertStringContainsString('disabled="disabled"', $field->getAttributesHTML());
267
        $field->setDisabled(false);
268
        $this->assertStringNotContainsString('disabled="disabled"', $field->getAttributesHTML());
269
    }
270
271
    public function testEveryFieldTransformsReadonlyAsClone()
272
    {
273
        $fieldClasses = ClassInfo::subclassesFor(FormField::class);
274
        foreach ($fieldClasses as $fieldClass) {
275
            $reflectionClass = new ReflectionClass($fieldClass);
276
            if (!$reflectionClass->isInstantiable()) {
277
                continue;
278
            }
279
            $constructor = $reflectionClass->getMethod('__construct');
280
            if ($constructor->getNumberOfRequiredParameters() > 1) {
281
                continue;
282
            }
283
            if (is_a($fieldClass, CompositeField::class, true)) {
284
                continue;
285
            }
286
287
            $fieldName = $reflectionClass->getShortName() . '_instance';
288
            /** @var FormField $instance */
289
            if ($fieldClass = NullableField::class) {
290
                $instance = new $fieldClass(new TextField($fieldName));
291
            } else {
292
                $instance = new $fieldClass($fieldName);
293
            }
294
            $isReadonlyBefore = $instance->isReadonly();
295
            $readonlyInstance = $instance->performReadonlyTransformation();
296
            $this->assertEquals(
297
                $isReadonlyBefore,
298
                $instance->isReadonly(),
299
                "FormField class {$fieldClass} retains its readonly state after calling performReadonlyTransformation()"
300
            );
301
            $this->assertTrue(
302
                $readonlyInstance->isReadonly(),
303
                "FormField class {$fieldClass} returns a valid readonly representation as of isReadonly()"
304
            );
305
            $this->assertNotSame(
306
                $readonlyInstance,
307
                $instance,
308
                "FormField class {$fieldClass} returns a valid cloned readonly representation"
309
            );
310
        }
311
    }
312
313
    public function testEveryFieldTransformsDisabledAsClone()
314
    {
315
        $fieldClasses = ClassInfo::subclassesFor(FormField::class);
316
        foreach ($fieldClasses as $fieldClass) {
317
            $reflectionClass = new ReflectionClass($fieldClass);
318
            if (!$reflectionClass->isInstantiable()) {
319
                continue;
320
            }
321
            $constructor = $reflectionClass->getMethod('__construct');
322
            if ($constructor->getNumberOfRequiredParameters() > 1) {
323
                continue;
324
            }
325
            if (is_a($fieldClass, CompositeField::class, true)) {
326
                continue;
327
            }
328
329
            $fieldName = $reflectionClass->getShortName() . '_instance';
330
            /** @var FormField $instance */
331
            if ($fieldClass = NullableField::class) {
332
                $instance = new $fieldClass(new TextField($fieldName));
333
            } else {
334
                $instance = new $fieldClass($fieldName);
335
            }
336
337
            $isDisabledBefore = $instance->isDisabled();
338
            $disabledInstance = $instance->performDisabledTransformation();
339
            $this->assertEquals(
340
                $isDisabledBefore,
341
                $instance->isDisabled(),
342
                "FormField class {$fieldClass} retains its disabled state after calling performDisabledTransformation()"
343
            );
344
            $this->assertTrue(
345
                $disabledInstance->isDisabled(),
346
                "FormField class {$fieldClass} returns a valid disabled representation as of isDisabled()"
347
            );
348
            $this->assertNotSame(
349
                $disabledInstance,
350
                $instance,
351
                "FormField class {$fieldClass} returns a valid cloned disabled representation"
352
            );
353
        }
354
    }
355
356
    public function testUpdateAttributes()
357
    {
358
        $field = new FormField('MyField');
359
        $this->assertArrayHasKey('extended', $field->getAttributes());
360
    }
361
362
    public function testSetSchemaComponent()
363
    {
364
        $field = new FormField('MyField');
365
        $field = $field->setSchemaComponent('MyComponent');
366
        $component = $field->getSchemaComponent();
367
        $this->assertEquals('MyComponent', $component);
368
    }
369
370
    public function testGetSchemaDataDefaults()
371
    {
372
        $field = new FormField('MyField');
373
        $schema = $field->getSchemaDataDefaults();
374
        $this->assertIsArray($schema);
375
    }
376
377
    public function testGetSchemaDataDefaultsTitleTip()
378
    {
379
        $field = new FormField('MyField');
380
        $schema = $field->getSchemaDataDefaults();
381
        $this->assertFalse(array_key_exists('titleTip', $schema));
382
        $field->setTitleTip(new Tip('Test tip'));
383
        $schema = $field->getSchemaDataDefaults();
384
        $this->assertSame('Test tip', $schema['titleTip']['content']);
385
    }
386
387
    public function testGetSchemaData()
388
    {
389
        $field = new FormField('MyField');
390
        $schema = $field->getSchemaData();
391
        $this->assertEquals('MyField', $schema['name']);
392
393
        // Make sure the schema data is up-to-date with object properties.
394
        $field->setName('UpdatedField');
395
        $schema = $field->getSchemaData();
396
        $this->assertEquals($field->getName(), $schema['name']);
397
    }
398
399
    public function testSetSchemaData()
400
    {
401
        $field = new FormField('MyField');
402
403
        // Make sure the user can update values.
404
        $field->setSchemaData(['name' => 'MyUpdatedField']);
405
        $schema = $field->getSchemaData();
406
        $this->assertEquals($schema['name'], 'MyUpdatedField');
407
408
        // Make user the user can't define custom keys on the schema.
409
        $field = $field->setSchemaData(['myCustomKey' => 'yolo']);
410
        $schema = $field->getSchemaData();
411
        $this->assertEquals(array_key_exists('myCustomKey', $schema), false);
412
    }
413
414
    public function testGetSchemaState()
415
    {
416
        $field = new FormField('MyField');
417
        $field->setValue('My value');
418
        $schema = $field->getSchemaState();
419
        $this->assertEquals('My value', $schema['value']);
420
    }
421
422
    public function testSetSchemaState()
423
    {
424
        $field = new FormField('MyField');
425
426
        // Make sure the user can update values.
427
        $field->setSchemaState(['value' => 'My custom value']);
428
        $schema = $field->getSchemaState();
429
        $this->assertEquals($schema['value'], 'My custom value');
430
431
        // Make user the user can't define custom keys on the schema.
432
        $field->setSchemaState(['myCustomKey' => 'yolo']);
433
        $schema = $field->getSchemaState();
434
        $this->assertEquals(array_key_exists('myCustomKey', $schema), false);
435
    }
436
437
    public function testGetSchemaStateWithFormValidation()
438
    {
439
        $field = new FormField('MyField', 'My Field');
440
        $validator = new RequiredFields('MyField');
441
        $form = new Form(null, 'TestForm', new FieldList($field), new FieldList(), $validator);
442
        $form->validationResult();
443
        $schema = $field->getSchemaState();
444
        $this->assertEquals(
445
            '"My Field" is required',
446
            $schema['message']['value']
447
        );
448
    }
449
450
    public function testHasClass()
451
    {
452
        $field = new FormField('Test');
453
        $field->addExtraClass('foo BAr cool-banana');
454
455
        $this->assertTrue($field->hasClass('foo'));
456
        $this->assertTrue($field->hasClass('bAr'));
457
        $this->assertFalse($field->hasClass('banana'));
458
        $this->assertTrue($field->hasClass('cool-BAnana'));
459
    }
460
461
    public function testLinkWithForm()
462
    {
463
        $field = new FormField('Test');
464
        $form = new Form(null, 'Test', new FieldList, new FieldList);
465
        $form->setFormAction('foo');
466
        $field->setForm($form);
467
        $this->assertSame('foo/field/Test/bar', $field->Link('bar'));
468
    }
469
470
    public function testLinkWithoutForm()
471
    {
472
        $this->expectException(LogicException::class);
473
        $field = new FormField('Test');
474
        $field->Link('bar');
475
    }
476
477
    /**
478
     * @param string $name
479
     * @param string $expected
480
     * @dataProvider nameToLabelProvider
481
     */
482
    public function testNameToLabel($name, $expected)
483
    {
484
        $this->assertSame($expected, FormField::name_to_label($name));
485
    }
486
487
    /**
488
     * @return array[]
489
     */
490
    public function nameToLabelProvider()
491
    {
492
        return [
493
            ['TotalAmount', 'Total amount'],
494
            ['Organisation.ZipCode', 'Organisation zip code'],
495
            ['Organisation.zipCode', 'Organisation zip code'],
496
            ['FooBarBaz', 'Foo bar baz'],
497
            ['URLSegment', 'URL segment'],
498
            ['ONLYCAPS', 'ONLYCAPS'],
499
            ['onlylower', 'Onlylower'],
500
            ['SpecialURL', 'Special URL'],
501
        ];
502
    }
503
504
    public function testGetSetTitleTip()
505
    {
506
        $field = new FormField('MyField');
507
        $this->assertNull($field->getTitleTip());
508
        $field->setTitleTip(new Tip('Test tip'));
509
        $this->assertInstanceOf(Tip::class, $field->getTitleTip());
510
        $this->assertSame('Test tip', $field->getTitleTip()->getMessage());
511
    }
512
}
513