Passed
Push — 4.4 ( 8dcaed...49fda5 )
by Maxime
08:16 queued 10s
created

FormFieldTest::escapeHtmlDataProvider()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 6
rs 10
c 0
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\ORM\ValidationResult;
19
20
class FormFieldTest extends SapphireTest
21
{
22
23
    protected static $required_extensions = [
24
        FormField::class => [
25
            TestExtension::class,
26
        ],
27
    ];
28
29
    public function testDefaultClasses()
30
    {
31
        Config::nest();
32
33
        FormField::config()->update(
34
            'default_classes',
35
            [
36
                'class1',
37
            ]
38
        );
39
40
        $field = new FormField('MyField');
41
42
        $this->assertContains('class1', $field->extraClass(), 'Class list does not contain expected class');
43
44
        FormField::config()->update(
45
            'default_classes',
46
            [
47
                'class1',
48
                'class2',
49
            ]
50
        );
51
52
        $field = new FormField('MyField');
53
54
        $this->assertContains('class1 class2', $field->extraClass(), 'Class list does not contain expected class');
55
56
        FormField::config()->update(
57
            'default_classes',
58
            [
59
                'class3',
60
            ]
61
        );
62
63
        $field = new FormField('MyField');
64
65
        $this->assertContains('class3', $field->extraClass(), 'Class list does not contain expected class');
66
67
        $field->removeExtraClass('class3');
68
69
        $this->assertNotContains('class3', $field->extraClass(), 'Class list contains unexpected class');
70
71
        TextField::config()->update(
72
            'default_classes',
73
            [
74
                'textfield-class',
75
            ]
76
        );
77
78
        $field = new TextField('MyField');
79
80
        //check default classes inherit
81
        $this->assertContains('class3', $field->extraClass(), 'Class list does not contain inherited class');
82
        $this->assertContains('textfield-class', $field->extraClass(), 'Class list does not contain expected class');
83
84
        Config::unnest();
85
    }
86
87
    public function testAddExtraClass()
88
    {
89
        $field = new FormField('MyField');
90
        $field->addExtraClass('class1');
91
        $field->addExtraClass('class2');
92
        $this->assertStringEndsWith('class1 class2', $field->extraClass());
93
    }
94
95
    public function testRemoveExtraClass()
96
    {
97
        $field = new FormField('MyField');
98
        $field->addExtraClass('class1');
99
        $field->addExtraClass('class2');
100
        $this->assertStringEndsWith('class1 class2', $field->extraClass());
101
        $field->removeExtraClass('class1');
102
        $this->assertStringEndsWith('class2', $field->extraClass());
103
    }
104
105
    public function testAddManyExtraClasses()
106
    {
107
        $field = new FormField('MyField');
108
        //test we can split by a range of spaces and tabs
109
        $field->addExtraClass('class1 class2     class3	class4		class5');
110
        $this->assertStringEndsWith(
111
            'class1 class2 class3 class4 class5',
112
            $field->extraClass()
113
        );
114
        //test that duplicate classes don't get added
115
        $field->addExtraClass('class1 class2');
116
        $this->assertStringEndsWith(
117
            'class1 class2 class3 class4 class5',
118
            $field->extraClass()
119
        );
120
    }
121
122
    public function testRemoveManyExtraClasses()
123
    {
124
        $field = new FormField('MyField');
125
        $field->addExtraClass('class1 class2     class3	class4		class5');
126
        //test we can remove a single class we just added
127
        $field->removeExtraClass('class3');
128
        $this->assertStringEndsWith(
129
            'class1 class2 class4 class5',
130
            $field->extraClass()
131
        );
132
        //check we can remove many classes at once
133
        $field->removeExtraClass('class1 class5');
134
        $this->assertStringEndsWith(
135
            'class2 class4',
136
            $field->extraClass()
137
        );
138
        //check that removing a dud class is fine
139
        $field->removeExtraClass('dudClass');
140
        $this->assertStringEndsWith(
141
            'class2 class4',
142
            $field->extraClass()
143
        );
144
    }
145
146
    public function testAttributes()
147
    {
148
        $field = new FormField('MyField');
149
        $field->setAttribute('foo', 'bar');
150
        $this->assertEquals('bar', $field->getAttribute('foo'));
151
        $attrs = $field->getAttributes();
152
        $this->assertArrayHasKey('foo', $attrs);
153
        $this->assertEquals('bar', $attrs['foo']);
154
    }
155
156
    public function testAttributesHTML()
157
    {
158
        $field = new FormField('MyField');
159
160
        $field->setAttribute('foo', 'bar');
161
        $this->assertContains('foo="bar"', $field->getAttributesHTML());
162
163
        $field->setAttribute('foo', null);
164
        $this->assertNotContains('foo=', $field->getAttributesHTML());
165
166
        $field->setAttribute('foo', '');
167
        $this->assertNotContains('foo=', $field->getAttributesHTML());
168
169
        $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

169
        $field->setAttribute('foo', /** @scrutinizer ignore-type */ false);
Loading history...
170
        $this->assertNotContains('foo=', $field->getAttributesHTML());
171
172
        $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

172
        $field->setAttribute('foo', /** @scrutinizer ignore-type */ true);
Loading history...
173
        $this->assertContains('foo="foo"', $field->getAttributesHTML());
174
175
        $field->setAttribute('foo', 'false');
176
        $this->assertContains('foo="false"', $field->getAttributesHTML());
177
178
        $field->setAttribute('foo', 'true');
179
        $this->assertContains('foo="true"', $field->getAttributesHTML());
180
181
        $field->setAttribute('foo', 0);
182
        $this->assertContains('foo="0"', $field->getAttributesHTML());
183
184
        $field->setAttribute('one', 1);
185
        $field->setAttribute('two', 2);
186
        $field->setAttribute('three', 3);
187
        $this->assertNotContains('one="1"', $field->getAttributesHTML('one', 'two'));
188
        $this->assertNotContains('two="2"', $field->getAttributesHTML('one', 'two'));
189
        $this->assertContains('three="3"', $field->getAttributesHTML('one', 'two'));
190
    }
191
192
    /**
193
     * Covering all potential inputs for Convert::raw2xml
194
     */
195
    public function escapeHtmlDataProvider()
196
    {
197
        return [
198
            ['<html>'],
199
            [['<html>']],
200
            [['<html>' => '<html>']]
201
        ];
202
    }
203
204
    /**
205
     * @dataProvider escapeHtmlDataProvider
206
     **/
207
    public function testGetAttributesEscapeHtml($value)
208
    {
209
        $key = bin2hex(random_bytes(4));
210
211
        if (is_scalar($value)) {
212
            $field = new FormField('<html>', '<html>', '<html>');
213
            $field->setAttribute($value, $key);
214
            $html = $field->getAttributesHTML();
215
            $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

215
            $this->assertFalse(/** @scrutinizer ignore-type */ strpos($html, '<html>'));
Loading history...
216
        }
217
218
        $field = new FormField('<html>', '<html>', '<html>');
219
        $field->setAttribute($key, $value);
220
        $html = $field->getAttributesHTML();
221
222
        $this->assertFalse(strpos($html, '<html>'));
223
    }
224
225
    /**
226
     * @dataProvider escapeHtmlDataProvider
227
     */
228
    public function testDebugEscapeHtml($value)
229
    {
230
        $field = new FormField('<html>', '<html>', '<html>');
231
        $field->setAttribute('<html>', $value);
232
        $field->setMessage('<html>', null, ValidationResult::CAST_HTML);
233
234
        $html = $field->debug();
235
236
        $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

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