FluentExtensionTest   A
last analyzed

Complexity

Total Complexity 12

Size/Duplication

Total Lines 414
Duplicated Lines 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
eloc 216
c 4
b 1
f 0
dl 0
loc 414
rs 10
wmc 12

11 Methods

Rating   Name   Duplication   Size   Complexity  
A testWritesToCurrentLocale() 0 30 1
A testGetLocalisedTable() 0 9 1
A testGetLinkingMode() 0 12 1
A testGetLocalisedFields() 0 45 1
A setUp() 0 5 1
A testLocalisedMixSorting() 0 29 1
A hasLocalisedRecord() 0 12 1
A testLocalisedFieldsCanBeSorted() 0 8 1
B sortRecordProvider() 0 108 1
A testLocalisedCopyClassNameChange() 0 72 2
A testFluentLocaleAndFrontendAreAddedToDataQuery() 0 10 1
1
<?php
2
3
namespace TractorCow\Fluent\Tests\Extension;
4
5
use Page;
0 ignored issues
show
Bug introduced by
The type Page was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use SilverStripe\CMS\Model\SiteTree;
0 ignored issues
show
Bug introduced by
The type SilverStripe\CMS\Model\SiteTree was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use SilverStripe\Dev\SapphireTest;
8
use SilverStripe\ORM\DataObject;
9
use SilverStripe\ORM\Queries\SQLSelect;
10
use SilverStripe\ORM\ValidationException;
11
use TractorCow\Fluent\Extension\FluentSiteTreeExtension;
12
use TractorCow\Fluent\Model\Locale;
13
use TractorCow\Fluent\State\FluentState;
14
use TractorCow\Fluent\Tests\Extension\FluentExtensionTest\LocalisedAnother;
15
use TractorCow\Fluent\Tests\Extension\FluentExtensionTest\LocalisedChild;
16
use TractorCow\Fluent\Tests\Extension\FluentExtensionTest\LocalisedParent;
17
use TractorCow\Fluent\Tests\Extension\FluentExtensionTest\MixedLocalisedSortObject;
18
use TractorCow\Fluent\Tests\Extension\FluentExtensionTest\TestModel;
19
use TractorCow\Fluent\Tests\Extension\FluentExtensionTest\TestRelationPage;
20
use TractorCow\Fluent\Tests\Extension\FluentExtensionTest\UnlocalisedChild;
21
use TractorCow\Fluent\Tests\Extension\Stub\FluentStubObject;
22
23
class FluentExtensionTest extends SapphireTest
24
{
25
    protected static $fixture_file = 'FluentExtensionTest.yml';
26
27
    protected static $extra_dataobjects = [
28
        LocalisedAnother::class,
29
        LocalisedChild::class,
30
        LocalisedParent::class,
31
        MixedLocalisedSortObject::class,
32
        UnlocalisedChild::class,
33
        TestRelationPage::class,
34
        TestModel::class,
35
    ];
36
37
    protected static $required_extensions = [
38
        SiteTree::class => [
39
            FluentSiteTreeExtension::class,
40
        ],
41
    ];
42
43
    protected function setUp(): void
44
    {
45
        parent::setUp();
46
47
        Locale::clearCached();
48
    }
49
50
    public function testFluentLocaleAndFrontendAreAddedToDataQuery()
51
    {
52
        FluentState::singleton()->withState(function (FluentState $newState) {
53
            $newState
54
                ->setLocale('test')
55
                ->setIsFrontend(true);
56
57
            $query = SiteTree::get()->dataQuery();
58
            $this->assertSame('test', $query->getQueryParam('Fluent.Locale'));
59
            $this->assertTrue($query->getQueryParam('Fluent.IsFrontend'));
60
        });
61
    }
62
63
    public function testGetLocalisedTable()
64
    {
65
        /** @var SiteTree|FluentSiteTreeExtension $page */
66
        $page = new SiteTree;
67
        $this->assertSame('SiteTree_Localised', $page->getLocalisedTable('SiteTree'));
68
        $this->assertSame(
69
            'SiteTree_Localised_FR',
70
            $page->getLocalisedTable('SiteTree', 'FR'),
71
            'Table aliases can be generated with getLocalisedTable()'
72
        );
73
    }
74
75
    public function testGetLinkingMode()
76
    {
77
        // Does not have a canViewInLocale method, locale is not current
78
        FluentState::singleton()->withState(function (FluentState $newState) {
79
            $newState->setLocale('en_US');
80
            $stub = new FluentStubObject();
81
            $this->assertSame('current', $stub->LocaleInformation('en_US')->getLinkingMode());
0 ignored issues
show
Bug introduced by
The method LocaleInformation() does not exist on TractorCow\Fluent\Tests\...n\Stub\FluentStubObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

81
            $this->assertSame('current', $stub->/** @scrutinizer ignore-call */ LocaleInformation('en_US')->getLinkingMode());
Loading history...
82
            $this->assertSame('link', $stub->LocaleInformation('de_DE')->getLinkingMode());
83
84
            $newState->setLocale('de_DE');
85
            $this->assertSame('link', $stub->LocaleInformation('en_US')->getLinkingMode());
86
            $this->assertSame('current', $stub->LocaleInformation('de_DE')->getLinkingMode());
87
        });
88
    }
89
90
    public function testGetLocalisedFields()
91
    {
92
        // test data_include / data_exclude
93
        // note: These parent fields should be all accessible from the child records as well
94
        $parent = new LocalisedParent();
95
        $parentLocalised = [
96
            'Title' => 'Varchar',
97
            'Details' => 'Varchar(200)',
98
        ];
99
        $this->assertEquals(
100
            $parentLocalised,
101
            $parent->getLocalisedFields()
0 ignored issues
show
Bug introduced by
The method getLocalisedFields() does not exist on TractorCow\Fluent\Tests\...ionTest\LocalisedParent. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

101
            $parent->/** @scrutinizer ignore-call */ 
102
                     getLocalisedFields()
Loading history...
102
        );
103
104
        // test field_include / field_exclude
105
        $another = new LocalisedAnother();
106
        $this->assertEquals(
107
            [
108
                'Bastion' => 'Varchar',
109
                'Data' => 'Varchar(100)',
110
            ],
111
            $another->getLocalisedFields()
0 ignored issues
show
Bug introduced by
The method getLocalisedFields() does not exist on TractorCow\Fluent\Tests\...onTest\LocalisedAnother. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

111
            $another->/** @scrutinizer ignore-call */ 
112
                      getLocalisedFields()
Loading history...
112
        );
113
        $this->assertEquals(
114
            $parentLocalised,
115
            $another->getLocalisedFields(LocalisedParent::class)
116
        );
117
118
        // Test translate directly
119
        $child = new LocalisedChild();
120
        $this->assertEquals(
121
            [ 'Record' => 'Text' ],
122
            $child->getLocalisedFields()
0 ignored issues
show
Bug introduced by
The method getLocalisedFields() does not exist on TractorCow\Fluent\Tests\...sionTest\LocalisedChild. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

122
            $child->/** @scrutinizer ignore-call */ 
123
                    getLocalisedFields()
Loading history...
123
        );
124
        $this->assertEquals(
125
            $parentLocalised,
126
            $child->getLocalisedFields(LocalisedParent::class)
127
        );
128
129
        // Test 'none'
130
        $unlocalised = new UnlocalisedChild();
131
        $this->assertEmpty($unlocalised->getLocalisedFields());
0 ignored issues
show
Bug introduced by
The method getLocalisedFields() does not exist on TractorCow\Fluent\Tests\...onTest\UnlocalisedChild. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

131
        $this->assertEmpty($unlocalised->/** @scrutinizer ignore-call */ getLocalisedFields());
Loading history...
132
        $this->assertEquals(
133
            $parentLocalised,
134
            $unlocalised->getLocalisedFields(LocalisedParent::class)
135
        );
136
    }
137
138
    public function testWritesToCurrentLocale()
139
    {
140
        FluentState::singleton()->withState(function (FluentState $newState) {
141
            $newState->setLocale('en_US');
142
143
            $record = $this->objFromFixture(LocalisedParent::class, 'record_a');
144
            $this->assertTrue(
145
                $this->hasLocalisedRecord($record, 'en_US'),
146
                'Record can be read from default locale'
147
            );
148
        });
149
150
        FluentState::singleton()->withState(function (FluentState $newState) {
151
            $newState->setLocale('de_DE');
152
153
            $record2 = $this->objFromFixture(LocalisedParent::class, 'record_a');
154
            $this->assertTrue(
155
                $this->hasLocalisedRecord($record2, 'de_DE'),
156
                'Existing record can be read from German locale'
157
            );
158
159
            $newState->setLocale('es_ES');
160
161
            $record2->Title = 'Un archivo';
162
            $record2->write();
163
164
            $record3 = $this->objFromFixture(LocalisedParent::class, 'record_a');
165
            $this->assertTrue(
166
                $this->hasLocalisedRecord($record3, 'es_ES'),
167
                'Record Locale is set to current locale when writing new records'
168
            );
169
        });
170
    }
171
172
    public function testLocalisedMixSorting()
173
    {
174
        FluentState::singleton()->withState(function (FluentState $newState) {
175
            $newState->setLocale('en_US');
176
177
            // Sort by the NonLocalisedSort field first then the LocalisedField second both in ascending order
178
            // so the result will be opposite if the order of the columns is not maintained
179
            $objects=MixedLocalisedSortObject::get()->sort(
180
                '"FluentExtensionTest_MixedLocalisedSortObject"."LocalizedSort", '.
181
                '"FluentExtensionTest_MixedLocalisedSortObject"."NonLocalizedSort", '.
182
                '"FluentExtensionTest_MixedLocalisedSortObject"."Title"'
183
            );
184
185
            // Make sure Item A is first
186
            $this->assertEquals(
187
                'Item A',
188
                $objects->offsetGet(0)->Title
189
            );
190
191
            // Make sure Item B is second
192
            $this->assertEquals(
193
                'Item B',
194
                $objects->offsetGet(1)->Title
195
            );
196
197
            // Make sure Item C is third
198
            $this->assertEquals(
199
                'Item C',
200
                $objects->offsetGet(2)->Title
201
            );
202
        });
203
    }
204
205
    /**
206
     * @throws ValidationException
207
     */
208
    public function testLocalisedCopyClassNameChange(): void
209
    {
210
        // Setup first localisation
211
        $pageId = FluentState::singleton()->withState(function (FluentState $state): int {
212
            $state->setLocale('en_US');
213
214
            /** @var Page|FluentSiteTreeExtension $page */
215
            $page = Page::create();
216
            $page->Title = 'Localised copy test page';
0 ignored issues
show
Bug introduced by
The property Title does not seem to exist on TractorCow\Fluent\Extens...FluentSiteTreeExtension.
Loading history...
217
            $page->URLSegment = 'localised-copy-test-page';
0 ignored issues
show
Bug introduced by
The property URLSegment does not seem to exist on TractorCow\Fluent\Extens...FluentSiteTreeExtension.
Loading history...
218
219
            return $page->write();
0 ignored issues
show
Bug introduced by
The method write() does not exist on TractorCow\Fluent\Extens...FluentSiteTreeExtension. ( Ignorable by Annotation )

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

219
            return $page->/** @scrutinizer ignore-call */ write();

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...
220
        });
221
222
        // Setup second localisation and make sure we have two localisations to work with
223
        FluentState::singleton()->withState(function (FluentState $state) use ($pageId): void {
224
            $state->setLocale('de_DE');
225
226
            /** @var Page|FluentSiteTreeExtension $page */
227
            $page = Page::get()->byID($pageId);
228
            $page->write();
229
            $locales = [];
230
231
            /** @var Locale $locale */
232
            foreach ($page->getLocaleInstances() as $locale) {
233
                $locales[] = $locale->Locale;
234
            }
235
236
            sort($locales, SORT_STRING);
237
238
            $this->assertEquals([
239
                'de_DE',
240
                'en_US',
241
            ], $locales);
242
        });
243
244
        // Change the ClassName of the first localisation, attach related model
245
        $modelId = FluentState::singleton()->withState(function (FluentState $state) use ($pageId): int {
246
            $state->setLocale('en_US');
247
248
            $model = TestModel::create();
249
            $model->Title = 'Test model';
250
            $model->write();
251
252
            /** @var Page|FluentSiteTreeExtension $page */
253
            $page = Page::get()->byID($pageId);
254
255
            /** @var TestRelationPage|FluentSiteTreeExtension $page */
256
            $page = $page->newClassInstance(TestRelationPage::class);
0 ignored issues
show
Bug introduced by
The method newClassInstance() does not exist on TractorCow\Fluent\Extens...FluentSiteTreeExtension. ( Ignorable by Annotation )

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

256
            /** @scrutinizer ignore-call */ 
257
            $page = $page->newClassInstance(TestRelationPage::class);

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...
257
            $page->TestRelationID = $model->ID;
0 ignored issues
show
Bug introduced by
The property TestRelationID does not seem to exist on TractorCow\Fluent\Extens...FluentSiteTreeExtension.
Loading history...
258
            $page->write();
259
260
            /** @var TestRelationPage|FluentSiteTreeExtension $page */
261
            $page = TestRelationPage::get()->byID($pageId);
262
263
            // Make sure that the relation remains intact
264
            $this->assertEquals($page->TestRelationID, $model->ID);
265
266
            return $model->ID;
267
        });
268
269
        // We expect the second localisation to also change class and have a copy of the related model
270
        FluentState::singleton()->withState(function (FluentState $state) use ($pageId, $modelId): void {
271
            $state->setLocale('de_DE');
272
273
            /** @var TestRelationPage|FluentSiteTreeExtension $page */
274
            $page = TestRelationPage::get()->byID($pageId);
275
276
            $this->assertGreaterThan(0, $page->TestRelationID);
0 ignored issues
show
Bug introduced by
The property TestRelationID does not seem to exist on TractorCow\Fluent\Extens...FluentSiteTreeExtension.
Loading history...
277
            $this->assertNotEquals($modelId, $page->TestRelationID);
278
            $this->assertTrue($page->TestRelation()->exists());
0 ignored issues
show
Bug introduced by
The method TestRelation() does not exist on TractorCow\Fluent\Extens...FluentSiteTreeExtension. ( Ignorable by Annotation )

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

278
            $this->assertTrue($page->/** @scrutinizer ignore-call */ TestRelation()->exists());

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...
279
            $this->assertEquals('Test model', $page->TestRelation()->Title);
280
        });
281
    }
282
283
    /**
284
     * Ensure that records can be sorted in their locales
285
     *
286
     * @dataProvider sortRecordProvider
287
     * @param string $locale
288
     * @param string[] $sortArgs
289
     * @param string[] $expected
290
     * @group exclude-from-travis
291
     */
292
    public function testLocalisedFieldsCanBeSorted($locale, array $sortArgs, $expected)
293
    {
294
        FluentState::singleton()->withState(function (FluentState $newState) use ($locale, $sortArgs, $expected) {
295
            $newState->setLocale($locale);
296
297
            $records = LocalisedParent::get()->sort(...$sortArgs);
298
            $titles = $records->column('Title');
299
            $this->assertEquals($expected, $titles);
300
        });
301
    }
302
303
    /**
304
     * @return array[] Keys: Locale, sorting arguments, expected titles in result
305
     */
306
    public function sortRecordProvider()
307
    {
308
        return [
309
            /**
310
             * Single field (non-composite) sorting syntax (either string or array syntax)
311
             *
312
             * E.g. `->sort('"foo"')`, `->sort('Title', 'DESC')` etc
313
             */
314
            'german ascending single sort' => [
315
                'de_DE',
316
                ['Title', 'ASC'],
317
                ['Eine Akte', 'Lesen Sie mehr', 'Rennen'],
318
            ],
319
            'german descending single sort' => [
320
                'de_DE',
321
                ['"Title" DESC'],
322
                ['Rennen', 'Lesen Sie mehr', 'Eine Akte'],
323
            ],
324
            'english ascending single sort' => [
325
                'en_US',
326
                ['"Title" ASC'],
327
                ['A record', 'Go for a run', 'Read about things'],
328
            ],
329
            'english descending single sort' => [
330
                'en_US',
331
                ['Title', 'DESC'],
332
                ['Read about things', 'Go for a run', 'A record'],
333
            ],
334
            'english ascending on unlocalised field' => [
335
                'en_US',
336
                ['"Description"'],
337
                ['Read about things', 'Go for a run', 'A record'],
338
            ],
339
            'english descending on unlocalised field' => [
340
                'en_US',
341
                ['"Description" DESC'],
342
                ['A record', 'Read about things', 'Go for a run'],
343
            ],
344
            'german ascending on unlocalised field' => [
345
                'de_DE',
346
                ['"Description"'],
347
                ['Lesen Sie mehr', 'Rennen', 'Eine Akte'],
348
            ],
349
            'german descending on unlocalised field' => [
350
                'de_DE',
351
                ['"Description" DESC'],
352
                ['Eine Akte', 'Lesen Sie mehr', 'Rennen'],
353
            ],
354
            /**
355
             * Composite sorting tests (either string syntax or array syntax)
356
             *
357
             * E.g. `->sort(['foo' => 'ASC', 'bar' => 'DESC'])`
358
             */
359
            'english composite sort, string' => [
360
                'en_US',
361
                ['"Details" ASC, "Title" ASC'],
362
                ['Go for a run', 'A record', 'Read about things']
363
            ],
364
            'german composite sort, string' => [
365
                'de_DE',
366
                ['"Details" ASC, "Title" ASC'],
367
                ['Rennen', 'Eine Akte', 'Lesen Sie mehr'],
368
            ],
369
            'english, composite sort, array' => [
370
                'en_US',
371
                [[
372
                    'Details' => 'ASC',
373
                    'Title' => 'ASC'
374
                ]],
375
                ['Go for a run', 'A record', 'Read about things'],
376
            ],
377
            'german, composite sort, array' => [
378
                'de_DE',
379
                [[
380
                    'Details' => 'ASC',
381
                    'Title' => 'ASC'
382
                ]],
383
                ['Rennen', 'Eine Akte', 'Lesen Sie mehr'],
384
            ],
385
            'german, composite sort, array (flipped)' => [
386
                'de_DE',
387
                [[
388
                    'Details' => 'ASC',
389
                    'Title' => 'DESC'
390
                ]],
391
                ['Rennen', 'Lesen Sie mehr', 'Eine Akte'],
392
            ],
393
            'english, composite sort, array (flipped)' => [
394
                'en_US',
395
                [[
396
                    'Details' => 'DESC',
397
                    'Title' => 'DESC'
398
                ]],
399
                ['Read about things', 'A record', 'Go for a run'],
400
            ],
401
            'german, composite sort, no directions' => [
402
                'de_DE',
403
                ['"Details", "Title"'],
404
                ['Rennen', 'Eine Akte', 'Lesen Sie mehr'],
405
            ],
406
            /**
407
             * Ignored types of sorting, e.g. subqueries. Ignored sorting should use the ORM default
408
             * and sort on whatever is in the base table.
409
             */
410
            'english, subquery sort' => [
411
                'en_US',
412
                ['CONCAT((SELECT COUNT(*) FROM "FluentExtensionTest_LocalisedParent_Localised"), "FluentExtensionTest_LocalisedParent"."ID")'],
413
                ['A record', 'Read about things', 'Go for a run'],
414
            ]
415
        ];
416
    }
417
418
    /**
419
     * Get a Locale field value directly from a record's localised database table, skipping the ORM
420
     *
421
     * @param DataObject $record
422
     * @param string $locale
423
     * @return boolean
424
     */
425
    protected function hasLocalisedRecord(DataObject $record, $locale)
426
    {
427
        $result = SQLSelect::create()
428
            ->setFrom('"' . $record->config()->get('table_name') . '_Localised"')
429
            ->setWhere([
430
                '"RecordID"' => $record->ID,
431
                '"Locale"' => $locale,
432
            ])
433
            ->execute()
434
            ->first();
435
436
        return !empty($result);
437
    }
438
}
439