Passed
Pull Request — 4 (#10382)
by Guy
07:30
created

SearchContextTest   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 395
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 228
dl 0
loc 395
rs 10
c 0
b 0
f 0
wmc 22

21 Methods

Rating   Name   Duplication   Size   Complexity  
A testCanGenerateQueryUsingAllFilterTypes() 0 21 1
A testDefaultFiltersDefinedWhenNotSetInDataObject() 0 10 1
A testRelationshipObjectsLinkedInSearch() 0 24 1
A testEndsWithFilterCaseInsensitive() 0 11 1
A testPartialMatchUsedByDefaultWhenNotExplicitlySet() 0 12 1
A testMatchAnySearchWithFilters() 0 19 1
A testUserDefinedFieldsAppearInSearchContext() 0 13 1
A testSearchableFieldsDefaultsToSummaryFields() 0 12 2
A testUserDefinedFiltersAppearInSearchContext() 0 12 1
A testMatchAnySearch() 0 29 1
A testSearchContextSummary() 0 59 1
A testAccessDefinedSummaryFields() 0 4 1
A testStartsWithFilterCaseInsensitive() 0 11 1
A testResultSetFilterReturnsExpectedCount() 0 12 1
A testSummaryIncludesDefaultFieldsIfNotDefined() 0 7 1
A testPrimarySearchCustomFieldName() 0 6 1
A testPrimarySearch() 0 28 1
A testprimarySearchFieldNameMustBeUnique() 0 6 1
A testNonPrimarySearchFields() 0 8 1
A testPrimaryOnlyUsesSearchableFields() 0 8 1
A testPrimarySearchDisabled() 0 18 1
1
<?php
2
3
namespace SilverStripe\ORM\Tests\Search;
4
5
use LogicException;
6
use SilverStripe\Core\Config\Config;
7
use SilverStripe\Dev\SapphireTest;
8
use SilverStripe\Forms\TextField;
9
use SilverStripe\Forms\TextareaField;
10
use SilverStripe\Forms\NumericField;
11
use SilverStripe\Forms\DropdownField;
12
use SilverStripe\Forms\CheckboxField;
13
use SilverStripe\Forms\FieldList;
14
use SilverStripe\Forms\HiddenField;
15
use SilverStripe\ORM\DataList;
16
use SilverStripe\ORM\Filters\PartialMatchFilter;
17
use SilverStripe\ORM\Search\SearchContext;
18
19
class SearchContextTest extends SapphireTest
20
{
21
22
    protected static $fixture_file = 'SearchContextTest.yml';
23
24
    protected static $extra_dataobjects = [
25
        SearchContextTest\Person::class,
26
        SearchContextTest\NoSearchableFields::class,
27
        SearchContextTest\Book::class,
28
        SearchContextTest\Company::class,
29
        SearchContextTest\Project::class,
30
        SearchContextTest\Deadline::class,
31
        SearchContextTest\Action::class,
32
        SearchContextTest\AllFilterTypes::class,
33
        SearchContextTest\Customer::class,
34
        SearchContextTest\Address::class,
35
        SearchContextTest\Order::class,
36
        SearchContextTest\PrimarySearch::class,
37
    ];
38
39
    public function testResultSetFilterReturnsExpectedCount()
40
    {
41
        $person = SearchContextTest\Person::singleton();
42
        $context = $person->getDefaultSearchContext();
43
        $results = $context->getResults(['Name' => '']);
44
        $this->assertEquals(5, $results->Count());
45
46
        $results = $context->getResults(['EyeColor' => 'green']);
47
        $this->assertEquals(2, $results->Count());
48
49
        $results = $context->getResults(['EyeColor' => 'green', 'HairColor' => 'black']);
50
        $this->assertEquals(1, $results->Count());
51
    }
52
53
    public function testSearchableFieldsDefaultsToSummaryFields()
54
    {
55
        $obj = new SearchContextTest\NoSearchableFields();
56
        $summaryFields = $obj->summaryFields();
57
        $expected = [];
58
        foreach ($summaryFields as $field => $label) {
59
            $expected[$field] = [
60
                'title' => $obj->fieldLabel($field),
61
                'filter' => 'PartialMatchFilter',
62
            ];
63
        }
64
        $this->assertEquals($expected, $obj->searchableFields());
65
    }
66
67
    public function testSummaryIncludesDefaultFieldsIfNotDefined()
68
    {
69
        $person = SearchContextTest\Person::singleton();
70
        $this->assertContains('Name', $person->summaryFields());
71
72
        $book = SearchContextTest\Book::singleton();
73
        $this->assertContains('Title', $book->summaryFields());
74
    }
75
76
    public function testAccessDefinedSummaryFields()
77
    {
78
        $company = SearchContextTest\Company::singleton();
79
        $this->assertContains('Industry', $company->summaryFields());
80
    }
81
82
    public function testPartialMatchUsedByDefaultWhenNotExplicitlySet()
83
    {
84
        $person = SearchContextTest\Person::singleton();
85
        $context = $person->getDefaultSearchContext();
86
87
        $this->assertEquals(
88
            [
89
                "Name" => new PartialMatchFilter("Name"),
90
                "HairColor" => new PartialMatchFilter("HairColor"),
91
                "EyeColor" => new PartialMatchFilter("EyeColor")
92
            ],
93
            $context->getFilters()
94
        );
95
    }
96
97
    public function testDefaultFiltersDefinedWhenNotSetInDataObject()
98
    {
99
        $book = SearchContextTest\Book::singleton();
100
        $context = $book->getDefaultSearchContext();
101
102
        $this->assertEquals(
103
            [
104
                "Title" => new PartialMatchFilter("Title")
105
            ],
106
            $context->getFilters()
107
        );
108
    }
109
110
    public function testUserDefinedFiltersAppearInSearchContext()
111
    {
112
        $company = SearchContextTest\Company::singleton();
113
        $context = $company->getDefaultSearchContext();
114
115
        $this->assertEquals(
116
            [
117
                "Name" => new PartialMatchFilter("Name"),
118
                "Industry" => new PartialMatchFilter("Industry"),
119
                "AnnualProfit" => new PartialMatchFilter("AnnualProfit")
120
            ],
121
            $context->getFilters()
122
        );
123
    }
124
125
    public function testUserDefinedFieldsAppearInSearchContext()
126
    {
127
        $company = SearchContextTest\Company::singleton();
128
        $context = $company->getDefaultSearchContext();
129
        $this->assertEquals(
130
            new FieldList(
131
                new HiddenField($company->primarySearchFieldName(), 'Primary Search'),
132
                (new TextField("Name", 'Name'))
133
                    ->setMaxLength(255),
134
                new TextareaField("Industry", 'Industry'),
135
                new NumericField("AnnualProfit", 'The Almighty Annual Profit')
136
            ),
137
            $context->getFields()
138
        );
139
    }
140
141
    public function testRelationshipObjectsLinkedInSearch()
142
    {
143
        $action3 = $this->objFromFixture(SearchContextTest\Action::class, 'action3');
144
145
        $project = SearchContextTest\Project::singleton();
146
        $context = $project->getDefaultSearchContext();
147
148
        $params = ["Name" => "Blog Website", "Actions__SolutionArea" => "technical"];
149
150
        /** @var DataList $results */
151
        $results = $context->getResults($params);
152
153
        $this->assertEquals(1, $results->count());
154
155
        /** @var SearchContextTest\Project $project */
156
        $project = $results->first();
157
158
        $this->assertInstanceOf(SearchContextTest\Project::class, $project);
159
        $this->assertEquals("Blog Website", $project->Name);
160
        $this->assertEquals(2, $project->Actions()->Count());
161
162
        $this->assertEquals(
163
            "Get RSS feeds working",
164
            $project->Actions()->find('ID', $action3->ID)->Description
0 ignored issues
show
Bug Best Practice introduced by
The property Description does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
165
        );
166
    }
167
168
    public function testCanGenerateQueryUsingAllFilterTypes()
169
    {
170
        $all = SearchContextTest\AllFilterTypes::singleton();
171
        $context = $all->getDefaultSearchContext();
172
        $params = [
173
            "ExactMatch" => "Match me exactly",
174
            "PartialMatch" => "partially",
175
            "CollectionMatch" => [
176
                "ExistingCollectionValue",
177
                "NonExistingCollectionValue",
178
                4,
179
                "Inline'Quotes'"
180
            ],
181
            "StartsWith" => "12345",
182
            "EndsWith" => "ijkl",
183
            "Fulltext" => "two"
184
        ];
185
186
        $results = $context->getResults($params);
187
        $this->assertEquals(1, $results->Count());
188
        $this->assertEquals("Filtered value", $results->First()->HiddenValue);
189
    }
190
191
    public function testStartsWithFilterCaseInsensitive()
192
    {
193
        $all = SearchContextTest\AllFilterTypes::singleton();
194
        $context = $all->getDefaultSearchContext();
195
        $params = [
196
            "StartsWith" => "12345-6789 camelcase", // spelled lowercase
197
        ];
198
199
        $results = $context->getResults($params);
200
        $this->assertEquals(1, $results->Count());
201
        $this->assertEquals("Filtered value", $results->First()->HiddenValue);
202
    }
203
204
    public function testEndsWithFilterCaseInsensitive()
205
    {
206
        $all = SearchContextTest\AllFilterTypes::singleton();
207
        $context = $all->getDefaultSearchContext();
208
        $params = [
209
            "EndsWith" => "IJKL", // spelled uppercase
210
        ];
211
212
        $results = $context->getResults($params);
213
        $this->assertEquals(1, $results->Count());
214
        $this->assertEquals("Filtered value", $results->First()->HiddenValue);
215
    }
216
217
    public function testSearchContextSummary()
218
    {
219
        $filters = [
220
            'KeywordSearch' => PartialMatchFilter::create('KeywordSearch'),
221
            'Country' => PartialMatchFilter::create('Country'),
222
            'CategoryID' => PartialMatchFilter::create('CategoryID'),
223
            'Featured' => PartialMatchFilter::create('Featured'),
224
            'Nothing' => PartialMatchFilter::create('Nothing'),
225
        ];
226
227
        $fields = FieldList::create(
228
            TextField::create('KeywordSearch', 'Keywords'),
229
            TextField::create('Country', 'Country'),
230
            DropdownField::create('CategoryID', 'Category', [
231
                1 => 'Category one',
232
                2 => 'Category two',
233
            ]),
234
            CheckboxField::create('Featured', 'Featured')
235
        );
236
237
        $context = SearchContext::create(
238
            SearchContextTest\Person::class,
239
            $fields,
240
            $filters
241
        );
242
243
        $context->setSearchParams([
244
            'KeywordSearch' => 'tester',
245
            'Country' => null,
246
            'CategoryID' => 2,
247
            'Featured' => 1,
248
            'Nothing' => 'empty',
249
        ]);
250
251
        $list = $context->getSummary();
252
253
        $this->assertEquals(3, $list->count());
254
        // KeywordSearch should be in the summary
255
        $keyword = $list->find('Field', 'Keywords');
256
        $this->assertNotNull($keyword);
257
        $this->assertEquals('tester', $keyword->Value);
258
259
        // Country should be skipped over
260
        $country = $list->find('Field', 'Country');
261
        $this->assertNull($country);
262
263
        // Category should be expressed as the label
264
        $category = $list->find('Field', 'Category');
265
        $this->assertNotNull($category);
266
        $this->assertEquals('Category two', $category->Value);
267
268
        // Featured should have no value, since it's binary
269
        $featured = $list->find('Field', 'Featured');
270
        $this->assertNotNull($featured);
271
        $this->assertNull($featured->Value);
272
273
        // "Nothing" should come back null since there's no field for it
274
        $nothing = $list->find('Field', 'Nothing');
275
        $this->assertNull($nothing);
276
    }
277
278
    public function testPrimarySearch()
279
    {
280
        $primary1 = $this->objFromFixture(SearchContextTest\PrimarySearch::class, 'primary1');
281
        $context = $primary1->getDefaultSearchContext();
282
        $primaryField = $primary1->primarySearchFieldName();
283
284
        // Matches on a variety of fields
285
        $results = $context->getResults([$primaryField => 'Primary']);
286
        $this->assertCount(1, $results);
287
        $results = $context->getResults([$primaryField => 'brown']);
288
        $this->assertCount(1, $results);
289
290
        // Respects ExactMatchFilter
291
        $results = $context->getResults([$primaryField => 'exact']);
292
        $this->assertCount(0, $results);
293
        $results = $context->getResults([$primaryField => 'This requires an exact match']);
294
        $this->assertCount(1, $results);
295
296
        // Respects PartialMatchFilter
297
        $results = $context->getResults([$primaryField => 'partial']);
298
        $this->assertCount(1, $results);
299
300
        // Uses match_any fields
301
        $results = $context->getResults([$primaryField => 'first']);
302
        $this->assertCount(1, $results);
303
        // Even across a relation
304
        $results = $context->getResults([$primaryField => 'arbitrary']);
305
        $this->assertCount(1, $results);
306
    }
307
308
    public function testNonPrimarySearchFields()
309
    {
310
        $primary1 = $this->objFromFixture(SearchContextTest\PrimarySearch::class, 'primary1');
311
        $context = $primary1->getDefaultSearchContext();
312
        $primaryField = $primary1->primarySearchFieldName();
313
        $results = $context->getResults([$primaryField => $primary1->ExcludeThisField]);
0 ignored issues
show
Bug Best Practice introduced by
The property ExcludeThisField does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
314
        $this->assertNotEmpty($primary1->ExcludeThisField);
315
        $this->assertCount(0, $results);
316
    }
317
318
    public function testPrimaryOnlyUsesSearchableFields()
319
    {
320
        $primary1 = $this->objFromFixture(SearchContextTest\PrimarySearch::class, 'primary1');
321
        $context = $primary1->getDefaultSearchContext();
322
        $primaryField = $primary1->primarySearchFieldName();
323
        $results = $context->getResults([$primaryField => $primary1->DoNotUseThisField]);
0 ignored issues
show
Bug Best Practice introduced by
The property DoNotUseThisField does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
324
        $this->assertNotEmpty($primary1->DoNotUseThisField);
325
        $this->assertCount(0, $results);
326
    }
327
328
    public function testPrimarySearchDisabled()
329
    {
330
        Config::modify()->set(SearchContextTest\PrimarySearch::class, 'primary_search_field_name', '');
331
        $primary1 = $this->objFromFixture(SearchContextTest\PrimarySearch::class, 'primary1');
332
        $context = $primary1->getDefaultSearchContext();
333
        $primaryField = $primary1->primarySearchFieldName();
334
        $this->assertEmpty($primaryField);
335
336
        // Defaults to returning all objects, because the field doesn't exist in the SearchContext
337
        $numObjs = SearchContextTest\PrimarySearch::get()->count();
338
        $results = $context->getResults([$primaryField => 'Primary']);
339
        $this->assertCount($numObjs, $results);
340
        $results = $context->getResults([$primaryField => 'brown']);
341
        $this->assertCount($numObjs, $results);
342
343
        // Searching on other fields still works as expected (e.g. first field, which is the UI default in this situation)
344
        $results = $context->getResults(['Name' => 'Primary']);
345
        $this->assertCount(1, $results);
346
    }
347
348
    public function testPrimarySearchCustomFieldName()
349
    {
350
        Config::modify()->set(SearchContextTest\PrimarySearch::class, 'primary_search_field_name', 'some_arbitrary_field_name');
351
        $obj = new SearchContextTest\PrimarySearch();
352
        $this->assertSame('some_arbitrary_field_name', $obj->primarySearchFieldName());
353
        $this->testPrimarySearch();
354
    }
355
356
    public function testprimarySearchFieldNameMustBeUnique()
357
    {
358
        Config::modify()->set(SearchContextTest\PrimarySearch::class, 'primary_search_field_name', 'MatchAny');
359
        $primary1 = $this->objFromFixture(SearchContextTest\PrimarySearch::class, 'primary1');
360
        $this->expectException(LogicException::class);
361
        $primary1->getDefaultSearchContext();
362
    }
363
364
    public function testMatchAnySearch()
365
    {
366
        $order1 = $this->objFromFixture(SearchContextTest\Order::class, 'order1');
367
        $context = $order1->getDefaultSearchContext();
368
369
        // Search should match Order's customer FirstName
370
        $results = $context->getResults(['CustomFirstName' => 'Bill']);
371
        $this->assertCount(2, $results);
372
        $this->assertListContains([
373
            ['Name' => 'Jane'],
374
            ['Name' => 'Jack'],
375
        ], $results);
376
377
        // Search should match Order's shipping address FirstName
378
        $results = $context->getResults(['CustomFirstName' => 'Bob']);
379
        $this->assertCount(2, $results);
380
        $this->assertListContains([
381
            ['Name' => 'Jane'],
382
            ['Name' => 'Jill'],
383
        ], $results);
384
385
        // Search should match Order's Name db field
386
        $results = $context->getResults(['CustomFirstName' => 'Jane']);
387
        $this->assertCount(1, $results);
388
        $this->assertSame('Jane', $results->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...
389
390
        // Search should not match any Order
391
        $results = $context->getResults(['CustomFirstName' => 'NoMatches']);
392
        $this->assertCount(0, $results);
393
    }
394
395
    public function testMatchAnySearchWithFilters()
396
    {
397
        $order1 = $this->objFromFixture(SearchContextTest\Order::class, 'order1');
398
        $context = $order1->getDefaultSearchContext();
399
400
        $results = $context->getResults(['ExactMatchField' => 'Bil']);
401
        $this->assertCount(0, $results);
402
        $results = $context->getResults(['PartialMatchField' => 'Bil']);
403
        $this->assertCount(2, $results);
404
405
        $results = $context->getResults(['ExactMatchField' => 'ob']);
406
        $this->assertCount(0, $results);
407
        $results = $context->getResults(['PartialMatchField' => 'ob']);
408
        $this->assertCount(2, $results);
409
410
        $results = $context->getResults(['ExactMatchField' => 'an']);
411
        $this->assertCount(0, $results);
412
        $results = $context->getResults(['PartialMatchField' => 'an']);
413
        $this->assertCount(1, $results);
414
    }
415
}
416