Passed
Pull Request — 4.10 (#10183)
by Loz
08:37
created

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