Passed
Pull Request — 4 (#10307)
by
unknown
06:47
created

DataListTest::testRemoveAll()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
eloc 4
c 1
b 1
f 0
nc 1
nop 0
dl 0
loc 6
rs 10
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use InvalidArgumentException;
6
use SilverStripe\Core\Convert;
7
use SilverStripe\Core\Injector\InjectorNotFoundException;
8
use SilverStripe\Dev\SapphireTest;
9
use SilverStripe\ORM\DataList;
10
use SilverStripe\ORM\DataQuery;
11
use SilverStripe\ORM\DB;
12
use SilverStripe\ORM\Filterable;
13
use SilverStripe\ORM\Filters\ExactMatchFilter;
14
use SilverStripe\ORM\Tests\DataObjectTest\DataListQueryCounter;
15
use SilverStripe\ORM\Tests\DataObjectTest\Fixture;
16
use SilverStripe\ORM\Tests\DataObjectTest\Bracket;
17
use SilverStripe\ORM\Tests\DataObjectTest\EquipmentCompany;
18
use SilverStripe\ORM\Tests\DataObjectTest\Fan;
19
use SilverStripe\ORM\Tests\DataObjectTest\Player;
20
use SilverStripe\ORM\Tests\DataObjectTest\Sortable;
21
use SilverStripe\ORM\Tests\DataObjectTest\Staff;
22
use SilverStripe\ORM\Tests\DataObjectTest\SubTeam;
23
use SilverStripe\ORM\Tests\DataObjectTest\Team;
24
use SilverStripe\ORM\Tests\DataObjectTest\TeamComment;
25
use SilverStripe\ORM\Tests\DataObjectTest\ValidatedObject;
26
use SilverStripe\ORM\Tests\ManyManyListTest\Category;
27
28
/**
29
 * @skipUpgrade
30
 */
31
class DataListTest extends SapphireTest
32
{
33
34
    // Borrow the model from DataObjectTest
35
    protected static $fixture_file = 'DataObjectTest.yml';
36
37
    public static function getExtraDataObjects()
38
    {
39
        return array_merge(
40
            DataObjectTest::$extra_data_objects,
41
            ManyManyListTest::$extra_data_objects
42
        );
43
    }
44
45
46
    public function testFilterDataObjectByCreatedDate()
47
    {
48
        // create an object to test with
49
        $obj1 = new ValidatedObject();
50
        $obj1->Name = 'test obj 1';
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\Tests\D...ectTest\ValidatedObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
51
        $obj1->write();
52
        $this->assertTrue($obj1->isInDB());
53
54
        // reload the object from the database and reset its Created timestamp to a known value
55
        $obj1 = ValidatedObject::get()->filter(['Name' => 'test obj 1'])->first();
56
        $this->assertTrue(is_object($obj1));
57
        $this->assertEquals('test obj 1', $obj1->Name);
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
58
        $obj1->Created = '2013-01-01 00:00:00';
59
        $obj1->write();
60
61
        // reload the object again and make sure that our Created date was properly persisted
62
        $obj1 = ValidatedObject::get()->filter(['Name' => 'test obj 1'])->first();
63
        $this->assertTrue(is_object($obj1));
64
        $this->assertEquals('test obj 1', $obj1->Name);
65
        $this->assertEquals('2013-01-01 00:00:00', $obj1->Created);
66
67
        // now save a second object to the DB with an automatically-set Created value
68
        $obj2 = new ValidatedObject();
69
        $obj2->Name = 'test obj 2';
70
        $obj2->write();
71
        $this->assertTrue($obj2->isInDB());
72
73
        // and a third object
74
        $obj3 = new ValidatedObject();
75
        $obj3->Name = 'test obj 3';
76
        $obj3->write();
77
        $this->assertTrue($obj3->isInDB());
78
79
        // now test the filtering based on Created timestamp
80
        $list = ValidatedObject::get()
81
            ->filter(['Created:GreaterThan' => '2013-02-01 00:00:00'])
82
            ->toArray();
83
        $this->assertEquals(2, count($list ?? []));
84
    }
85
86
    public function testSubtract()
87
    {
88
        $comment1 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1');
89
        $subtractList = TeamComment::get()->filter('ID', $comment1->ID);
90
        $fullList = TeamComment::get();
91
        $newList = $fullList->subtract($subtractList);
92
        $this->assertEquals(2, $newList->Count(), 'List should only contain two objects after subtraction');
93
    }
94
95
    public function testSubtractBadDataclassThrowsException()
96
    {
97
        $this->expectException(InvalidArgumentException::class);
98
        $teamsComments = TeamComment::get();
99
        $teams = Team::get();
100
        $teamsComments->subtract($teams);
101
    }
102
103
    public function testListCreationSortAndLimit()
104
    {
105
        // By default, a DataList will contain all items of that class
106
        $list = TeamComment::get()->sort('ID');
107
108
        // We can iterate on the DataList
109
        $names = [];
110
        foreach ($list as $item) {
111
            $names[] = $item->Name;
112
        }
113
        $this->assertEquals(['Joe', 'Bob', 'Phil'], $names);
114
115
        // If we don't want to iterate, we can extract a single column from the list with column()
116
        $this->assertEquals(['Joe', 'Bob', 'Phil'], $list->column('Name'));
117
118
        // We can sort a list
119
        $list = $list->sort('Name');
120
        $this->assertEquals(['Bob', 'Joe', 'Phil'], $list->column('Name'));
121
122
        // We can also restrict the output to a range
123
        $this->assertEquals(['Joe', 'Phil'], $list->limit(2, 1)->column('Name'));
124
    }
125
126
    public function testLimitAndOffset()
127
    {
128
        $list = TeamComment::get();
129
        $check = $list->limit(3);
130
131
        $this->assertEquals(3, $check->count());
132
133
        $check = $list->limit(1);
134
        $this->assertEquals(1, $check->count());
135
136
        $check = $list->limit(1, 1);
137
        $this->assertEquals(1, $check->count());
138
139
        $check = $list->limit(false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer expected by parameter $limit of SilverStripe\ORM\DataList::limit(). ( Ignorable by Annotation )

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

139
        $check = $list->limit(/** @scrutinizer ignore-type */ false);
Loading history...
140
        $this->assertEquals(3, $check->count());
141
142
        $check = $list->limit(null);
143
        $this->assertEquals(3, $check->count());
144
145
        $check = $list->limit(null, 2);
146
        $this->assertEquals(1, $check->count());
147
148
        // count()/first()/last() methods may alter limit/offset, so run the query and manually check the count
149
        $check = $list->limit(null, 1)->toArray();
150
        $this->assertEquals(2, count($check ?? []));
151
    }
152
153
    public function testDistinct()
154
    {
155
        $list = TeamComment::get();
156
        $this->assertStringContainsString('SELECT DISTINCT', $list->dataQuery()->sql($params), 'Query is set as distinct by default');
157
158
        $list = $list->distinct(false);
159
        $this->assertStringNotContainsString('SELECT DISTINCT', $list->dataQuery()->sql($params), 'Query does not contain distinct');
160
161
        $list = $list->distinct(true);
162
        $this->assertStringContainsString('SELECT DISTINCT', $list->dataQuery()->sql($params), 'Query contains distinct');
163
    }
164
165
    public function testDataClass()
166
    {
167
        $list = TeamComment::get();
168
        $this->assertEquals(DataObjectTest\TeamComment::class, $list->dataClass());
169
    }
170
171
    public function testDataClassCaseInsensitive()
172
    {
173
        $list = DataList::create(strtolower(DataObjectTest\TeamComment::class));
174
        $this->assertTrue($list->exists());
175
    }
176
177
    public function testClone()
178
    {
179
        $list = TeamComment::get();
180
        $this->assertEquals($list, clone($list));
181
    }
182
183
    public function testSql()
184
    {
185
        $db = DB::get_conn();
186
        $list = TeamComment::get();
187
        $expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", '
188
            . '"DataObjectTest_TeamComment"."LastEdited", "DataObjectTest_TeamComment"."Created", '
189
            . '"DataObjectTest_TeamComment"."Name", "DataObjectTest_TeamComment"."Comment", '
190
            . '"DataObjectTest_TeamComment"."TeamID", "DataObjectTest_TeamComment"."ID", '
191
            . 'CASE WHEN "DataObjectTest_TeamComment"."ClassName" IS NOT NULL '
192
            . 'THEN "DataObjectTest_TeamComment"."ClassName" ELSE '
193
            . $db->quoteString(DataObjectTest\TeamComment::class)
194
            . ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment"'
195
            . ' ORDER BY "DataObjectTest_TeamComment"."Name" ASC';
196
        $this->assertSQLEquals($expected, $list->sql($parameters));
197
    }
198
199
    public function testInnerJoin()
200
    {
201
        $db = DB::get_conn();
202
203
        $list = TeamComment::get();
204
        $list = $list->innerJoin(
205
            'DataObjectTest_Team',
206
            '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"',
207
            'Team'
208
        );
209
210
        $expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", '
211
            . '"DataObjectTest_TeamComment"."LastEdited", "DataObjectTest_TeamComment"."Created", '
212
            . '"DataObjectTest_TeamComment"."Name", "DataObjectTest_TeamComment"."Comment", '
213
            . '"DataObjectTest_TeamComment"."TeamID", "DataObjectTest_TeamComment"."ID", '
214
            . 'CASE WHEN "DataObjectTest_TeamComment"."ClassName" IS NOT NULL'
215
            . ' THEN "DataObjectTest_TeamComment"."ClassName" ELSE '
216
            . $db->quoteString(DataObjectTest\TeamComment::class)
217
            . ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment" INNER JOIN '
218
            . '"DataObjectTest_Team" AS "Team" ON "DataObjectTest_Team"."ID" = '
219
            . '"DataObjectTest_TeamComment"."TeamID"'
220
            . ' ORDER BY "DataObjectTest_TeamComment"."Name" ASC';
221
222
223
        $this->assertSQLEquals($expected, $list->sql($parameters));
224
        $this->assertEmpty($parameters);
225
    }
226
227
    public function testInnerJoinParameterised()
228
    {
229
        $db = DB::get_conn();
230
231
        $list = TeamComment::get();
232
        $list = $list->innerJoin(
233
            'DataObjectTest_Team',
234
            '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID" '
235
            . 'AND "DataObjectTest_Team"."Title" LIKE ?',
236
            'Team',
237
            20,
238
            ['Team%']
239
        );
240
241
        $expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", '
242
            . '"DataObjectTest_TeamComment"."LastEdited", "DataObjectTest_TeamComment"."Created", '
243
            . '"DataObjectTest_TeamComment"."Name", "DataObjectTest_TeamComment"."Comment", '
244
            . '"DataObjectTest_TeamComment"."TeamID", "DataObjectTest_TeamComment"."ID", '
245
            . 'CASE WHEN "DataObjectTest_TeamComment"."ClassName" IS NOT NULL'
246
            . ' THEN "DataObjectTest_TeamComment"."ClassName" ELSE '
247
            . $db->quoteString(DataObjectTest\TeamComment::class)
248
            . ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment" INNER JOIN '
249
            . '"DataObjectTest_Team" AS "Team" ON "DataObjectTest_Team"."ID" = '
250
            . '"DataObjectTest_TeamComment"."TeamID" '
251
            . 'AND "DataObjectTest_Team"."Title" LIKE ?'
252
            . ' ORDER BY "DataObjectTest_TeamComment"."Name" ASC';
253
254
        $this->assertSQLEquals($expected, $list->sql($parameters));
255
        $this->assertEquals(['Team%'], $parameters);
256
    }
257
258
    public function testLeftJoin()
259
    {
260
        $db = DB::get_conn();
261
262
        $list = TeamComment::get();
263
        $list = $list->leftJoin(
264
            'DataObjectTest_Team',
265
            '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"',
266
            'Team'
267
        );
268
269
        $expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", '
270
            . '"DataObjectTest_TeamComment"."LastEdited", "DataObjectTest_TeamComment"."Created", '
271
            . '"DataObjectTest_TeamComment"."Name", "DataObjectTest_TeamComment"."Comment", '
272
            . '"DataObjectTest_TeamComment"."TeamID", "DataObjectTest_TeamComment"."ID", '
273
            . 'CASE WHEN "DataObjectTest_TeamComment"."ClassName" IS NOT NULL '
274
            . 'THEN "DataObjectTest_TeamComment"."ClassName" ELSE '
275
            . $db->quoteString(DataObjectTest\TeamComment::class)
276
            . ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment" LEFT JOIN "DataObjectTest_Team" '
277
            . 'AS "Team" ON "DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"'
278
            . ' ORDER BY "DataObjectTest_TeamComment"."Name" ASC';
279
280
281
        $this->assertSQLEquals($expected, $list->sql($parameters));
282
        $this->assertEmpty($parameters);
283
    }
284
285
    public function testLeftJoinParameterised()
286
    {
287
        $db = DB::get_conn();
288
289
        $list = TeamComment::get();
290
        $list = $list->leftJoin(
291
            'DataObjectTest_Team',
292
            '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID" '
293
            . 'AND "DataObjectTest_Team"."Title" LIKE ?',
294
            'Team',
295
            20,
296
            ['Team%']
297
        );
298
299
        $expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", '
300
            . '"DataObjectTest_TeamComment"."LastEdited", "DataObjectTest_TeamComment"."Created", '
301
            . '"DataObjectTest_TeamComment"."Name", "DataObjectTest_TeamComment"."Comment", '
302
            . '"DataObjectTest_TeamComment"."TeamID", "DataObjectTest_TeamComment"."ID", '
303
            . 'CASE WHEN "DataObjectTest_TeamComment"."ClassName" IS NOT NULL'
304
            . ' THEN "DataObjectTest_TeamComment"."ClassName" ELSE '
305
            . $db->quoteString(DataObjectTest\TeamComment::class)
306
            . ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment" LEFT JOIN '
307
            . '"DataObjectTest_Team" AS "Team" ON "DataObjectTest_Team"."ID" = '
308
            . '"DataObjectTest_TeamComment"."TeamID" '
309
            . 'AND "DataObjectTest_Team"."Title" LIKE ?'
310
            . ' ORDER BY "DataObjectTest_TeamComment"."Name" ASC';
311
312
        $this->assertSQLEquals($expected, $list->sql($parameters));
313
        $this->assertEquals(['Team%'], $parameters);
314
    }
315
316
    public function testToNestedArray()
317
    {
318
        $list = TeamComment::get()->sort('ID');
319
        $nestedArray = $list->toNestedArray();
320
        $expected = [
321
            0=>
322
            [
323
                'ClassName'=>DataObjectTest\TeamComment::class,
324
                'Name'=>'Joe',
325
                'Comment'=>'This is a team comment by Joe',
326
                'TeamID'=> $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1')->TeamID,
0 ignored issues
show
Bug Best Practice introduced by
The property TeamID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
327
            ],
328
            1=>
329
            [
330
                'ClassName'=>DataObjectTest\TeamComment::class,
331
                'Name'=>'Bob',
332
                'Comment'=>'This is a team comment by Bob',
333
                'TeamID'=> $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment2')->TeamID,
334
            ],
335
            2=>
336
            [
337
                'ClassName'=>DataObjectTest\TeamComment::class,
338
                'Name'=>'Phil',
339
                'Comment'=>'Phil is a unique guy, and comments on team2',
340
                'TeamID'=> $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment3')->TeamID,
341
            ],
342
        ];
343
        $this->assertEquals(3, count($nestedArray ?? []));
344
        $this->assertEquals($expected[0]['Name'], $nestedArray[0]['Name']);
345
        $this->assertEquals($expected[1]['Comment'], $nestedArray[1]['Comment']);
346
        $this->assertEquals($expected[2]['TeamID'], $nestedArray[2]['TeamID']);
347
    }
348
349
    public function testMap()
350
    {
351
        $map = TeamComment::get()->map()->toArray();
352
        $expected = [
353
            $this->idFromFixture(DataObjectTest\TeamComment::class, 'comment1') => 'Joe',
354
            $this->idFromFixture(DataObjectTest\TeamComment::class, 'comment2') => 'Bob',
355
            $this->idFromFixture(DataObjectTest\TeamComment::class, 'comment3') => 'Phil'
356
        ];
357
358
        $this->assertEquals($expected, $map);
359
        $otherMap = TeamComment::get()->map('Name', 'TeamID')->toArray();
360
        $otherExpected = [
361
            'Joe' => $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1')->TeamID,
0 ignored issues
show
Bug Best Practice introduced by
The property TeamID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
362
            'Bob' => $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment2')->TeamID,
363
            'Phil' => $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment3')->TeamID
364
        ];
365
366
        $this->assertEquals($otherExpected, $otherMap);
367
    }
368
369
    public function testAmbiguousAggregate()
370
    {
371
        // Test that we avoid ambiguity error when a field exists on two joined tables
372
        // Fetch the sponsors in a round-about way to simulate this
373
        $teamID = $this->idFromFixture(DataObjectTest\Team::class, 'team2');
374
        $sponsors = EquipmentCompany::get()->filter('SponsoredTeams.ID', $teamID);
375
        $this->assertNotNull($sponsors->Max('ID'));
376
        $this->assertNotNull($sponsors->Min('ID'));
377
        $this->assertNotNull($sponsors->Avg('ID'));
378
        $this->assertNotNull($sponsors->Sum('ID'));
379
380
        // Test non-orm many_many_extraFields
381
        $company = $this->objFromFixture(EquipmentCompany::class, 'equipmentcompany1');
382
        $this->assertNotNull($company->SponsoredTeams()->Max('SponsorFee'));
0 ignored issues
show
Bug introduced by
The method SponsoredTeams() does not exist on SilverStripe\ORM\DataObject. 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

382
        $this->assertNotNull($company->/** @scrutinizer ignore-call */ SponsoredTeams()->Max('SponsorFee'));
Loading history...
383
        $this->assertNotNull($company->SponsoredTeams()->Min('SponsorFee'));
384
        $this->assertNotNull($company->SponsoredTeams()->Avg('SponsorFee'));
385
        $this->assertNotNull($company->SponsoredTeams()->Sum('SponsorFee'));
386
    }
387
388
    public function testEach()
389
    {
390
        $list = TeamComment::get();
391
392
        $count = 0;
393
        $list->each(
394
            function ($item) use (&$count) {
395
                $count++;
396
                $this->assertInstanceOf(TeamComment::class, $item);
397
            }
398
        );
399
400
        $this->assertEquals($count, $list->count());
401
    }
402
403
    public function testWhere()
404
    {
405
        // We can use raw SQL queries with where.  This is only recommended for advanced uses;
406
        // if you can, you should use filter().
407
        $list = TeamComment::get();
408
409
        // where() returns a new DataList, like all the other modifiers, so it can be chained.
410
        $list2 = $list->where('"Name" = \'Joe\'');
411
        $this->assertEquals(['This is a team comment by Joe'], $list2->column('Comment'));
412
413
        // The where() clauses are chained together with AND
414
        $list3 = $list2->where('"Name" = \'Bob\'');
415
        $this->assertEquals([], $list3->column('Comment'));
416
    }
417
418
    /**
419
     * Test DataList->byID()
420
     */
421
    public function testByID()
422
    {
423
        // We can get a single item by ID.
424
        $id = $this->idFromFixture(DataObjectTest\Team::class, 'team2');
425
        $team = Team::get()->byID($id);
426
427
        // byID() returns a DataObject, rather than a DataList
428
        $this->assertInstanceOf(DataObjectTest\Team::class, $team);
429
        $this->assertEquals('Team 2', $team->Title);
430
431
        // Assert that filtering on ID searches by the base table, not the child table field
432
        $query = SubTeam::get()->filter('ID', 4)->sql($parameters);
433
        $this->assertStringContainsString('WHERE ("DataObjectTest_Team"."ID" = ?)', $query);
434
        $this->assertStringNotContainsString('WHERE ("DataObjectTest_SubTeam"."ID" = ?)', $query);
435
    }
436
437
    public function testByIDs()
438
    {
439
        $knownIDs = $this->allFixtureIDs(DataObjectTest\Player::class);
440
        $removedID = array_pop($knownIDs);
441
        $filteredPlayers = Player::get()->byIDs($knownIDs);
442
        foreach ($filteredPlayers as $player) {
443
            $this->assertContains($player->ID, $knownIDs);
444
            $this->assertNotEquals($removedID, $player->ID);
445
        }
446
    }
447
448
    /**
449
     * Test DataList->removeByID()
450
     */
451
    public function testRemoveByID()
452
    {
453
        $list = Team::get();
454
        $id = $this->idFromFixture(DataObjectTest\Team::class, 'team2');
455
456
        $this->assertNotNull($list->byID($id));
457
        $list->removeByID($id);
458
        $this->assertNull($list->byID($id));
459
    }
460
461
    /**
462
     * Test DataList->removeAll()
463
     */
464
    public function testRemoveAll()
465
    {
466
        $list = Team::get();
467
        $this->assertGreaterThan(0, $list->count());
468
        $list->removeAll();
469
        $this->assertCount(0, $list);
470
    }
471
472
    /**
473
     * Test DataList->canSortBy()
474
     */
475
    public function testCanSortBy()
476
    {
477
        // Basic check
478
        $team = Team::get();
479
        $this->assertTrue($team->canSortBy("Title"));
480
        $this->assertFalse($team->canSortBy("SomethingElse"));
481
482
        // Subclasses
483
        $subteam = SubTeam::get();
484
        $this->assertTrue($subteam->canSortBy("Title"));
485
        $this->assertTrue($subteam->canSortBy("SubclassDatabaseField"));
486
    }
487
488
    public function testDataListArrayAccess()
489
    {
490
        $list = Team::get()->sort('Title');
491
492
        // We can use array access to refer to single items in the DataList, as if it were an array
493
        $this->assertEquals("Subteam 1", $list[0]->Title);
494
        $this->assertEquals("Subteam 3", $list[2]->Title);
495
        $this->assertEquals("Team 2", $list[4]->Title);
496
    }
497
498
    public function testFind()
499
    {
500
        $list = Team::get();
501
        $record = $list->find('Title', 'Team 1');
502
        $this->assertEquals($this->idFromFixture(DataObjectTest\Team::class, 'team1'), $record->ID);
503
    }
504
505
    public function testFindById()
506
    {
507
        $list = Team::get();
508
        $record = $list->find('ID', $this->idFromFixture(DataObjectTest\Team::class, 'team1'));
509
        $this->assertEquals('Team 1', $record->Title);
510
        // Test that you can call it twice on the same list
511
        $record = $list->find('ID', $this->idFromFixture(DataObjectTest\Team::class, 'team2'));
512
        $this->assertEquals('Team 2', $record->Title);
513
    }
514
515
    public function testSimpleSort()
516
    {
517
        $list = TeamComment::get();
518
        $list = $list->sort('Name');
519
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
520
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
521
    }
522
523
    public function testSimpleSortOneArgumentASC()
524
    {
525
        $list = TeamComment::get();
526
        $list = $list->sort('Name ASC');
527
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
528
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
529
    }
530
531
    public function testSimpleSortOneArgumentDESC()
532
    {
533
        $list = TeamComment::get();
534
        $list = $list->sort('Name DESC');
535
        $this->assertEquals('Phil', $list->first()->Name, 'Last comment should be from Phil');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
536
        $this->assertEquals('Bob', $list->last()->Name, 'First comment should be from Bob');
537
    }
538
539
    public function testSortOneArgumentMultipleColumns()
540
    {
541
        $list = TeamComment::get();
542
        $list = $list->sort('TeamID ASC, Name DESC');
543
        $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
544
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
545
    }
546
547
    public function testSimpleSortASC()
548
    {
549
        $list = TeamComment::get();
550
        $list = $list->sort('Name', 'asc');
551
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
552
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
553
    }
554
555
    public function testSimpleSortDESC()
556
    {
557
        $list = TeamComment::get();
558
        $list = $list->sort('Name', 'desc');
559
        $this->assertEquals('Phil', $list->first()->Name, 'Last comment should be from Phil');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
560
        $this->assertEquals('Bob', $list->last()->Name, 'First comment should be from Bob');
561
    }
562
563
    public function testSortWithArraySyntaxSortASC()
564
    {
565
        $list = TeamComment::get();
566
        $list = $list->sort(['Name'=>'asc']);
567
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
568
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
569
    }
570
571
    public function testSortWithArraySyntaxSortDESC()
572
    {
573
        $list = TeamComment::get();
574
        $list = $list->sort(['Name'=>'desc']);
575
        $this->assertEquals('Phil', $list->first()->Name, 'Last comment should be from Phil');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
576
        $this->assertEquals('Bob', $list->last()->Name, 'First comment should be from Bob');
577
    }
578
579
    public function testSortWithMultipleArraySyntaxSort()
580
    {
581
        $list = TeamComment::get();
582
        $list = $list->sort(['TeamID'=>'asc','Name'=>'desc']);
583
        $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
584
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
585
    }
586
587
    public function testSortWithCompositeSyntax()
588
    {
589
        // Phil commented on team with founder surname "Aaron"
590
        $list = TeamComment::get();
591
        $list = $list->sort('Team.Founder.Surname', 'asc');
592
        $this->assertEquals('Phil', $list->first()->Name);
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
593
        $list = $list->sort('Team.Founder.Surname', 'desc');
594
        $this->assertEquals('Phil', $list->last()->Name);
595
    }
596
597
    public function testSortInvalidParameters()
598
    {
599
        $this->expectException(InvalidArgumentException::class);
600
        $this->expectDeprecationMessage('Fans is not a linear relation on model SilverStripe\ORM\Tests\DataObjectTest\Player');
601
        $list = Team::get();
602
        $list->sort('Founder.Fans.Surname'); // Can't sort on has_many
603
    }
604
605
    public function testSortNumeric()
606
    {
607
        $list = Sortable::get();
608
        $list1 = $list->sort('Sort', 'ASC');
609
        $this->assertEquals(
610
            [
611
            -10,
612
            -2,
613
            -1,
614
            0,
615
            1,
616
            2,
617
            10
618
            ],
619
            $list1->column('Sort')
620
        );
621
    }
622
623
    public function testSortMixedCase()
624
    {
625
        $list = Sortable::get();
626
        $list1 = $list->sort('Name', 'ASC');
627
        $this->assertEquals(
628
            [
629
            'Bob',
630
            'bonny',
631
            'jane',
632
            'John',
633
            'sam',
634
            'Steve',
635
            'steven'
636
            ],
637
            $list1->column('Name')
638
        );
639
    }
640
641
    /**
642
     * Test DataList->canFilterBy()
643
     */
644
    public function testCanFilterBy()
645
    {
646
        // Basic check
647
        $team = Team::get();
648
        $this->assertTrue($team->canFilterBy("Title"));
649
        $this->assertFalse($team->canFilterBy("SomethingElse"));
650
651
        // Has one
652
        $this->assertTrue($team->canFilterBy("CaptainID"));
653
        $this->assertTrue($team->canFilterBy("Captain.ShirtNumber"));
654
        $this->assertFalse($team->canFilterBy("SomethingElse.ShirtNumber"));
655
        $this->assertFalse($team->canFilterBy("Captain.SomethingElse"));
656
        $this->assertTrue($team->canFilterBy("Captain.FavouriteTeam.Captain.ShirtNumber"));
657
658
        // Has many
659
        $this->assertTrue($team->canFilterBy("Fans.Name"));
660
        $this->assertFalse($team->canFilterBy("SomethingElse.Name"));
661
        $this->assertFalse($team->canFilterBy("Fans.SomethingElse"));
662
663
        // Many many
664
        $this->assertTrue($team->canFilterBy("Players.FirstName"));
665
        $this->assertFalse($team->canFilterBy("SomethingElse.FirstName"));
666
        $this->assertFalse($team->canFilterBy("Players.SomethingElse"));
667
668
        // Subclasses
669
        $subteam = SubTeam::get();
670
        $this->assertTrue($subteam->canFilterBy("Title"));
671
        $this->assertTrue($subteam->canFilterBy("SubclassDatabaseField"));
672
    }
673
674
    /**
675
     * $list->filter('Name', 'bob'); // only bob in the list
676
     */
677
    public function testSimpleFilter()
678
    {
679
        $list = Team::get();
680
        $list = $list->filter('Title', 'Team 2');
681
        $this->assertEquals(1, $list->count());
682
        $this->assertEquals('Team 2', $list->first()->Title, 'List should only contain Team 2');
683
        $this->assertEquals('Team 2', $list->last()->Title, 'Last should only contain Team 2');
684
    }
685
686
    public function testSimpleFilterEndsWith()
687
    {
688
        $list = TeamComment::get();
689
        $list = $list->filter('Name:EndsWith', 'b');
690
        $this->assertEquals(1, $list->count());
691
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
692
    }
693
694
    public function testSimpleFilterExactMatchFilter()
695
    {
696
        $list = TeamComment::get();
697
        $list = $list->filter('Name:ExactMatch', 'Bob');
698
        $this->assertEquals(1, $list->count());
699
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
700
    }
701
702
    public function testSimpleFilterGreaterThanFilter()
703
    {
704
        $list = TeamComment::get();
705
        $list = $list->filter('TeamID:GreaterThan', $this->idFromFixture(DataObjectTest\Team::class, 'team1'));
706
        $this->assertEquals(1, $list->count());
707
        $this->assertEquals('Phil', $list->first()->Name, 'First comment should be from Phil');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
708
    }
709
710
    public function testSimpleFilterGreaterThanOrEqualFilter()
711
    {
712
        $list = TeamComment::get();
713
        $list = $list->filter(
714
            'TeamID:GreaterThanOrEqual',
715
            $this->idFromFixture(DataObjectTest\Team::class, 'team1')
716
        )->sort("ID");
717
        $this->assertEquals(3, $list->count());
718
        $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Joe');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
719
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
720
    }
721
722
    public function testSimpleFilterLessThanFilter()
723
    {
724
        $list = TeamComment::get();
725
        $list = $list->filter(
726
            'TeamID:LessThan',
727
            $this->idFromFixture(DataObjectTest\Team::class, 'team2')
728
        )->sort('Name');
729
        $this->assertEquals(2, $list->count());
730
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
731
        $this->assertEquals('Joe', $list->Last()->Name, 'Last comment should be from Joe');
732
    }
733
734
    public function testSimpleFilterLessThanOrEqualFilter()
735
    {
736
        $list = TeamComment::get();
737
        $list = $list->filter(
738
            'TeamID:LessThanOrEqual',
739
            $this->idFromFixture(DataObjectTest\Team::class, 'team1')
740
        )->sort('ID');
741
        $this->assertEquals(2, $list->count());
742
        $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Joe');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
743
        $this->assertEquals('Bob', $list->Last()->Name, 'Last comment should be from Bob');
744
    }
745
746
    public function testSimplePartialMatchFilter()
747
    {
748
        $list = TeamComment::get();
749
        $list = $list->filter('Name:PartialMatch', 'o')->sort('Name');
750
        $this->assertEquals(2, $list->count());
751
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
752
        $this->assertEquals('Joe', $list->last()->Name, 'First comment should be from Joe');
753
    }
754
755
    public function testSimpleFilterStartsWith()
756
    {
757
        $list = TeamComment::get();
758
        $list = $list->filter('Name:StartsWith', 'B');
759
        $this->assertEquals(1, $list->count());
760
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
761
    }
762
763
    public function testSimpleFilterWithNonExistingComparisator()
764
    {
765
        $this->expectException(InjectorNotFoundException::class);
766
        $this->expectExceptionMessageMatches('/Class "?DataListFilter.Bogus"? does not exist/');
767
768
        $list = TeamComment::get();
769
        $list->filter('Comment:Bogus', 'team comment');
770
    }
771
772
    /**
773
     * Invalid modifiers are treated as failed filter construction
774
     */
775
    public function testInvalidModifier()
776
    {
777
        $this->expectException(InjectorNotFoundException::class);
778
        $this->expectExceptionMessageMatches('/Class "?DataListFilter.invalidmodifier"? does not exist/');
779
780
        $list = TeamComment::get();
781
        $list->filter('Comment:invalidmodifier', 'team comment');
782
    }
783
784
    /**
785
     * $list->filter('Name', ['aziz', 'bob']); // aziz and bob in list
786
     */
787
    public function testSimpleFilterWithMultiple()
788
    {
789
        $list = TeamComment::get();
790
        $list = $list->filter('Name', ['Bob','Phil']);
791
        $list = $list->sort('Name', 'ASC');
792
        $this->assertEquals(2, $list->count());
793
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
794
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
795
    }
796
797
    public function testMultipleFilterWithNoMatch()
798
    {
799
        $list = TeamComment::get();
800
        $list = $list->filter(['Name'=>'Bob', 'Comment'=>'Phil is a unique guy, and comments on team2']);
801
        $this->assertEquals(0, $list->count());
802
    }
803
804
    /**
805
     *  $list->filter(['Name'=>'bob, 'Age'=>21]); // bob with the age 21
806
     */
807
    public function testFilterMultipleArray()
808
    {
809
        $list = TeamComment::get();
810
        $list = $list->filter(['Name'=>'Bob', 'Comment'=>'This is a team comment by Bob']);
811
        $list = $list->sort('Name', 'ASC');
812
        $this->assertEquals(1, $list->count());
813
        $this->assertEquals('Bob', $list->first()->Name, 'Only comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
814
    }
815
816
    public function testFilterMultipleWithTwoMatches()
817
    {
818
        $list = TeamComment::get();
819
        $list = $list->filter(['TeamID'=>$this->idFromFixture(DataObjectTest\Team::class, 'team1')]);
820
        $this->assertEquals(2, $list->count());
821
    }
822
823
    public function testFilterMultipleWithArrayFilter()
824
    {
825
        $list = TeamComment::get();
826
        $list = $list->filter(['Name'=>['Bob','Phil']]);
827
        $list = $list->sort('Name', 'ASC');
828
        $this->assertEquals(2, $list->count(), 'There should be two comments');
829
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
830
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
831
    }
832
833
    public function testFilterMultipleWithArrayFilterAndModifiers()
834
    {
835
        $list = TeamComment::get();
836
        $list = $list->filter(['Name:StartsWith'=>['Bo', 'Jo']]);
837
        $list = $list->sort('Name', 'ASC');
838
        $this->assertEquals(2, $list->count());
839
        $this->assertEquals('Bob', $list->first()->Name);
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
840
        $this->assertEquals('Joe', $list->last()->Name);
841
    }
842
843
    /**
844
     * $list->filter(['Name'=>['aziz','bob'], 'Age'=>[21, 43]]);
845
     */
846
    public function testFilterArrayInArray()
847
    {
848
        $list = TeamComment::get();
849
        $list = $list->filter(
850
            [
851
            'Name'=>['Bob','Phil'],
852
            'TeamID'=>[$this->idFromFixture(DataObjectTest\Team::class, 'team1')]]
853
        );
854
        $this->assertEquals(1, $list->count(), 'There should be one comment');
855
        $this->assertEquals('Bob', $list->first()->Name, 'Only comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
856
    }
857
858
    public function testFilterWithModifiers()
859
    {
860
        $list = TeamComment::get();
861
        $nocaseList = $list->filter('Name:nocase', 'bob');
862
        $this->assertEquals(1, $nocaseList->count(), 'There should be one comment');
863
        $caseList = $list->filter('Name:case', 'bob');
864
        $this->assertEquals(0, $caseList->count(), 'There should be no comments');
865
        $gtList = $list->filter(
866
            'TeamID:GreaterThan:not',
867
            $this->idFromFixture(DataObjectTest\Team::class, 'team1')
868
        );
869
        $this->assertEquals(2, $gtList->count());
870
    }
871
872
    /**
873
     * Test that a filter correctly aliases relationships that share common classes
874
     */
875
    public function testFilterSharedRelationalClasses()
876
    {
877
        /** @var Bracket $final1 */
878
        $final1 = $this->objFromFixture(Bracket::class, 'final');
879
        $prefinal1 = $this->objFromFixture(Bracket::class, 'prefinal1');
880
        $prefinal2 = $this->objFromFixture(Bracket::class, 'prefinal2');
881
        $semifinal1 = $this->objFromFixture(Bracket::class, 'semifinal1');
882
        $team2 = $this->objFromFixture(Team::class, 'team2');
883
884
        // grand child can be found from parent
885
        $found = Bracket::get()->filter('Next.Next.Title', $final1->Title);
886
        $this->assertListEquals(
887
            [['Title' => $semifinal1->Title]],
888
            $found
889
        );
890
891
        // grand child can be found from child
892
        $found = Bracket::get()->filter('Next.Title', $prefinal1->Title);
893
        $this->assertListEquals(
894
            [['Title' => $semifinal1->Title]],
895
            $found
896
        );
897
898
        // child can be found from parent
899
        $found = Bracket::get()->filter('Next.Title', $final1->Title);
900
        $this->assertListEquals(
901
            [
902
                ['Title' => $prefinal1->Title],
903
                ['Title' => $prefinal2->Title]
904
            ],
905
            $found
906
        );
907
908
        // Complex filter, get brackets where the following bracket was won by team 1
909
        // Note: Includes results from multiple levels
910
        $found = Bracket::get()->filter('Next.Winner.Title', $team2->Title);
911
        $this->assertListEquals(
912
            [
913
                ['Title' => $prefinal1->Title],
914
                ['Title' => $prefinal2->Title],
915
                ['Title' => $semifinal1->Title]
916
            ],
917
            $found
918
        );
919
    }
920
921
    public function testFilterOnImplicitJoinWithSharedInheritance()
922
    {
923
        $list = DataObjectTest\RelationChildFirst::get()->filter([
924
            'ManyNext.ID' => [
925
                $this->idFromFixture(DataObjectTest\RelationChildSecond::class, 'test1'),
926
                $this->idFromFixture(DataObjectTest\RelationChildSecond::class, 'test2'),
927
            ],
928
        ]);
929
        $this->assertEquals(2, $list->count());
930
        $ids = $list->column('ID');
931
        $this->assertContains($this->idFromFixture(DataObjectTest\RelationChildFirst::class, 'test1'), $ids);
932
        $this->assertContains($this->idFromFixture(DataObjectTest\RelationChildFirst::class, 'test2'), $ids);
933
    }
934
935
    public function testFilterAny()
936
    {
937
        $list = TeamComment::get();
938
        $list = $list->filterAny('Name', 'Bob');
939
        $this->assertEquals(1, $list->count());
940
    }
941
942
    public function testFilterAnyWithRelation()
943
    {
944
        $list = Player::get();
945
        $list = $list->filterAny([
946
            'Teams.Title:StartsWith' => 'Team',
947
            'ID:GreaterThan' => 0,
948
        ]);
949
        $this->assertCount(4, $list);
950
    }
951
952
    public function testFilterAnyWithTwoGreaterThanFilters()
953
    {
954
955
        for ($i=1; $i<=3; $i++) {
956
            $f = new Fixture();
957
            $f->MyDecimal = $i;
0 ignored issues
show
Bug Best Practice introduced by
The property MyDecimal does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __set, consider adding a @property annotation.
Loading history...
958
            $f->write();
959
960
            $f = new Fixture();
961
            $f->MyInt = $i;
0 ignored issues
show
Bug Best Practice introduced by
The property MyInt does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __set, consider adding a @property annotation.
Loading history...
962
            $f->write();
963
        }
964
965
        $list = Fixture::get()->filterAny([
966
            'MyDecimal:GreaterThan' => 1, // 2 records
967
            'MyInt:GreaterThan' => 2, // 1 record
968
        ]);
969
970
        $this->assertCount(3, $list);
971
    }
972
973
    public function testFilterAnyMultipleArray()
974
    {
975
        $list = TeamComment::get();
976
        $list = $list->filterAny(['Name'=>'Bob', 'Comment'=>'This is a team comment by Bob']);
977
        $this->assertEquals(1, $list->count());
978
        $this->assertEquals('Bob', $list->first()->Name, 'Only comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
979
    }
980
981
    public function testFilterAnyOnFilter()
982
    {
983
        $list = TeamComment::get();
984
        $list = $list->filter(
985
            [
986
            'TeamID'=>$this->idFromFixture(DataObjectTest\Team::class, 'team1')
987
            ]
988
        );
989
        $list = $list->filterAny(
990
            [
991
            'Name'=>['Phil', 'Joe'],
992
            'Comment'=>'This is a team comment by Bob'
993
            ]
994
        );
995
        $list = $list->sort('Name');
996
        $this->assertEquals(2, $list->count());
997
        $this->assertEquals(
998
            'Bob',
999
            $list->offsetGet(0)->Name,
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1000
            'Results should include comments from Bob, matched by comment and team'
1001
        );
1002
        $this->assertEquals(
1003
            'Joe',
1004
            $list->offsetGet(1)->Name,
1005
            'Results should include comments by Joe, matched by name and team (not by comment)'
1006
        );
1007
1008
        $list = TeamComment::get();
1009
        $list = $list->filter(
1010
            [
1011
            'TeamID'=>$this->idFromFixture(DataObjectTest\Team::class, 'team1')
1012
            ]
1013
        );
1014
        $list = $list->filterAny(
1015
            [
1016
            'Name'=>['Phil', 'Joe'],
1017
            'Comment'=>'This is a team comment by Bob'
1018
            ]
1019
        );
1020
        $list = $list->sort('Name');
1021
        $list = $list->filter(['Name' => 'Bob']);
1022
        $this->assertEquals(1, $list->count());
1023
        $this->assertEquals(
1024
            'Bob',
1025
            $list->offsetGet(0)->Name,
1026
            'Results should include comments from Bob, matched by name and team'
1027
        );
1028
    }
1029
1030
    public function testFilterAnyMultipleWithArrayFilter()
1031
    {
1032
        $list = TeamComment::get();
1033
        $list = $list->filterAny(['Name'=>['Bob','Phil']]);
1034
        $this->assertEquals(2, $list->count(), 'There should be two comments');
1035
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1036
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
1037
    }
1038
1039
    public function testFilterAnyArrayInArray()
1040
    {
1041
        $list = TeamComment::get();
1042
        $list = $list->filterAny(
1043
            [
1044
            'Name'=>['Bob','Phil'],
1045
            'TeamID'=>[$this->idFromFixture(DataObjectTest\Team::class, 'team1')]]
1046
        )
1047
            ->sort('Name');
1048
        $this->assertEquals(3, $list->count());
1049
        $this->assertEquals(
1050
            'Bob',
1051
            $list->offsetGet(0)->Name,
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1052
            'Results should include comments from Bob, matched by name and team'
1053
        );
1054
        $this->assertEquals(
1055
            'Joe',
1056
            $list->offsetGet(1)->Name,
1057
            'Results should include comments by Joe, matched by team (not by name)'
1058
        );
1059
        $this->assertEquals(
1060
            'Phil',
1061
            $list->offsetGet(2)->Name,
1062
            'Results should include comments from Phil, matched by name (even if he\'s not in Team1)'
1063
        );
1064
    }
1065
1066
    public function testFilterOnJoin()
1067
    {
1068
        $list = TeamComment::get()
1069
            ->leftJoin(
1070
                'DataObjectTest_Team',
1071
                '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"'
1072
            )->filter([
1073
                'Title' => 'Team 1'
1074
            ]);
1075
1076
        $this->assertEquals(2, $list->count());
1077
        $values = $list->column('Name');
1078
        $this->assertEquals(array_intersect($values ?? [], ['Joe', 'Bob']), $values);
1079
    }
1080
1081
    public function testFilterOnImplicitJoin()
1082
    {
1083
        // Many to many
1084
        $list = Team::get()
1085
            ->filter('Players.FirstName', ['Captain', 'Captain 2']);
1086
1087
        $this->assertEquals(2, $list->count());
1088
1089
        // Has many
1090
        $list = Team::get()
1091
            ->filter('Comments.Name', ['Joe', 'Phil']);
1092
1093
        $this->assertEquals(2, $list->count());
1094
1095
        // Has one
1096
        $list = Player::get()
1097
            ->filter('FavouriteTeam.Title', 'Team 1');
1098
1099
        $this->assertEquals(1, $list->count());
1100
        $this->assertEquals('007', $list->first()->ShirtNumber);
0 ignored issues
show
Bug Best Practice introduced by
The property ShirtNumber does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1101
    }
1102
1103
    public function testFilterOnInvalidRelation()
1104
    {
1105
        $this->expectException(InvalidArgumentException::class);
1106
        $this->expectExceptionMessage('MascotAnimal is not a relation on model SilverStripe\ORM\Tests\DataObjectTest\Team');
1107
        // Filter on missing relation 'MascotAnimal'
1108
        Team::get()
1109
            ->filter('MascotAnimal.Name', 'Richard')
1110
            ->toArray();
1111
    }
1112
1113
    public function testFilterAndExcludeById()
1114
    {
1115
        $id = $this->idFromFixture(SubTeam::class, 'subteam1');
1116
        $list = SubTeam::get()->filter('ID', $id);
1117
        $this->assertEquals($id, $list->first()->ID);
1118
1119
        $list = SubTeam::get();
1120
        $this->assertEquals(3, count($list ?? []));
1121
        $this->assertEquals(2, count($list->exclude('ID', $id) ?? []));
1122
    }
1123
1124
    /**
1125
     * @skipUpgrade
1126
     */
1127
    public function testFilterByNull()
1128
    {
1129
        $list = Fan::get();
1130
        // Force DataObjectTest_Fan/fan5::Email to empty string
1131
        $fan5id = $this->idFromFixture(Fan::class, 'fan5');
1132
        DB::prepared_query("UPDATE \"DataObjectTest_Fan\" SET \"Email\" = '' WHERE \"ID\" = ?", [$fan5id]);
1133
1134
        // Filter by null email
1135
        $nullEmails = $list->filter('Email', null);
1136
        $this->assertListEquals(
1137
            [
1138
            [
1139
                'Name' => 'Stephen',
1140
            ],
1141
            [
1142
                'Name' => 'Mitch',
1143
            ]
1144
            ],
1145
            $nullEmails
1146
        );
1147
1148
        // Filter by non-null
1149
        $nonNullEmails = $list->filter('Email:not', null);
1150
        $this->assertListEquals(
1151
            [
1152
            [
1153
                'Name' => 'Damian',
1154
                'Email' => '[email protected]',
1155
            ],
1156
            [
1157
                'Name' => 'Richard',
1158
                'Email' => '[email protected]',
1159
            ],
1160
            [
1161
                'Name' => 'Hamish',
1162
            ]
1163
            ],
1164
            $nonNullEmails
1165
        );
1166
1167
        // Filter by empty only
1168
        $emptyOnly = $list->filter('Email', '');
1169
        $this->assertListEquals(
1170
            [
1171
            [
1172
                'Name' => 'Hamish',
1173
            ]
1174
            ],
1175
            $emptyOnly
1176
        );
1177
1178
        // Non-empty only. This should include null values, since ExactMatchFilter works around
1179
        // the caveat that != '' also excludes null values in ANSI SQL-92 behaviour.
1180
        $nonEmptyOnly = $list->filter('Email:not', '');
1181
        $this->assertListEquals(
1182
            [
1183
            [
1184
                'Name' => 'Damian',
1185
                'Email' => '[email protected]',
1186
            ],
1187
            [
1188
                'Name' => 'Richard',
1189
                'Email' => '[email protected]',
1190
            ],
1191
            [
1192
                'Name' => 'Stephen',
1193
            ],
1194
            [
1195
                'Name' => 'Mitch',
1196
            ]
1197
            ],
1198
            $nonEmptyOnly
1199
        );
1200
1201
        // Filter by many including null, empty string, and non-empty
1202
        $items1 = $list->filter('Email', [null, '', '[email protected]']);
1203
        $this->assertListEquals(
1204
            [
1205
            [
1206
                'Name' => 'Damian',
1207
                'Email' => '[email protected]',
1208
            ],
1209
            [
1210
                'Name' => 'Stephen',
1211
            ],
1212
            [
1213
                'Name' => 'Mitch',
1214
            ],
1215
            [
1216
                'Name' => 'Hamish',
1217
            ]
1218
            ],
1219
            $items1
1220
        );
1221
1222
        // Filter exclusion of above list
1223
        $items2 = $list->filter('Email:not', [null, '', '[email protected]']);
1224
        $this->assertListEquals(
1225
            [
1226
            [
1227
                'Name' => 'Richard',
1228
                'Email' => '[email protected]',
1229
            ],
1230
            ],
1231
            $items2
1232
        );
1233
1234
        // Filter by many including empty string and non-empty
1235
        $items3 = $list->filter('Email', ['', '[email protected]']);
1236
        $this->assertListEquals(
1237
            [
1238
            [
1239
                'Name' => 'Damian',
1240
                'Email' => '[email protected]',
1241
            ],
1242
            [
1243
                'Name' => 'Hamish',
1244
            ]
1245
            ],
1246
            $items3
1247
        );
1248
1249
        // Filter by many including empty string and non-empty
1250
        // This also relies no the workaround for null comparison as in the $nonEmptyOnly test
1251
        $items4 = $list->filter('Email:not', ['', '[email protected]']);
1252
        $this->assertListEquals(
1253
            [
1254
            [
1255
                'Name' => 'Richard',
1256
                'Email' => '[email protected]',
1257
            ],
1258
            [
1259
                'Name' => 'Stephen',
1260
            ],
1261
            [
1262
                'Name' => 'Mitch',
1263
            ]
1264
            ],
1265
            $items4
1266
        );
1267
1268
        // Filter by many including empty string and non-empty
1269
        // The extra null check isn't necessary, but check that this doesn't fail
1270
        $items5 = $list->filterAny(
1271
            [
1272
            'Email:not' => ['', '[email protected]'],
1273
            'Email' => null
1274
            ]
1275
        );
1276
        $this->assertListEquals(
1277
            [
1278
            [
1279
                'Name' => 'Richard',
1280
                'Email' => '[email protected]',
1281
            ],
1282
            [
1283
                'Name' => 'Stephen',
1284
            ],
1285
            [
1286
                'Name' => 'Mitch',
1287
            ]
1288
            ],
1289
            $items5
1290
        );
1291
1292
        // Filter by null or empty values
1293
        $items6 = $list->filter('Email', [null, '']);
1294
        $this->assertListEquals(
1295
            [
1296
            [
1297
                'Name' => 'Stephen',
1298
            ],
1299
            [
1300
                'Name' => 'Mitch',
1301
            ],
1302
            [
1303
                'Name' => 'Hamish',
1304
            ]
1305
            ],
1306
            $items6
1307
        );
1308
    }
1309
1310
    /**
1311
     * Test null checks with case modifiers
1312
     */
1313
    public function testFilterByNullCase()
1314
    {
1315
        // Test with case (case/nocase both use same code path)
1316
        // Test with and without null, and with inclusion/exclusion permutations
1317
        $list = Fan::get();
1318
1319
        // Only an explicit NOT NULL should include null values
1320
        $items6 = $list->filter('Email:not:case', [null, '', '[email protected]']);
1321
        $this->assertSQLContains(' AND "DataObjectTest_Fan"."Email" IS NOT NULL', $items6->sql());
1322
1323
        // These should all include values where Email IS NULL
1324
        $items7 = $list->filter('Email:nocase', [null, '', '[email protected]']);
1325
        $this->assertSQLContains(' OR "DataObjectTest_Fan"."Email" IS NULL', $items7->sql());
1326
        $items8 = $list->filter('Email:not:case', ['', '[email protected]']);
1327
        $this->assertSQLContains(' OR "DataObjectTest_Fan"."Email" IS NULL', $items8->sql());
1328
1329
        // These should not contain any null checks at all
1330
        $items9 = $list->filter('Email:nocase', ['', '[email protected]']);
1331
        $this->assertSQLNotContains('"DataObjectTest_Fan"."Email" IS NULL', $items9->sql());
1332
        $this->assertSQLNotContains('"DataObjectTest_Fan"."Email" IS NOT NULL', $items9->sql());
1333
    }
1334
1335
    public function testAggregateDBName()
1336
    {
1337
        $filter = new ExactMatchFilter(
1338
            'Comments.Count()'
1339
        );
1340
        $filter->apply(new DataQuery(DataObjectTest\Team::class));
1341
        $this->assertEquals('COUNT("comments_DataObjectTest_TeamComment"."ID")', $filter->getDBName());
1342
1343
        foreach (['Comments.Max(ID)', 'Comments.Max( ID )', 'Comments.Max(  ID)'] as $name) {
1344
            $filter = new ExactMatchFilter($name);
1345
            $filter->apply(new DataQuery(DataObjectTest\Team::class));
1346
            $this->assertEquals('MAX("comments_DataObjectTest_TeamComment"."ID")', $filter->getDBName());
1347
        }
1348
    }
1349
1350
    public function testAggregateFilterExceptions()
1351
    {
1352
        $ex = null;
1353
        try {
1354
            $filter = new ExactMatchFilter('Comments.Max( This will not parse! )');
0 ignored issues
show
Unused Code introduced by
The assignment to $filter is dead and can be removed.
Loading history...
1355
        } catch (\Exception $e) {
1356
            $ex = $e;
1357
        }
1358
        $this->assertInstanceOf(\InvalidArgumentException::class, $ex);
1359
        $this->assertMatchesRegularExpression('/Malformed/', $ex->getMessage());
0 ignored issues
show
Bug introduced by
The method getMessage() does not exist on null. ( Ignorable by Annotation )

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

1359
        $this->assertMatchesRegularExpression('/Malformed/', $ex->/** @scrutinizer ignore-call */ getMessage());

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...
1360
1361
1362
        $filter = new ExactMatchFilter('Comments.Max(NonExistentColumn)');
1363
        $filter->setModel(new DataObjectTest\Team());
1364
        $ex = null;
1365
        try {
1366
            $name = $filter->getDBName();
0 ignored issues
show
Unused Code introduced by
The assignment to $name is dead and can be removed.
Loading history...
1367
        } catch (\Exception $e) {
1368
            $ex = $e;
1369
        }
1370
        $this->assertInstanceOf(\InvalidArgumentException::class, $ex);
1371
        $this->assertMatchesRegularExpression('/Invalid column/', $ex->getMessage());
1372
    }
1373
1374
    public function testAggregateFilters()
1375
    {
1376
        $teams = Team::get()->filter('Comments.Count()', 2);
1377
1378
        $team1 = $this->objFromFixture(Team::class, 'team1');
1379
        $team2 = $this->objFromFixture(Team::class, 'team2');
1380
        $team3 = $this->objFromFixture(Team::class, 'team3');
1381
        $team4 = $this->objFromFixture(SubTeam::class, 'subteam1');
1382
        $team5 = $this->objFromFixture(SubTeam::class, 'subteam2_with_player_relation');
1383
        $team6 = $this->objFromFixture(SubTeam::class, 'subteam3_with_empty_fields');
1384
1385
        $company1 = $this->objFromFixture(EquipmentCompany::class, 'equipmentcompany1');
1386
        $company2 = $this->objFromFixture(EquipmentCompany::class, 'equipmentcompany2');
1387
1388
        $company1->CurrentStaff()->add(Staff::create(['Salary' => 3])->write());
0 ignored issues
show
Bug introduced by
The method CurrentStaff() does not exist on SilverStripe\ORM\DataObject. 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

1388
        $company1->/** @scrutinizer ignore-call */ 
1389
                   CurrentStaff()->add(Staff::create(['Salary' => 3])->write());
Loading history...
1389
        $company1->CurrentStaff()->add(Staff::create(['Salary' => 5])->write());
1390
        $company2->CurrentStaff()->add(Staff::create(['Salary' => 4])->write());
1391
1392
        $this->assertCount(1, $teams);
1393
        $this->assertEquals($team1->ID, $teams->first()->ID);
1394
1395
        $teams = Team::get()->filter('Comments.Count()', [1,2]);
1396
1397
        $this->assertCount(2, $teams);
1398
        foreach ([$team1, $team2] as $expectedTeam) {
1399
            $this->assertContains($expectedTeam->ID, $teams->column('ID'));
1400
        }
1401
1402
        $teams = Team::get()->filter('Comments.Count():GreaterThan', 1);
1403
1404
        $this->assertCount(1, $teams);
1405
        $this->assertContains(
1406
            $this->objFromFixture(Team::class, 'team1')->ID,
1407
            $teams->column('ID')
1408
        );
1409
1410
        $teams = Team::get()->filter('Comments.Count():LessThan', 2);
1411
1412
        $this->assertCount(5, $teams);
1413
        foreach ([$team2, $team3, $team4, $team5, $team6] as $expectedTeam) {
1414
            $this->assertContains($expectedTeam->ID, $teams->column('ID'));
1415
        }
1416
1417
        $teams = Team::get()->filter('Comments.Count():GreaterThanOrEqual', 1);
1418
1419
        $this->assertCount(2, $teams);
1420
        foreach ([$team1, $team2] as $expectedTeam) {
1421
            $this->assertContains($expectedTeam->ID, $teams->column('ID'));
1422
        }
1423
1424
        $teams = Team::get()->filter('Comments.Count():LessThanOrEqual', 1);
1425
1426
        $this->assertCount(5, $teams);
1427
        foreach ([$team2, $team3, $team4, $team5, $team6] as $expectedTeam) {
1428
            $this->assertContains($expectedTeam->ID, $teams->column('ID'));
1429
        }
1430
1431
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Max(Salary)', 5);
1432
        $this->assertCount(1, $companies);
1433
        $this->assertEquals($company1->ID, $companies->first()->ID);
1434
1435
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Min(Salary)', 3);
1436
        $this->assertCount(1, $companies);
1437
        $this->assertEquals($company1->ID, $companies->first()->ID);
1438
1439
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Max(Salary):GreaterThan', 3);
1440
        $this->assertCount(2, $companies);
1441
        foreach ([$company1, $company2] as $expectedTeam) {
1442
            $this->assertContains($expectedTeam->ID, $companies->column('ID'));
1443
        }
1444
1445
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Sum(Salary)', 8);
1446
        $this->assertCount(1, $companies);
1447
        $this->assertEquals($company1->ID, $companies->first()->ID);
1448
1449
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Sum(Salary):LessThan', 7);
1450
        $this->assertCount(1, $companies);
1451
        $this->assertEquals($company2->ID, $companies->first()->ID);
1452
1453
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Sum(Salary):GreaterThan', 100);
1454
        $this->assertCount(0, $companies);
1455
1456
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Sum(Salary):GreaterThan', 7);
1457
        $this->assertCount(1, $companies);
1458
        $this->assertEquals($company1->ID, $companies->first()->ID);
1459
1460
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Avg(Salary)', 4);
1461
        $this->assertCount(2, $companies);
1462
        foreach ([$company1, $company2] as $expectedTeam) {
1463
            $this->assertContains($expectedTeam->ID, $companies->column('ID'));
1464
        }
1465
1466
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Avg(Salary):LessThan', 10);
1467
        $this->assertCount(2, $companies);
1468
        foreach ([$company1, $company2] as $expectedTeam) {
1469
            $this->assertContains($expectedTeam->ID, $companies->column('ID'));
1470
        }
1471
    }
1472
1473
    /**
1474
     * $list = $list->filterByCallback(function($item, $list) { return $item->Age == 21; })
1475
     */
1476
    public function testFilterByCallback()
1477
    {
1478
        $team1ID = $this->idFromFixture(DataObjectTest\Team::class, 'team1');
1479
        $list = TeamComment::get();
1480
        $list = $list->filterByCallback(
1481
            function ($item, $list) use ($team1ID) {
0 ignored issues
show
Unused Code introduced by
The parameter $list is not used and could be removed. ( Ignorable by Annotation )

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

1481
            function ($item, /** @scrutinizer ignore-unused */ $list) use ($team1ID) {

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

Loading history...
1482
                return $item->TeamID == $team1ID;
1483
            }
1484
        );
1485
1486
        $result = $list->column('Name');
1487
        $expected = array_intersect($result ?? [], ['Joe', 'Bob']);
1488
1489
        $this->assertEquals(2, $list->count());
1490
        $this->assertEquals($expected, $result, 'List should only contain comments from Team 1 (Joe and Bob)');
1491
        $this->assertTrue($list instanceof Filterable, 'The List should be of type SS_Filterable');
1492
    }
1493
1494
    /**
1495
     * $list->exclude('Name', 'bob'); // exclude bob from list
1496
     */
1497
    public function testSimpleExclude()
1498
    {
1499
        $list = TeamComment::get();
1500
        $list = $list->exclude('Name', 'Bob');
1501
        $list = $list->sort('Name');
1502
        $this->assertEquals(2, $list->count());
1503
        $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Joe');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1504
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
1505
    }
1506
    //
1507
    /**
1508
     * $list->exclude('Name', ['aziz', 'bob']); // exclude aziz and bob from list
1509
     */
1510
    public function testSimpleExcludeWithMultiple()
1511
    {
1512
        $list = TeamComment::get();
1513
        $list = $list->exclude('Name', ['Joe','Phil']);
1514
        $this->assertEquals(1, $list->count());
1515
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1516
    }
1517
1518
    /**
1519
     * $list->exclude(['Name'=>'bob, 'Age'=>21]); // negative version
1520
     */
1521
    public function testMultipleExcludeWithMiss()
1522
    {
1523
        $list = TeamComment::get();
1524
        $list = $list->exclude(['Name'=>'Bob', 'Comment'=>'Does not match any comments']);
1525
        $this->assertEquals(3, $list->count());
1526
    }
1527
1528
    /**
1529
     * $list->exclude(['Name'=>'bob, 'Age'=>21]); // exclude bob that has Age 21
1530
     */
1531
    public function testMultipleExclude()
1532
    {
1533
        $list = TeamComment::get();
1534
        $list = $list->exclude(['Name'=>'Bob', 'Comment'=>'This is a team comment by Bob']);
1535
        $this->assertEquals(2, $list->count());
1536
    }
1537
1538
    /**
1539
     * Test doesn't exclude if only matches one
1540
     * $list->exclude(['Name'=>'bob, 'Age'=>21]); // exclude bob that has Age 21
1541
     */
1542
    public function testMultipleExcludeMultipleMatches()
1543
    {
1544
        $list = TeamComment::get();
1545
        $list = $list->exclude(['Name'=>'Bob', 'Comment'=>'Phil is a unique guy, and comments on team2']);
1546
        $this->assertCount(3, $list);
1547
    }
1548
1549
    /**
1550
     * // exclude only those that match both
1551
     */
1552
    public function testMultipleExcludeArraysMultipleMatches()
1553
    {
1554
        $list = TeamComment::get();
1555
        $list = $list->exclude([
1556
            'Name'=> ['Bob', 'Phil'],
1557
            'Comment'=> [
1558
                'This is a team comment by Bob',
1559
                'Phil is a unique guy, and comments on team2'
1560
            ]
1561
        ]);
1562
        $this->assertListEquals([['Name' => 'Joe']], $list);
1563
    }
1564
1565
    /**
1566
     * Exclude only which matches both params
1567
     */
1568
    public function testMultipleExcludeArraysMultipleMatchesOneMiss()
1569
    {
1570
        $list = TeamComment::get();
1571
        $list = $list->exclude([
1572
            'Name' => ['Bob', 'Phil'],
1573
            'Comment' => [
1574
                'Does not match any comments',
1575
                'Phil is a unique guy, and comments on team2'
1576
            ]
1577
        ]);
1578
        $list = $list->sort('Name');
1579
        $this->assertListEquals(
1580
            [
1581
                ['Name' => 'Bob'],
1582
                ['Name' => 'Joe'],
1583
            ],
1584
            $list
1585
        );
1586
    }
1587
1588
    /**
1589
     * Test that if an exclude() is applied to a filter(), the filter() is still preserved.
1590
     */
1591
    public function testExcludeOnFilter()
1592
    {
1593
        $list = TeamComment::get();
1594
        $list = $list->filter('Comment', 'Phil is a unique guy, and comments on team2');
1595
        $list = $list->exclude('Name', 'Bob');
1596
1597
        $sql = $list->sql($parameters);
1598
        $this->assertSQLContains(
1599
            'WHERE ("DataObjectTest_TeamComment"."Comment" = ?) AND (("DataObjectTest_TeamComment"."Name" != ? '
1600
            . 'OR "DataObjectTest_TeamComment"."Name" IS NULL))',
1601
            $sql
1602
        );
1603
        $this->assertEquals(['Phil is a unique guy, and comments on team2', 'Bob'], $parameters);
1604
        $this->assertListEquals([['Name' => 'Phil']], $list);
1605
    }
1606
1607
    /**
1608
     * Test that if a complicated exclude() is applied to a filter(), the filter() is still preserved.
1609
     */
1610
    public function testComplicatedExcludeOnFilter()
1611
    {
1612
        $list = TeamComment::get();
1613
        $list = $list->filter('Name', ['Phil', 'Bob']);
1614
        $list = $list->exclude('Name', ['Bob', 'Joe']);
1615
1616
        $sql = $list->sql($parameters);
1617
        $this->assertSQLContains(
1618
            'WHERE ("DataObjectTest_TeamComment"."Name" IN (?, ?)) AND (("DataObjectTest_TeamComment"."Name" NOT IN (?, ?) '
1619
            . 'OR "DataObjectTest_TeamComment"."Name" IS NULL))',
1620
            $sql
1621
        );
1622
        $this->assertEquals(['Phil', 'Bob', 'Bob', 'Joe'], $parameters);
1623
        $this->assertListEquals([['Name' => 'Phil']], $list);
1624
    }
1625
1626
    /**
1627
     * Test that if a very complicated exclude() is applied to a filter(), the filter() is still preserved.
1628
     */
1629
    public function testVeryComplicatedExcludeOnFilter()
1630
    {
1631
        $list = TeamComment::get();
1632
        $list = $list->filter('Name', ['Phil', 'Bob']);
1633
        $list = $list->exclude([
1634
            'Name' => ['Joe', 'Phil'],
1635
            'Comment' => ['Matches no comments', 'Not a matching comment']
1636
        ]);
1637
1638
        $sql = $list->sql($parameters);
1639
        $this->assertSQLContains(
1640
            'WHERE ("DataObjectTest_TeamComment"."Name" IN (?, ?)) '
1641
            . 'AND (("DataObjectTest_TeamComment"."Name" NOT IN (?, ?) '
1642
            . 'OR "DataObjectTest_TeamComment"."Name" IS NULL) '
1643
            . 'OR ("DataObjectTest_TeamComment"."Comment" NOT IN (?, ?) '
1644
            . 'OR "DataObjectTest_TeamComment"."Comment" IS NULL))',
1645
            $sql
1646
        );
1647
        $this->assertEquals(['Phil', 'Bob', 'Joe', 'Phil', 'Matches no comments', 'Not a matching comment'], $parameters);
1648
        $list = $list->sort('Name');
1649
        $this->assertListEquals(
1650
            [
1651
                ['Name' => 'Bob'],
1652
                ['Name' => 'Phil'],
1653
            ],
1654
            $list
1655
        );
1656
    }
1657
1658
    public function testExcludeWithSearchFilter()
1659
    {
1660
        $list = TeamComment::get();
1661
        $list = $list->exclude('Name:LessThan', 'Bob');
1662
1663
        $sql = $list->sql($parameters);
1664
        $this->assertSQLContains('WHERE (("DataObjectTest_TeamComment"."Name" >= ?))', $sql);
1665
        $this->assertEquals(['Bob'], $parameters);
1666
    }
1667
1668
    /**
1669
     * Test that Bob and Phil are excluded (one match each)
1670
     */
1671
    public function testExcludeAny()
1672
    {
1673
        $list = TeamComment::get();
1674
        $list = $list->excludeAny([
1675
            'Name' => 'Bob',
1676
            'Comment' => 'Phil is a unique guy, and comments on team2'
1677
        ]);
1678
        $this->assertListEquals([['Name' => 'Joe']], $list);
1679
    }
1680
1681
    /**
1682
     * Test that Bob and Phil are excluded by Name
1683
     */
1684
    public function testExcludeAnyArrays()
1685
    {
1686
        $list = TeamComment::get();
1687
        $list = $list->excludeAny([
1688
            'Name' => ['Bob', 'Phil'],
1689
            'Comment' => 'No matching comments'
1690
        ]);
1691
        $this->assertListEquals([['Name' => 'Joe']], $list);
1692
    }
1693
1694
    /**
1695
     * Test that Bob is excluded by Name, Phil by comment
1696
     */
1697
    public function testExcludeAnyMultiArrays()
1698
    {
1699
        $list = TeamComment::get();
1700
        $list = $list->excludeAny([
1701
            'Name' => ['Bob', 'Fred'],
1702
            'Comment' => ['No matching comments', 'Phil is a unique guy, and comments on team2']
1703
        ]);
1704
        $this->assertListEquals([['Name' => 'Joe']], $list);
1705
    }
1706
1707
    public function testEmptyFilter()
1708
    {
1709
        $this->expectException(InvalidArgumentException::class);
1710
        $this->expectExceptionMessage('Cannot filter "DataObjectTest_TeamComment"."Name" against an empty set');
1711
        $list = TeamComment::get();
1712
        $list->exclude('Name', []);
1713
    }
1714
1715
    /**
1716
     * $list->exclude(['Name'=>'bob, 'Age'=>[21, 43]]); // exclude bob with Age 21 or 43
1717
     */
1718
    public function testMultipleExcludeWithMultipleThatCheersEitherTeam()
1719
    {
1720
        $list = TeamComment::get();
1721
        $list = $list->exclude(
1722
            ['Name'=>'Bob', 'TeamID'=>[
1723
            $this->idFromFixture(DataObjectTest\Team::class, 'team1'),
1724
            $this->idFromFixture(DataObjectTest\Team::class, 'team2')
1725
            ]]
1726
        );
1727
        $list = $list->sort('Name');
1728
        $this->assertEquals(2, $list->count());
1729
        $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Phil');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1730
        $this->assertEquals('Phil', $list->last()->Name, 'First comment should be from Phil');
1731
    }
1732
1733
    /**
1734
     * $list->exclude(['Name'=>'bob, 'Age'=>[21, 43]]); // negative version
1735
     */
1736
    public function testMultipleExcludeWithMultipleThatCheersOnNonExistingTeam()
1737
    {
1738
        $list = TeamComment::get();
1739
        $list = $list->exclude(['Name'=>'Bob', 'TeamID'=>[3]]);
1740
        $this->assertEquals(3, $list->count());
1741
    }
1742
1743
    /**
1744
     * $list->exclude(['Name'=>['bob','phil'], 'Age'=>[21, 43]]); //negative version
1745
     */
1746
    public function testMultipleExcludeWithNoExclusion()
1747
    {
1748
        $list = TeamComment::get();
1749
        $list = $list->exclude(
1750
            [
1751
            'Name'=>['Bob','Joe'],
1752
            'Comment' => 'Phil is a unique guy, and comments on team2']
1753
        );
1754
        $this->assertEquals(3, $list->count());
1755
    }
1756
1757
    /**
1758
     *  $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43)));
1759
     */
1760
    public function testMultipleExcludeWithTwoArray()
1761
    {
1762
        $list = TeamComment::get();
1763
        $list = $list->exclude(
1764
            ['Name' => ['Bob','Joe'], 'TeamID' => [
1765
            $this->idFromFixture(DataObjectTest\Team::class, 'team1'),
1766
            $this->idFromFixture(DataObjectTest\Team::class, 'team2')
1767
            ]]
1768
        );
1769
        $this->assertEquals(1, $list->count());
1770
        $this->assertEquals('Phil', $list->last()->Name, 'Only comment should be from Phil');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1771
    }
1772
1773
    /**
1774
     *  $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43)));
1775
     */
1776
    public function testMultipleExcludeWithTwoArrayOneTeam()
1777
    {
1778
        $list = TeamComment::get();
1779
        $list = $list->exclude(
1780
            [
1781
            'Name' => ['Bob', 'Phil'],
1782
            'TeamID' => [$this->idFromFixture(DataObjectTest\Team::class, 'team1')]]
1783
        );
1784
        $list = $list->sort('Name');
1785
        $this->assertEquals(2, $list->count());
1786
        $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Joe');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1787
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
1788
    }
1789
1790
    /**
1791
     *
1792
     */
1793
    public function testSortByRelation()
1794
    {
1795
        $list = TeamComment::get();
1796
        $list = $list->sort(['Team.Title' => 'DESC']);
1797
        $this->assertEquals(3, $list->count());
1798
        $this->assertEquals(
1799
            $this->idFromFixture(DataObjectTest\Team::class, 'team2'),
1800
            $list->first()->TeamID,
0 ignored issues
show
Bug Best Practice introduced by
The property TeamID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1801
            'First comment should be for Team 2'
1802
        );
1803
        $this->assertEquals(
1804
            $this->idFromFixture(DataObjectTest\Team::class, 'team1'),
1805
            $list->last()->TeamID,
1806
            'Last comment should be for Team 1'
1807
        );
1808
    }
1809
1810
    public function testReverse()
1811
    {
1812
        $list = TeamComment::get();
1813
        $list = $list->sort('Name');
1814
        $list = $list->reverse();
1815
1816
        $this->assertEquals('Bob', $list->last()->Name, 'Last comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1817
        $this->assertEquals('Phil', $list->first()->Name, 'First comment should be from Phil');
1818
    }
1819
1820
    public function testSortByComplexExpression()
1821
    {
1822
        // Test an expression with both spaces and commas. This test also tests that column() can be called
1823
        // with a complex sort expression, so keep using column() below
1824
        $teamClass = Convert::raw2sql(SubTeam::class);
1825
        $list = Team::get()->sort(
1826
            'CASE WHEN "DataObjectTest_Team"."ClassName" = \'' . $teamClass . '\' THEN 0 ELSE 1 END, "Title" DESC'
1827
        );
1828
        $this->assertEquals(
1829
            [
1830
            'Subteam 3',
1831
            'Subteam 2',
1832
            'Subteam 1',
1833
            'Team 3',
1834
            'Team 2',
1835
            'Team 1',
1836
            ],
1837
            $list->column("Title")
1838
        );
1839
    }
1840
1841
    public function testShuffle()
1842
    {
1843
        $list = Team::get()->shuffle();
1844
1845
        $this->assertSQLContains(DB::get_conn()->random() . ' AS "_SortColumn', $list->dataQuery()->sql());
1846
    }
1847
1848
    public function testColumnFailureInvalidColumn()
1849
    {
1850
        $this->expectException(InvalidArgumentException::class);
1851
1852
        Category::get()->column('ObviouslyInvalidColumn');
1853
    }
1854
1855
    public function testColumnFailureInvalidTable()
1856
    {
1857
        $this->expectException(InvalidArgumentException::class);
1858
1859
        $columnName = null;
1860
        Category::get()
1861
            ->applyRelation('Products.ID', $columnName)
1862
            ->column('"ObviouslyInvalidTable"."ID"');
1863
    }
1864
1865
    public function testColumnFromRelatedTable()
1866
    {
1867
        $columnName = null;
1868
        $productTitles = Category::get()
1869
            ->applyRelation('Products.Title', $columnName)
1870
            ->column($columnName);
1871
1872
        $productTitles = array_diff($productTitles ?? [], [null]);
1873
        sort($productTitles);
1874
1875
        $this->assertEquals([
1876
            'Product A',
1877
            'Product B',
1878
        ], $productTitles);
1879
    }
1880
1881
    public function testChunkedFetch()
1882
    {
1883
        $expectedIDs = Team::get()->map('ID', 'ID')->toArray();
1884
        $expectedSize = sizeof($expectedIDs ?? []);
1885
1886
        $dataQuery = new DataListQueryCounter(Team::class);
1887
        $this->chunkTester(
1888
            $expectedIDs,
1889
            Team::get()->setDataQuery($dataQuery)->chunkedFetch(),
1890
            $dataQuery,
1891
            1
1892
        );
1893
1894
        $dataQuery = new DataListQueryCounter(Team::class);
1895
        $this->chunkTester(
1896
            $expectedIDs,
1897
            Team::get()->setDataQuery($dataQuery)->chunkedFetch(1),
1898
            $dataQuery,
1899
            $expectedSize+1
1900
        );
1901
1902
        $dataQuery = new DataListQueryCounter(Team::class);
1903
        $this->chunkTester(
1904
            $expectedIDs,
1905
            Team::get()->setDataQuery($dataQuery)->chunkedFetch($expectedSize),
1906
            $dataQuery,
1907
            2
1908
        );
1909
1910
        $dataQuery = new DataListQueryCounter(Team::class);
1911
        $this->chunkTester(
1912
            $expectedIDs,
1913
            Team::get()->setDataQuery($dataQuery)->chunkedFetch($expectedSize-1),
1914
            $dataQuery,
1915
            2
1916
        );
1917
1918
        $dataQuery = new DataListQueryCounter(Team::class);
1919
        $this->chunkTester(
1920
            $expectedIDs,
1921
            Team::get()->setDataQuery($dataQuery)->chunkedFetch($expectedSize+1),
1922
            $dataQuery,
1923
            1
1924
        );
1925
    }
1926
1927
    public function testFilteredChunk()
1928
    {
1929
        $dataQuery = new DataListQueryCounter(Team::class);
1930
        $this->chunkTester(
1931
            Team::get()->filter('ClassName', Team::class)->map('ID', 'ID')->toArray(),
1932
            Team::get()->setDataQuery($dataQuery)->filter('ClassName', Team::class)->chunkedFetch(),
1933
            $dataQuery,
1934
            1
1935
        );
1936
    }
1937
1938
    public function testSortedChunk()
1939
    {
1940
        $dataQuery = new DataListQueryCounter(Team::class);
1941
        $this->chunkTester(
1942
            Team::get()->sort('ID', 'Desc')->map('ID', 'ID')->toArray(),
1943
            Team::get()->setDataQuery($dataQuery)->sort('ID', 'Desc')->chunkedFetch(),
1944
            $dataQuery,
1945
            1
1946
        );
1947
    }
1948
1949
    public function testEmptyChunk()
1950
    {
1951
        $dataQuery = new DataListQueryCounter(Team::class);
1952
        $this->chunkTester(
1953
            [],
1954
            Team::get()->setDataQuery($dataQuery)->filter('ClassName', 'non-sense')->chunkedFetch(),
1955
            $dataQuery,
1956
            1
1957
        );
1958
    }
1959
1960
    public function testInvalidChunkSize()
1961
    {
1962
        $this->expectException(InvalidArgumentException::class);
1963
        foreach (Team::get()->chunkedFetch(0) as $item) {
1964
            // You don't get the error until you iterate over the list
1965
        };
1966
    }
1967
1968
    /**
1969
     * Loop over a chunk list and make sure it matches our expected results
1970
     * @param int[] $expectedIDs
1971
     * @param iterable $chunkList
1972
     */
1973
    private function chunkTester(
1974
        array $expectedIDs,
1975
        iterable $chunkList,
1976
        DataListQueryCounter $dataQuery,
1977
        int $expectedQueryCount
1978
    ) {
1979
        foreach ($chunkList as $chunkedTeam) {
1980
            $this->assertInstanceOf(
1981
                Team::class,
1982
                $chunkedTeam,
1983
                'Chunk return the correct type of data object'
1984
            );
1985
1986
            $expectedID = array_shift($expectedIDs);
1987
1988
            $this->assertEquals(
1989
                $expectedID,
1990
                $chunkedTeam->ID,
1991
                'chunk returns the same results in the same order as the regular iterator'
1992
            );
1993
        }
1994
1995
        $this->assertEmpty($expectedIDs, 'chunk returns all the results that the regular iterator does');
1996
        $this->assertEquals($expectedQueryCount, $dataQuery->getCount());
1997
    }
1998
}
1999