Skip to content

Commit 5f5ad93

Browse files
authored
fix: falsy boolean queries (#13)
* test: mock engine should not give values by reference * test: allow stubbing fs without specifying a filesystem * refactor: make Engine.configuration public * fix: allow falsy matching queries * refactor: unnecessary return await * refactor: avoid square-bracket notation * refactor: empty functions * refactor: variable name shadows variable in outer scope * refactor: class methods should utilize this * refactor: all code paths should have explicit returns, or none * refactor: remove shorthand type coercions * refactor: usage of exported name as property of default import * docs: add jsdoc blocks for test models * docs: add jsdoc block for test model collection * docs: add jsdoc blocks tyo all type classes * docs: add jsdoc block for complex types * docs: add jsdoc blocks for engine classes * docs: add jsdoc block for calledWith test function * refactor: file retrival consistency * refactor: async function should have await expression
1 parent 1e32454 commit 5f5ad93

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2303
-1550
lines changed

src/Persist.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import enableTransactions from './Transactions.js';
44
/**
55
* @class Persist
66
*/
7-
export default class Persist {
7+
class Persist {
88
static _engine = {};
99
/**
1010
* @memberof Persist
@@ -42,3 +42,5 @@ export default class Persist {
4242
engine.configure(configuration);
4343
}
4444
}
45+
46+
export default Persist;

src/Persist.test.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import test from 'ava';
55
class TestEngine {
66
static configure(configuration = {}) {
77
class ConfiguredTestEngine extends TestEngine {
8-
static _configuration = configuration;
8+
static configuration = configuration;
99
}
1010

1111
return ConfiguredTestEngine;
@@ -19,13 +19,13 @@ test('includes Type', async t => {
1919
test('.addEngine(group, engine, configuration) adds and configures an engine', t => {
2020
Persist.addEngine('one', TestEngine, {test: true});
2121

22-
t.like(Persist._engine.one.TestEngine._configuration, {test: true});
22+
t.like(Persist._engine.one.TestEngine.configuration, {test: true});
2323
});
2424

2525
test('.getEngine(group, engine) retrieves an engine', t => {
2626
Persist.addEngine('one', TestEngine, {test: true});
2727

28-
t.like(Persist.getEngine('one', TestEngine)._configuration, {test: true});
28+
t.like(Persist.getEngine('one', TestEngine).configuration, {test: true});
2929
});
3030

3131
test('.getEngine(group, nonEngine) retrieves no engines', t => {

src/Query.js

+37-16
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,60 @@
11
/**
2-
* persist query language features:
3-
* - value match {title: 'test'} or {title: {$is: 'test'}}
4-
* - contains match {list: {$contains: 'test'}} or {string: {$contains: 'es'}}
5-
* - nested query {list: {$contains: {slug: 'test'}}}
6-
* - deep nesting queries {list: {$contains: {string: {$contains: 'test'}}}}
7-
*/
8-
9-
/**
10-
* @class Query
2+
* The `Query` class is responsible for executing searches on an indexed dataset
3+
* based on a structured query. It supports various query types including value matches,
4+
* contains matches, and nested queries.
5+
*
6+
* @example
7+
* // The object has the property `title` witch exactly equals `test`.
8+
* const query = new Query({title: 'test'});
9+
* const query = new Query({title: {$is: 'test'}});
10+
*
11+
* // The object has the property `list` witch contains the string `test`.
12+
* const query = new Query({list: {$contains: 'test'}});
13+
*
14+
* // The object has the property `string` witch contains the string `es`.
15+
* const query = new Query({string: {$contains: 'es'}});
16+
*
17+
* // The object has the property `list` contains an object
18+
* // with a property `string` that contains the string `test`.
19+
* const query = new Query({
20+
* list: {
21+
* $contains: {
22+
* string: {
23+
* $contains: 'test'
24+
* }
25+
* }
26+
* }
27+
* });
1128
*/
1229
class Query {
30+
/**
31+
* The query object that defines the search criteria.
32+
* @type {Object}
33+
*/
1334
query;
1435

1536
/**
37+
* Constructs a new `Query` instance with the provided query object.
1638
*
17-
* @param {object} query
39+
* @param {Object} query - The structured query object defining the search criteria.
1840
*/
1941
constructor(query) {
2042
this.query = query;
2143
}
2244

2345
/**
24-
* Using the input query, find records in an index that match
46+
* Executes the query against a model's index and returns the matching results.
2547
*
26-
* @param {typeof Model} model
27-
* @param {object} index
48+
* @param {Model.constructor} model - The model class that contains the `fromData` method for constructing models from data.
49+
* @param {Object<string, Model>} index - The index dataset to search through.
50+
* @returns {Array<Model>} The models that match the query.
2851
*/
2952
execute(model, index) {
30-
const matchIs = (query) => !!query?.$is;
53+
const matchIs = (query) => query?.$is !== undefined;
3154
const matchPrimitive = (query) => ['string', 'number', 'boolean'].includes(typeof query);
3255
const matchContains = (query) => !!query?.$contains;
3356

3457
const matchesQuery = (subject, inputQuery = this.query) => {
35-
if (!subject || !inputQuery) return false;
36-
3758
if (matchPrimitive(inputQuery)) return subject === inputQuery;
3859

3960
if (matchIs(inputQuery))

src/Query.test.js

+95-100
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import {MainModel} from '../test/fixtures/TestModel.js';
1+
import {MainModel} from '../test/fixtures/Models.js';
2+
import {Models} from '../test/fixtures/ModelCollection.js';
23
import Query from './Query.js';
3-
import {TestIndex} from '../test/fixtures/TestIndex.js';
44
import test from 'ava';
55

66
test('new Query(query) stores the query', t => {
@@ -9,122 +9,117 @@ test('new Query(query) stores the query', t => {
99
t.deepEqual(query.query, {string: 'test'});
1010
});
1111

12-
test('Query.execute(index) finds exact matches with primitive types', t => {
12+
test('Query.execute(index) finds exact string matches with primitive type', t => {
13+
const models = new Models();
14+
const model = models.createFullTestModel();
15+
1316
const query = new Query({string: 'test'});
14-
const results = query.execute(MainModel, TestIndex);
15-
16-
t.deepEqual(results, [
17-
MainModel.fromData({
18-
id: 'MainModel/000000000000',
19-
string: 'test',
20-
arrayOfString: ['test'],
21-
linkedMany: [{
22-
id: 'LinkedManyModel/000000000000000',
23-
string: 'test',
24-
}],
25-
}),
26-
]);
17+
18+
const results = query.execute(MainModel, models.getIndex(MainModel));
19+
20+
t.like(results, [model.toIndexData()]);
2721
});
2822

29-
test('Query.execute(index) finds exact matches with $is', t => {
23+
test('Query.execute(index) finds exact string matches with $is', t => {
24+
const models = new Models();
25+
const model = models.createFullTestModel();
26+
models.createFullTestModel({string: 'another test'});
27+
3028
const query = new Query({string: {$is: 'test'}});
31-
const results = query.execute(MainModel, TestIndex);
32-
33-
t.deepEqual(results, [
34-
MainModel.fromData({
35-
id: 'MainModel/000000000000',
36-
string: 'test',
37-
arrayOfString: ['test'],
38-
linkedMany: [{
39-
id: 'LinkedManyModel/000000000000000',
40-
string: 'test',
41-
}],
42-
}),
43-
]);
29+
const results = query.execute(MainModel, models.getIndex(MainModel));
30+
31+
t.like(results, [model.toIndexData()]);
32+
});
33+
34+
test('Query.execute(index) finds exact boolean matches with primitive type', t => {
35+
const models = new Models();
36+
const model = models.createFullTestModel();
37+
38+
const query = new Query({boolean: false});
39+
const results = query.execute(MainModel, models.getIndex(MainModel));
40+
41+
t.like(results, [model.toIndexData()]);
42+
});
43+
44+
test('Query.execute(index) finds exact boolean matches with $is', t => {
45+
const models = new Models();
46+
const model = models.createFullTestModel();
47+
48+
const query = new Query({boolean: {$is: false}});
49+
const results = query.execute(MainModel, models.getIndex(MainModel));
50+
51+
t.like(results, [model.toIndexData()]);
52+
});
53+
54+
test('Query.execute(index) finds exact number matches with $is', t => {
55+
const models = new Models();
56+
const model = models.createFullTestModel();
57+
58+
const query = new Query({number: {$is: 24.3}});
59+
const results = query.execute(MainModel, models.getIndex(MainModel));
60+
61+
t.like(results, [model.toIndexData()]);
62+
});
63+
64+
test('Query.execute(index) finds exact number matches with primitive type', t => {
65+
const models = new Models();
66+
const model = models.createFullTestModel();
67+
68+
const query = new Query({number: 24.3});
69+
const results = query.execute(MainModel, models.getIndex(MainModel));
70+
71+
t.like(results, [model.toIndexData()]);
4472
});
4573

4674
test('Query.execute(index) finds matches containing for strings', t => {
75+
const models = new Models();
76+
const model1 = models.createFullTestModel();
77+
const model2 = models.createFullTestModel();
78+
models.createFullTestModel({string: 'not matching'});
79+
80+
model2.string = 'testing';
81+
4782
const query = new Query({string: {$contains: 'test'}});
48-
const results = query.execute(MainModel, TestIndex);
49-
50-
t.deepEqual(results, [
51-
MainModel.fromData({
52-
id: 'MainModel/000000000000',
53-
string: 'test',
54-
arrayOfString: ['test'],
55-
linkedMany: [{
56-
id: 'LinkedManyModel/000000000000000',
57-
string: 'test',
58-
}],
59-
}),
60-
MainModel.fromData({
61-
id: 'MainModel/111111111111',
62-
string: 'testing',
63-
arrayOfString: ['testing'],
64-
linkedMany: [{
65-
id: 'LinkedManyModel/111111111111',
66-
string: 'testing',
67-
}],
68-
}),
83+
const results = query.execute(MainModel, models.getIndex(MainModel));
84+
85+
t.like(results, [
86+
model1.toIndexData(),
87+
model2.toIndexData(),
6988
]);
7089
});
7190

7291
test('Query.execute(index) finds matches containing for arrays', t => {
92+
const models = new Models();
93+
const model = models.createFullTestModel();
94+
7395
const query = new Query({arrayOfString: {$contains: 'test'}});
74-
const results = query.execute(MainModel, TestIndex);
75-
76-
t.deepEqual(results, [
77-
MainModel.fromData({
78-
id: 'MainModel/000000000000',
79-
string: 'test',
80-
arrayOfString: ['test'],
81-
linkedMany: [{
82-
id: 'LinkedManyModel/000000000000000',
83-
string: 'test',
84-
}],
85-
}),
86-
]);
96+
const results = query.execute(MainModel, models.getIndex(MainModel));
97+
98+
t.like(results, [model.toIndexData()]);
8799
});
88100

89101
test('Query.execute(index) finds exact matches for elements in arrays', t => {
90-
const query = new Query({linkedMany: {$contains: {string: 'test'}}});
91-
const results = query.execute(MainModel, TestIndex);
92-
93-
t.deepEqual(results, [
94-
MainModel.fromData({
95-
id: 'MainModel/000000000000',
96-
string: 'test',
97-
arrayOfString: ['test'],
98-
linkedMany: [{
99-
id: 'LinkedManyModel/000000000000000',
100-
string: 'test',
101-
}],
102-
}),
103-
]);
102+
const models = new Models();
103+
const model = models.createFullTestModel();
104+
105+
const query = new Query({linkedMany: {$contains: {string: 'many'}}});
106+
const results = query.execute(MainModel, models.getIndex(MainModel));
107+
108+
t.like(results, [model.toIndexData()]);
104109
});
105110

106111
test('Query.execute(index) finds partial matches for elements in arrays', t => {
107-
const query = new Query({linkedMany: {$contains: {string: {$contains: 'test'}}}});
108-
const results = query.execute(MainModel, TestIndex);
109-
110-
t.deepEqual(results, [
111-
MainModel.fromData({
112-
id: 'MainModel/000000000000',
113-
string: 'test',
114-
arrayOfString: ['test'],
115-
linkedMany: [{
116-
id: 'LinkedManyModel/000000000000000',
117-
string: 'test',
118-
}],
119-
}),
120-
MainModel.fromData({
121-
id: 'MainModel/111111111111',
122-
string: 'testing',
123-
arrayOfString: ['testing'],
124-
linkedMany: [{
125-
id: 'LinkedManyModel/111111111111',
126-
string: 'testing',
127-
}],
128-
}),
112+
const models = new Models();
113+
const model1 = models.createFullTestModel();
114+
const model2 = models.createFullTestModel();
115+
116+
model2.linkedMany[0].string = 'many tests';
117+
118+
const query = new Query({linkedMany: {$contains: {string: {$contains: 'many'}}}});
119+
const results = query.execute(MainModel, models.getIndex(MainModel));
120+
121+
t.like(results, [
122+
model1.toIndexData(),
123+
model2.toIndexData(),
129124
]);
130125
});

0 commit comments

Comments
 (0)