Skip to content

Commit 774ec43

Browse files
authored
Add new assertion 'assertCount' (#20)
* Add new assertion 'assertCount' * Added 'whereNotIn' filter * fix tests
1 parent 35592b2 commit 774ec43

File tree

7 files changed

+235
-24
lines changed

7 files changed

+235
-24
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/vendor
2+
/.idea
23
/.env
34
composer.phar
45
composer.lock

README.md

+24
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,30 @@ User::factory()->create();
174174
Search::assertNotEmptyIn('users'); // ✅
175175
```
176176

177+
### assertCount($count)
178+
179+
Checks if there is at least one record in any of search indexes.
180+
181+
```php
182+
Search::assertCount(1); // ❌
183+
184+
User::factory()->create();
185+
186+
Search::assertCount(1); // ✅
187+
```
188+
189+
### assertCountIn($index)
190+
191+
Checks if search index is not empty.
192+
193+
```php
194+
Search::assertNotEmptyIn('users', 1); // ❌
195+
196+
User::factory()->create();
197+
198+
Search::assertNotEmptyIn('users', 1); // ✅
199+
```
200+
177201
### assertSynced($model, $callback = null)
178202

179203
Checks if model was synced to search index. This assertion checks every record of the given model which was synced during the request.

src/ArrayStore.php

+5
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ public function mock(string $index, string $key, array $data, bool $merge = true
8383
$this->setData('mocks', $index, Arr::prepend($this->getRecordsExcept('current', $key, $index), $data));
8484
}
8585

86+
public function getDefaultIndex(string $type = 'current'): string
87+
{
88+
return Arr::first(array_keys($this->storage[$type])) ?? '';
89+
}
90+
8691
private function add(string $type, string $index, string $key, array $data, bool $removeOld = true): void
8792
{
8893
$array = &$this->storage[$type];

src/Engines/ArrayEngine.php

+61-23
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public function __construct($store, $softDelete = false)
3434
/**
3535
* Update the given model in the index.
3636
*
37-
* @param Collection $models
37+
* @param Collection $models
3838
* @return void
3939
*/
4040
public function update($models)
@@ -58,7 +58,7 @@ public function update($models)
5858
/**
5959
* Remove the given model from the index.
6060
*
61-
* @param Collection $models
61+
* @param Collection $models
6262
* @return void
6363
*/
6464
public function delete($models)
@@ -84,8 +84,8 @@ public function search(Builder $builder)
8484
/**
8585
* Perform the given search on the engine.
8686
*
87-
* @param int $perPage
88-
* @param int $page
87+
* @param int $perPage
88+
* @param int $page
8989
* @return mixed
9090
*/
9191
public function paginate(Builder $builder, $perPage, $page)
@@ -99,7 +99,7 @@ public function paginate(Builder $builder, $perPage, $page)
9999
/**
100100
* Perform the given search on the engine.
101101
*
102-
* @return mixed
102+
* @return array
103103
*/
104104
protected function performSearch(Builder $builder, array $options = [])
105105
{
@@ -108,9 +108,12 @@ protected function performSearch(Builder $builder, array $options = [])
108108
$matches = $this->store->find($index, function ($record) use ($builder) {
109109
$values = new RecursiveIteratorIterator(new RecursiveArrayIterator($record));
110110

111-
return $this->matchesFilters($record, $builder->wheres) && ! empty(array_filter(iterator_to_array($values, false), function ($value) use ($builder) {
112-
return ! $builder->query || stripos($value, $builder->query) !== false;
113-
}));
111+
return $this->matchesFilters($record, $builder->wheres) &&
112+
$this->matchesFilters($record, $builder->whereIns) &&
113+
$this->matchesFilters($record, data_get($builder, 'whereNotIns', []), true) &&
114+
!empty(array_filter(iterator_to_array($values, false), function ($value) use ($builder) {
115+
return !$builder->query || stripos($value, $builder->query) !== false;
116+
}));
114117
}, true);
115118

116119
$matches = Collection::make($matches);
@@ -124,25 +127,46 @@ protected function performSearch(Builder $builder, array $options = [])
124127
/**
125128
* Determine if the given record matches given filters.
126129
*
127-
* @param array $record
128-
* @param array $filters
130+
* @param array $record
131+
* @param array $filters
132+
* @param bool $not
129133
* @return bool
130134
*/
131-
private function matchesFilters($record, $filters)
135+
private function matchesFilters($record, $filters, $not = false)
132136
{
133137
if (empty($filters)) {
134138
return true;
135139
}
136140

137-
return Collection::make($filters)->every(function ($value, $key) use ($record) {
138-
return $record[$key] === $value;
141+
$match = function ($record, $key, $value) {
142+
if (is_array($value)) {
143+
return in_array(data_get($record, $key), $value, true);
144+
}
145+
return data_get($record, $key) === $value;
146+
};
147+
148+
$match = Collection::make($filters)->every(function ($value, $key) use ($match, $record) {
149+
$keyExploded = explode('.', $key);
150+
if (count($keyExploded) > 1) {
151+
if (data_get($record, $keyExploded[0]) instanceof Collection) {
152+
return data_get($record, $keyExploded[0])->contains(function ($subRecord) use ($match, $keyExploded, $value) {
153+
return $match($subRecord, $keyExploded[1], $value);
154+
});
155+
}
156+
157+
return $match($record, $keyExploded, $value);
158+
}
159+
160+
return $match($record, $key, $value);
139161
});
162+
163+
return $not ? !$match : $match;
140164
}
141165

142166
/**
143167
* Pluck and return the primary keys of the given results.
144168
*
145-
* @param mixed $results
169+
* @param mixed $results
146170
* @return \Illuminate\Support\Collection
147171
*/
148172
public function mapIds($results)
@@ -153,8 +177,8 @@ public function mapIds($results)
153177
/**
154178
* Map the given results to instances of the given model.
155179
*
156-
* @param mixed $results
157-
* @param \Illuminate\Database\Eloquent\Model $model
180+
* @param mixed $results
181+
* @param \Illuminate\Database\Eloquent\Model $model
158182
* @return Collection
159183
*/
160184
public function map(Builder $builder, $results, $model)
@@ -177,8 +201,8 @@ public function map(Builder $builder, $results, $model)
177201
/**
178202
* Map the given results to instances of the given model via a lazy collection.
179203
*
180-
* @param mixed $results
181-
* @param \Illuminate\Database\Eloquent\Model $model
204+
* @param mixed $results
205+
* @param \Illuminate\Database\Eloquent\Model $model
182206
* @return \Illuminate\Support\LazyCollection
183207
*/
184208
public function lazyMap(Builder $builder, $results, $model)
@@ -203,7 +227,7 @@ public function lazyMap(Builder $builder, $results, $model)
203227
/**
204228
* Get the total count from a raw result returned by the engine.
205229
*
206-
* @param mixed $results
230+
* @param mixed $results
207231
* @return int
208232
*/
209233
public function getTotalCount($results)
@@ -214,7 +238,7 @@ public function getTotalCount($results)
214238
/**
215239
* Flush all of the model's records from the engine.
216240
*
217-
* @param \Illuminate\Database\Eloquent\Model $model
241+
* @param \Illuminate\Database\Eloquent\Model $model
218242
* @return void
219243
*/
220244
public function flush($model)
@@ -225,7 +249,7 @@ public function flush($model)
225249
/**
226250
* Create a search index.
227251
*
228-
* @param string $name
252+
* @param string $name
229253
* @return mixed
230254
*/
231255
public function createIndex($name, array $options = [])
@@ -236,7 +260,7 @@ public function createIndex($name, array $options = [])
236260
/**
237261
* Delete a search index.
238262
*
239-
* @param string $name
263+
* @param string $name
240264
* @return mixed
241265
*/
242266
public function deleteIndex($name)
@@ -247,11 +271,25 @@ public function deleteIndex($name)
247271
/**
248272
* Determine if the given model uses soft deletes.
249273
*
250-
* @param \Illuminate\Database\Eloquent\Model $model
274+
* @param \Illuminate\Database\Eloquent\Model $model
251275
* @return bool
252276
*/
253277
protected function usesSoftDelete($model)
254278
{
255279
return in_array(SoftDeletes::class, class_uses_recursive($model));
256280
}
281+
282+
protected function buildSearchQuery(Builder $builder)
283+
{
284+
$query = $this->initializeSearchQuery(
285+
$builder,
286+
array_keys($builder->model->toSearchableArray()),
287+
$this->getPrefixColumns($builder),
288+
$this->getFullTextColumns($builder)
289+
);
290+
291+
return $this->constrainForSoftDeletes(
292+
$builder, $this->addAdditionalConstraints($builder, $query->take($builder->limit))
293+
);
294+
}
257295
}

src/Search.php

+26
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,32 @@ public function assertNotEmptyIn(string $index): self
103103
return $this;
104104
}
105105

106+
public function assertCount(int $count, ?Closure $callback = null): self
107+
{
108+
$countFiltered = count($this->store->find($this->store->getDefaultIndex(), function ($record) use ($callback) {
109+
return ($callback ? $callback($record) : true);
110+
}));
111+
112+
Assert::assertSame(
113+
$countFiltered, $count, 'Failed asserting that search index does not have the expected size.'
114+
);
115+
116+
return $this;
117+
}
118+
119+
public function assertCountIn(string $index, int $count, ?Closure $callback = null): self
120+
{
121+
$countFiltered = count($this->store->find($index, function ($record) use ($callback) {
122+
return ($callback ? $callback($record) : true);
123+
}));
124+
125+
Assert::assertSame(
126+
$countFiltered, $count, "Failed asserting that '{$index}' search index does not have the expected size."
127+
);
128+
129+
return $this;
130+
}
131+
106132
public function assertSynced(Model $model, ?Closure $callback = null): self
107133
{
108134
Assert::assertNotEmpty(

tests/Engines/ArrayEngineTest.php

+44-1
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ public function it_can_flush_all_models_records()
415415
}
416416

417417
/** @test */
418-
public function it_can_be_filtered()
418+
public function it_can_be_filtered_using_wheres()
419419
{
420420
$engine = new ArrayEngine(new ArrayStore());
421421
$engine->update(Collection::make([
@@ -435,6 +435,49 @@ public function it_can_be_filtered()
435435
$this->assertEquals(2, $results['hits'][0]['scoutKey']);
436436
}
437437

438+
/** @test */
439+
public function it_can_be_filtered_using_where_in()
440+
{
441+
$engine = new ArrayEngine(new ArrayStore());
442+
$engine->update(Collection::make([
443+
new SearchableModel(['foo' => 'bar', 'x' => 'y', 'scoutKey' => 1]),
444+
new SearchableModel(['foo' => 'baz', 'x' => 'x', 'scoutKey' => 2]),
445+
new SearchableModel(['foo' => 'bax', 'x' => 'z', 'scoutKey' => 3]),
446+
]));
447+
448+
$builder = new Builder(new SearchableModel(), null);
449+
$builder->whereIns = [
450+
'foo' => ['baz'],
451+
'x' => ['x', 'y']
452+
];
453+
$results = $engine->search($builder);
454+
455+
$this->assertCount(1, $results['hits']);
456+
$this->assertEquals(2, $results['hits'][0]['scoutKey']);
457+
}
458+
459+
/** @test */
460+
public function it_can_be_filtered_using_where_not_in()
461+
{
462+
$engine = new ArrayEngine(new ArrayStore());
463+
$engine->update(Collection::make([
464+
new SearchableModel(['foo' => 'bar', 'x' => 'y', 'scoutKey' => 1]),
465+
new SearchableModel(['foo' => 'baz', 'x' => 'x', 'scoutKey' => 2]),
466+
new SearchableModel(['foo' => 'bax', 'x' => 'z', 'scoutKey' => 3]),
467+
]));
468+
469+
$builder = new Builder(new SearchableModel(), null);
470+
$builder->whereNotIns = [
471+
'foo' => ['baz'],
472+
'x' => ['x', 'y']
473+
];
474+
$results = $engine->search($builder);
475+
476+
$this->assertCount(2, $results['hits']);
477+
$this->assertEquals(3, $results['hits'][0]['scoutKey']);
478+
$this->assertEquals(1, $results['hits'][1]['scoutKey']);
479+
}
480+
438481
/** @test */
439482
public function it_can_create_search_index()
440483
{

0 commit comments

Comments
 (0)