Completed
Push — 4 ( ec6b01...dc3b90 )
by Steve
05:20 queued 05:10
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 testRemoveExtraClass()
97
    {
98
        $field = new FormField('MyField');
99
        $field->addExtraClass('class1');
100
        $field->addExtraClass('class2');
101
        $this->assertStringEndsWith('class1 class2', $field->extraClass());
102
        $field->removeExtraClass('class1');
103
        $this->assertStringEndsWith('class2', $field->extraClass());
104
    }
105
106
    public function testAddManyExtraClasses()
107
    {
108
        $field = new FormField('MyField');
109
        //test we can split by a range of spaces and tabs
110
        $field->addExtraClass('class1 class2     class3	class4		class5');
111
        $this->assertStringEndsWith(
112
            'class1 class2 class3 class4 class5',
113
            $field->extraClass()
114
        );
115
        //test that duplicate classes don't get added
116
        $field->addExtraClass('class1 class2');
117
        $this->assertStringEndsWith(
118
            'class1 class2 class3 class4 class5',
119
            $field->extraClass()
120
        );
121
    }
122
123
    public function testRemoveManyExtraClasses()
124
    {
125
        $field = new FormField('MyField');
126
        $field->addExtraClass('class1 class2     class3	class4		class5');
127
        //test we can remove a single class we just added
128
        $field->removeExtraClass('class3');
129
        $this->assertStringEndsWith(
130
            'class1 class2 class4 class5',
131
            $field->extraClass()
132
        );
133
        //check we can remove many classes at once
134
        $field->removeExtraClass('class1 class5');
135
        $this->assertStringEndsWith(
136
            'class2 class4',
137
            $field->extraClass()
138
        );
139
        //check that removing a dud class is fine
140
        $field->removeExtraClass('dudClass');
141
        $this->assertStringEndsWith(
142
            'class2 class4',
143
            $field->extraClass()
144
        );
145
    }
146
147
    public function testAttributes()
148
    {
149
        $field = new FormField('MyField');
150
        $field->setAttribute('foo', 'bar');
151
        $this->assertEquals('bar', $field->getAttribute('foo'));
152
        $attrs = $field->getAttributes();
153
        $this->assertArrayHasKey('foo', $attrs);
154
        $this->assertEquals('bar', $attrs['foo']);
155
    }
156
157
    public function testAttributesHTML()
158
    {
159
        $field = new FormField('MyField');
160
161
        $field->setAttribute('foo', 'bar');
162
        $this->assertContains('foo="bar"', $field->getAttributesHTML());
163
164
        $field->setAttribute('foo', null);
165
        $this->assertNotContains('foo=', $field->getAttributesHTML());
166
167
        $field->setAttribute('foo', '');
168
        $this->assertNotContains('foo=', $field->getAttributesHTML());
169
170
        $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

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

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

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

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