Completed
Push — 3.x ( e0aa21...a9c151 )
by Vincent
06:07 queued 03:02
created

ListMapperTest::testTypeGuessActionField()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Tests\Datagrid;
15
16
use PHPUnit\Framework\TestCase;
17
use Sonata\AdminBundle\Admin\AbstractAdmin;
18
use Sonata\AdminBundle\Admin\AdminInterface;
19
use Sonata\AdminBundle\Admin\BaseFieldDescription;
20
use Sonata\AdminBundle\Admin\FieldDescriptionCollection;
21
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
22
use Sonata\AdminBundle\Builder\ListBuilderInterface;
23
use Sonata\AdminBundle\Datagrid\ListMapper;
24
use Sonata\AdminBundle\Model\ModelManagerInterface;
25
use Sonata\AdminBundle\Translator\NoopLabelTranslatorStrategy;
26
27
/**
28
 * @author Andrej Hudec <[email protected]>
29
 */
30
class ListMapperTest extends TestCase
31
{
32
    private const DEFAULT_GRANTED_ROLE = 'ROLE_ADMIN_BAZ';
33
34
    /**
35
     * @var ListMapper
36
     */
37
    private $listMapper;
38
39
    /**
40
     * @var FieldDescriptionCollection
41
     */
42
    private $fieldDescriptionCollection;
43
44
    /**
45
     * @var AdminInterface
46
     */
47
    private $admin;
48
49
    protected function setUp(): void
50
    {
51
        $listBuilder = $this->createMock(ListBuilderInterface::class);
52
        $this->fieldDescriptionCollection = new FieldDescriptionCollection();
53
        $this->admin = $this->createMock(AbstractAdmin::class);
54
55
        $listBuilder
56
            ->method('addField')
57
            ->willReturnCallback(static function (
58
                FieldDescriptionCollection $list,
59
                ?string $type,
60
                BaseFieldDescription $fieldDescription,
61
                AbstractAdmin $admin
0 ignored issues
show
Unused Code introduced by
The parameter $admin is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
62
            ): void {
63
                $fieldDescription->setType($type);
64
                $list->add($fieldDescription);
65
            });
66
67
        $modelManager = $this->createMock(ModelManagerInterface::class);
68
69
        $modelManager
70
            ->method('getNewFieldDescriptionInstance')
71
            ->willReturnCallback(function (?string $class, string $name, array $options = []): BaseFieldDescription {
72
                $fieldDescription = $this->getFieldDescriptionMock();
73
                $fieldDescription->setName($name);
74
                $fieldDescription->setOptions($options);
75
76
                return $fieldDescription;
77
            });
78
79
        $this->admin
80
            ->method('getModelManager')
81
            ->willReturn($modelManager);
82
83
        $labelTranslatorStrategy = new NoopLabelTranslatorStrategy();
84
85
        $this->admin
86
            ->method('getLabelTranslatorStrategy')
87
            ->willReturn($labelTranslatorStrategy);
88
89
        $this->admin
90
            ->method('isGranted')
91
            ->willReturnCallback(static function (string $name, ?object $object = null): bool {
0 ignored issues
show
Unused Code introduced by
The parameter $object is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
92
                return self::DEFAULT_GRANTED_ROLE === $name;
93
            });
94
95
        $this->listMapper = new ListMapper($listBuilder, $this->fieldDescriptionCollection, $this->admin);
96
    }
97
98
    public function testFluidInterface(): void
99
    {
100
        $fieldDescription = $this->getFieldDescriptionMock('fooName', 'fooLabel');
101
102
        $this->assertSame($this->listMapper, $this->listMapper->add($fieldDescription));
103
        $this->assertSame($this->listMapper, $this->listMapper->remove('fooName'));
104
        $this->assertSame($this->listMapper, $this->listMapper->reorder([]));
105
    }
106
107
    public function testGet(): void
108
    {
109
        $this->assertFalse($this->listMapper->has('fooName'));
110
111
        $fieldDescription = $this->getFieldDescriptionMock('fooName', 'fooLabel');
112
113
        $this->listMapper->add($fieldDescription);
114
        $this->assertSame($fieldDescription, $this->listMapper->get('fooName'));
115
    }
116
117
    public function testAddIdentifier(): void
118
    {
119
        $this->assertFalse($this->listMapper->has('fooName'));
120
121
        $fieldDescription = $this->getFieldDescriptionMock('fooName', 'fooLabel');
122
123
        $this->listMapper->addIdentifier($fieldDescription);
0 ignored issues
show
Documentation introduced by
$fieldDescription is of type object<Sonata\AdminBundl...n\BaseFieldDescription>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
124
        $this->assertTrue($this->listMapper->has('fooName'));
125
126
        $fieldDescription = $this->listMapper->get('fooName');
127
        $this->assertTrue($fieldDescription->getOption('identifier'));
128
    }
129
130
    public function testAddOptionIdentifier(): void
131
    {
132
        $this->assertFalse($this->listMapper->has('fooName'));
133
        $this->assertFalse($this->listMapper->has('barName'));
134
        $this->assertFalse($this->listMapper->has('bazName'));
135
136
        $this->listMapper->add('barName');
137
        $this->assertNull($this->listMapper->get('barName')->getOption('identifier'));
138
        $this->listMapper->add('fooName', null, ['identifier' => true]);
139
        $this->assertTrue($this->listMapper->has('fooName'));
140
        $this->assertTrue($this->listMapper->get('fooName')->getOption('identifier'));
141
        $this->listMapper->add('bazName', null, ['identifier' => false]);
142
        $this->assertTrue($this->listMapper->has('bazName'));
143
        $this->assertFalse($this->listMapper->get('bazName')->getOption('identifier'));
144
    }
145
146
    /**
147
     * @group legacy
148
     *
149
     * @expectedDeprecation Passing a non boolean value for the "identifier" option is deprecated since sonata-project/admin-bundle 3.51 and will throw an exception in 4.0.
150
     *
151
     * @dataProvider getWrongIdentifierOptions
152
     */
153
    public function testAddOptionIdentifierWithDeprecatedValue(bool $expected, $value): void
154
    {
155
        $this->assertFalse($this->listMapper->has('fooName'));
156
        $this->listMapper->add('fooName', null, ['identifier' => $value]);
157
        $this->assertTrue($this->listMapper->has('fooName'));
158
        $this->assertSame($expected, $this->listMapper->get('fooName')->getOption('identifier'));
159
    }
160
161
    /**
162
     * @dataProvider getWrongIdentifierOptions
163
     */
164
    public function testAddOptionIdentifierWithWrongValue(bool $expected, $value): void
165
    {
166
        // NEXT_MAJOR: Remove the following `markTestSkipped()` call and the `testAddOptionIdentifierWithDeprecatedValue()` test
167
        $this->markTestSkipped('This test must be run in 4.0');
168
169
        $this->assertFalse($this->listMapper->has('fooName'));
170
171
        $this->expectException(\InvalidArgumentException::class);
172
        $this->expectExceptionMessageMatches('/^Value for "identifier" option must be boolean, .+ given.$/');
173
174
        $this->listMapper->add('fooName', null, ['identifier' => $value]);
175
    }
176
177
    public function getWrongIdentifierOptions(): iterable
178
    {
179
        return [
180
            [true, 1],
181
            [true, 'string'],
182
            [true, new \stdClass()],
183
            [true, [null]],
184
            [false, 0],
185
            [false, null],
186
            [false, ''],
187
            [false, '0'],
188
            [false, []],
189
        ];
190
    }
191
192
    public function testAdd(): void
193
    {
194
        $this->listMapper->add('fooName');
195
        $this->listMapper->add('fooNameLabelBar', null, ['label' => 'Foo Bar']);
196
        $this->listMapper->add('fooNameLabelFalse', null, ['label' => false]);
197
198
        $this->assertTrue($this->listMapper->has('fooName'));
199
200
        $fieldDescription = $this->listMapper->get('fooName');
201
        $fieldLabelBar = $this->listMapper->get('fooNameLabelBar');
202
        $fieldLabelFalse = $this->listMapper->get('fooNameLabelFalse');
203
204
        $this->assertInstanceOf(FieldDescriptionInterface::class, $fieldDescription);
205
        $this->assertSame('fooName', $fieldDescription->getName());
206
        $this->assertSame('fooName', $fieldDescription->getOption('label'));
207
        $this->assertSame('Foo Bar', $fieldLabelBar->getOption('label'));
208
        $this->assertFalse($fieldLabelFalse->getOption('label'));
209
    }
210
211
    /**
212
     * @group legacy
213
     */
214
    public function testLegacyAddViewInlineAction(): void
215
    {
216
        $this->assertFalse($this->listMapper->has('_action'));
217
        $this->listMapper->add('_action', 'actions', ['actions' => ['view' => []]]);
218
219
        $this->assertTrue($this->listMapper->has('_action'));
220
221
        $fieldDescription = $this->listMapper->get('_action');
222
223
        $this->assertInstanceOf(FieldDescriptionInterface::class, $fieldDescription);
224
        $this->assertSame('_action', $fieldDescription->getName());
225
        $this->assertCount(1, $fieldDescription->getOption('actions'));
226
        $this->assertSame(['show' => []], $fieldDescription->getOption('actions'));
227
    }
228
229
    public function testAddViewInlineAction(): void
230
    {
231
        $this->assertFalse($this->listMapper->has('_action'));
232
        $this->listMapper->add('_action', 'actions', ['actions' => ['show' => []]]);
233
234
        $this->assertTrue($this->listMapper->has('_action'));
235
236
        $fieldDescription = $this->listMapper->get('_action');
237
238
        $this->assertInstanceOf(FieldDescriptionInterface::class, $fieldDescription);
239
        $this->assertSame('_action', $fieldDescription->getName());
240
        $this->assertCount(1, $fieldDescription->getOption('actions'));
241
        $this->assertSame(['show' => []], $fieldDescription->getOption('actions'));
242
    }
243
244
    public function testAddRemove(): void
245
    {
246
        $this->assertFalse($this->listMapper->has('fooName'));
247
248
        $fieldDescription = $this->getFieldDescriptionMock('fooName', 'fooLabel');
249
250
        $this->listMapper->add($fieldDescription);
251
        $this->assertTrue($this->listMapper->has('fooName'));
252
253
        $this->listMapper->remove('fooName');
254
        $this->assertFalse($this->listMapper->has('fooName'));
255
    }
256
257
    public function testAddDuplicateNameException(): void
258
    {
259
        $tmpNames = [];
260
        $this->admin
0 ignored issues
show
Bug introduced by
The method method() does not seem to exist on object<Sonata\AdminBundle\Admin\AdminInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
261
            ->method('hasListFieldDescription')
262
            ->willReturnCallback(static function (string $name) use (&$tmpNames): bool {
263
                if (isset($tmpNames[$name])) {
264
                    return true;
265
                }
266
                $tmpNames[$name] = $name;
267
268
                return false;
269
            });
270
271
        $this->expectException(\LogicException::class);
272
        $this->expectExceptionMessage('Duplicate field name "fooName" in list mapper. Names should be unique.');
273
274
        $this->listMapper->add('fooName');
275
        $this->listMapper->add('fooName');
276
    }
277
278
    public function testAddWrongTypeException(): void
279
    {
280
        $this->expectException(\TypeError::class);
281
        $this->expectExceptionMessage(
282
            'Unknown field name in list mapper. Field name should be either of FieldDescriptionInterface interface or string.'
283
        );
284
285
        $this->listMapper->add(12345);
286
    }
287
288
    public function testAutoAddVirtualOption(): void
289
    {
290
        foreach (['actions', 'batch', 'select'] as $type) {
291
            $this->listMapper->add('_'.$type, $type);
292
        }
293
294
        foreach ($this->fieldDescriptionCollection->getElements() as $field) {
295
            $this->assertTrue(
296
                $field->isVirtual(),
297
                'Failed asserting that FieldDescription with type "'.$field->getType().'" is tagged with virtual flag.'
298
            );
299
        }
300
    }
301
302
    public function testAutoSortOnAssociatedProperty(): void
303
    {
304
        $this->listMapper->add('fooName');
305
        $this->listMapper->add(
306
            'fooNameAutoSort',
307
            null,
308
            [
309
                'associated_property' => 'fooAssociatedProperty',
310
            ]
311
        );
312
        $this->listMapper->add(
313
            'fooNameManualSort',
314
            null,
315
            [
316
                'associated_property' => 'fooAssociatedProperty',
317
                'sortable' => false,
318
                'sort_parent_association_mappings' => 'fooSortParentAssociationMapping',
319
                'sort_field_mapping' => 'fooSortFieldMapping',
320
            ]
321
        );
322
323
        $field = $this->listMapper->get('fooName');
324
        $fieldAutoSort = $this->listMapper->get('fooNameAutoSort');
325
        $fieldManualSort = $this->listMapper->get('fooNameManualSort');
326
327
        $this->assertNull($field->getOption('associated_property'));
328
        $this->assertNull($field->getOption('sortable'));
329
        $this->assertNull($field->getOption('sort_parent_association_mappings'));
330
        $this->assertNull($field->getOption('sort_field_mapping'));
331
332
        $this->assertSame('fooAssociatedProperty', $fieldAutoSort->getOption('associated_property'));
333
        $this->assertTrue($fieldAutoSort->getOption('sortable'));
334
        $this->assertSame([['fieldName' => $fieldAutoSort->getName()]], $fieldAutoSort->getOption('sort_parent_association_mappings'));
335
        $this->assertSame(['fieldName' => $fieldAutoSort->getOption('associated_property')], $fieldAutoSort->getOption('sort_field_mapping'));
336
337
        $this->assertSame('fooAssociatedProperty', $fieldManualSort->getOption('associated_property'));
338
        $this->assertFalse($fieldManualSort->getOption('sortable'));
339
        $this->assertSame('fooSortParentAssociationMapping', $fieldManualSort->getOption('sort_parent_association_mappings'));
340
        $this->assertSame('fooSortFieldMapping', $fieldManualSort->getOption('sort_field_mapping'));
341
    }
342
343
    public function testKeys(): void
344
    {
345
        $fieldDescription1 = $this->getFieldDescriptionMock('fooName1', 'fooLabel1');
346
        $fieldDescription2 = $this->getFieldDescriptionMock('fooName2', 'fooLabel2');
347
348
        $this->listMapper->add($fieldDescription1);
349
        $this->listMapper->add($fieldDescription2);
350
351
        $this->assertSame(['fooName1', 'fooName2'], $this->listMapper->keys());
352
    }
353
354
    public function testReorder(): void
355
    {
356
        $fieldDescription1 = $this->getFieldDescriptionMock('fooName1', 'fooLabel1');
357
        $fieldDescription2 = $this->getFieldDescriptionMock('fooName2', 'fooLabel2');
358
        $fieldDescription3 = $this->getFieldDescriptionMock('fooName3', 'fooLabel3');
359
        $fieldDescription4 = $this->getFieldDescriptionMock('fooName4', 'fooLabel4');
360
361
        $this->listMapper->add($fieldDescription1);
362
        $this->listMapper->add($fieldDescription2);
363
        $this->listMapper->add($fieldDescription3);
364
        $this->listMapper->add($fieldDescription4);
365
366
        $this->assertSame([
367
            'fooName1' => $fieldDescription1,
368
            'fooName2' => $fieldDescription2,
369
            'fooName3' => $fieldDescription3,
370
            'fooName4' => $fieldDescription4,
371
        ], $this->fieldDescriptionCollection->getElements());
372
373
        $this->listMapper->reorder(['fooName3', 'fooName2', 'fooName1', 'fooName4']);
374
375
        // print_r is used to compare order of items in associative arrays
376
        $this->assertSame(print_r([
377
            'fooName3' => $fieldDescription3,
378
            'fooName2' => $fieldDescription2,
379
            'fooName1' => $fieldDescription1,
380
            'fooName4' => $fieldDescription4,
381
        ], true), print_r($this->fieldDescriptionCollection->getElements(), true));
382
    }
383
384
    public function testAddOptionRole(): void
385
    {
386
        $this->listMapper->add('bar', 'bar');
387
388
        $this->assertTrue($this->listMapper->has('bar'));
389
390
        $this->listMapper->add('quux', 'bar', ['role' => 'ROLE_QUX']);
391
392
        $this->assertTrue($this->listMapper->has('bar'));
393
        $this->assertFalse($this->listMapper->has('quux'));
394
395
        $this->listMapper
396
            ->add('foobar', 'bar', ['role' => self::DEFAULT_GRANTED_ROLE])
397
            ->add('foo', 'bar', ['role' => 'ROLE_QUX'])
398
            ->add('baz', 'bar');
399
400
        $this->assertTrue($this->listMapper->has('foobar'));
401
        $this->assertFalse($this->listMapper->has('foo'));
402
        $this->assertTrue($this->listMapper->has('baz'));
403
    }
404
405
    public function testTypeGuessActionField(): void
406
    {
407
        $this->listMapper->add('_action', null);
408
409
        $field = $this->fieldDescriptionCollection->get('_action');
410
411
        $this->assertTrue(
412
            $field->isVirtual(),
413
            'Failed asserting that FieldDescription with name "'.$field->getName().'" is tagged with virtual flag.'
414
        );
415
    }
416
417
    private function getFieldDescriptionMock(?string $name = null, ?string $label = null): BaseFieldDescription
418
    {
419
        $fieldDescription = $this->getMockForAbstractClass(BaseFieldDescription::class);
420
421
        if (null !== $name) {
422
            $fieldDescription->setName($name);
423
        }
424
425
        if (null !== $label) {
426
            $fieldDescription->setOption('label', $label);
427
        }
428
429
        return $fieldDescription;
430
    }
431
}
432