Passed
Pull Request — 4 (#10112)
by Guy
06:11
created

FormFieldTest::testGetSchemaDataDefaultsTitleTip()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 6
nc 1
nop 0
dl 0
loc 8
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Forms\Tests;
4
5
use ReflectionClass;
6
use SilverStripe\Core\ClassInfo;
7
use SilverStripe\Core\Config\Config;
8
use SilverStripe\Core\Convert;
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->assertContains('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->assertContains('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->assertContains('class3', $field->extraClass(), 'Class list does not contain expected class');
67
68
        $field->removeExtraClass('class3');
69
70
        $this->assertNotContains('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->assertContains('class3', $field->extraClass(), 'Class list does not contain inherited class');
83
        $this->assertContains('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->extraClass('class2'));
0 ignored issues
show
Bug introduced by
$field->extraClass('class2') of type string is incompatible with the type boolean expected by parameter $condition of PHPUnit_Framework_Assert::assertTrue(). ( Ignorable by Annotation )

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

102
        $this->assertTrue(/** @scrutinizer ignore-type */ $field->extraClass('class2'));
Loading history...
Unused Code introduced by
The call to SilverStripe\Forms\FormField::extraClass() has too many arguments starting with 'class2'. ( Ignorable by Annotation )

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

102
        $this->assertTrue($field->/** @scrutinizer ignore-call */ extraClass('class2'));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
103
        $this->assertTrue($field->extraClass('class1 class2'));
104
        $this->assertTrue($field->extraClass('class2 class1'));
105
        $this->assertFalse($field->extraClass('class3'));
0 ignored issues
show
Bug introduced by
$field->extraClass('class3') of type string is incompatible with the type boolean expected by parameter $condition of PHPUnit_Framework_Assert::assertFalse(). ( Ignorable by Annotation )

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

105
        $this->assertFalse(/** @scrutinizer ignore-type */ $field->extraClass('class3'));
Loading history...
106
    }
107
108
    public function testRemoveExtraClass()
109
    {
110
        $field = new FormField('MyField');
111
        $field->addExtraClass('class1');
112
        $field->addExtraClass('class2');
113
        $this->assertStringEndsWith('class1 class2', $field->extraClass());
114
        $field->removeExtraClass('class1');
115
        $this->assertStringEndsWith('class2', $field->extraClass());
116
    }
117
118
    public function testAddManyExtraClasses()
119
    {
120
        $field = new FormField('MyField');
121
        //test we can split by a range of spaces and tabs
122
        $field->addExtraClass('class1 class2     class3	class4		class5');
123
        $this->assertStringEndsWith(
124
            'class1 class2 class3 class4 class5',
125
            $field->extraClass()
126
        );
127
        //test that duplicate classes don't get added
128
        $field->addExtraClass('class1 class2');
129
        $this->assertStringEndsWith(
130
            'class1 class2 class3 class4 class5',
131
            $field->extraClass()
132
        );
133
    }
134
135
    public function testRemoveManyExtraClasses()
136
    {
137
        $field = new FormField('MyField');
138
        $field->addExtraClass('class1 class2     class3	class4		class5');
139
        //test we can remove a single class we just added
140
        $field->removeExtraClass('class3');
141
        $this->assertStringEndsWith(
142
            'class1 class2 class4 class5',
143
            $field->extraClass()
144
        );
145
        //check we can remove many classes at once
146
        $field->removeExtraClass('class1 class5');
147
        $this->assertStringEndsWith(
148
            'class2 class4',
149
            $field->extraClass()
150
        );
151
        //check that removing a dud class is fine
152
        $field->removeExtraClass('dudClass');
153
        $this->assertStringEndsWith(
154
            'class2 class4',
155
            $field->extraClass()
156
        );
157
    }
158
159
    public function testAttributes()
160
    {
161
        $field = new FormField('MyField');
162
        $field->setAttribute('foo', 'bar');
163
        $this->assertEquals('bar', $field->getAttribute('foo'));
164
        $attrs = $field->getAttributes();
165
        $this->assertArrayHasKey('foo', $attrs);
166
        $this->assertEquals('bar', $attrs['foo']);
167
    }
168
169
    public function testAttributesHTML()
170
    {
171
        $field = new FormField('MyField');
172
173
        $field->setAttribute('foo', 'bar');
174
        $this->assertContains('foo="bar"', $field->getAttributesHTML());
175
176
        $field->setAttribute('foo', null);
177
        $this->assertNotContains('foo=', $field->getAttributesHTML());
178
179
        $field->setAttribute('foo', '');
180
        $this->assertNotContains('foo=', $field->getAttributesHTML());
181
182
        $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

182
        $field->setAttribute('foo', /** @scrutinizer ignore-type */ false);
Loading history...
183
        $this->assertNotContains('foo=', $field->getAttributesHTML());
184
185
        $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

185
        $field->setAttribute('foo', /** @scrutinizer ignore-type */ true);
Loading history...
186
        $this->assertContains('foo="foo"', $field->getAttributesHTML());
187
188
        $field->setAttribute('foo', 'false');
189
        $this->assertContains('foo="false"', $field->getAttributesHTML());
190
191
        $field->setAttribute('foo', 'true');
192
        $this->assertContains('foo="true"', $field->getAttributesHTML());
193
194
        $field->setAttribute('foo', 0);
195
        $this->assertContains('foo="0"', $field->getAttributesHTML());
196
197
        $field->setAttribute('one', 1);
198
        $field->setAttribute('two', 2);
199
        $field->setAttribute('three', 3);
200
        $this->assertNotContains('one="1"', $field->getAttributesHTML('one', 'two'));
201
        $this->assertNotContains('two="2"', $field->getAttributesHTML('one', 'two'));
202
        $this->assertContains('three="3"', $field->getAttributesHTML('one', 'two'));
203
    }
204
205
    /**
206
     * Covering all potential inputs for Convert::raw2xml
207
     */
208
    public function escapeHtmlDataProvider()
209
    {
210
        return [
211
            ['<html>'],
212
            [['<html>']],
213
            [['<html>' => '<html>']]
214
        ];
215
    }
216
217
    /**
218
     * @dataProvider escapeHtmlDataProvider
219
     **/
220
    public function testGetAttributesEscapeHtml($value)
221
    {
222
        $key = bin2hex(random_bytes(4));
223
224
        if (is_scalar($value)) {
225
            $field = new FormField('<html>', '<html>', '<html>');
226
            $field->setAttribute($value, $key);
227
            $html = $field->getAttributesHTML();
228
            $this->assertFalse(strpos($html, '<html>'));
0 ignored issues
show
Bug introduced by
strpos($html, '<html>') of type integer is incompatible with the type boolean expected by parameter $condition of PHPUnit_Framework_Assert::assertFalse(). ( Ignorable by Annotation )

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

228
            $this->assertFalse(/** @scrutinizer ignore-type */ strpos($html, '<html>'));
Loading history...
229
        }
230
231
        $field = new FormField('<html>', '<html>', '<html>');
232
        $field->setAttribute($key, $value);
233
        $html = $field->getAttributesHTML();
234
235
        $this->assertFalse(strpos($html, '<html>'));
236
    }
237
238
    /**
239
     * @dataProvider escapeHtmlDataProvider
240
     */
241
    public function testDebugEscapeHtml($value)
242
    {
243
        $field = new FormField('<html>', '<html>', '<html>');
244
        $field->setAttribute('<html>', $value);
245
        $field->setMessage('<html>', null, ValidationResult::CAST_HTML);
246
247
        $html = $field->debug();
248
249
        $this->assertFalse(strpos($html, '<html>'));
0 ignored issues
show
Bug introduced by
strpos($html, '<html>') of type integer is incompatible with the type boolean expected by parameter $condition of PHPUnit_Framework_Assert::assertFalse(). ( Ignorable by Annotation )

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

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