Skip to content

Commit 54cabf2

Browse files
authored
Merge pull request #28 from XbyOrange/v1.3.0
V1.3.0
2 parents f4aee87 + 00b3bd3 commit 54cabf2

10 files changed

+306
-53
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1414
## [TO BE DEPRECATED]
1515
- Last argument of Selectors will stop being assigned as "defaultValue". To define default value, it will be mandatory to pass an options object as last argument, containing a "defaultValue" property.
1616

17+
## [1.3.0] - 2019-10-14
18+
### Added
19+
- defaultValue argument in Origin Constructor now can be a function. It will be called to obtain the defaultValue, passing to it the current query as argument.
20+
- Add utility for testing catch functions of selector sources.
21+
1722
## [1.2.0] - 2019-10-14
1823
### Added
1924
- Accept options object in Origin constructor as last argument.

docs/origin/api.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ If you don't define one of this methods, the correspondant CRUD method will not
99
Call `super` from your own constructor, passing:
1010
* `super([id, defaultValue, options])`
1111
* Arguments:
12-
* `defaultId` Used for debugging purposes. The `_id` of the resultant source will be a hash calculated using this default id and default value.
13-
* `defaultValue` Resultant origin will have this value in the `value` property until data is fetched.
14-
* `options` Object containing another options, such as:
15-
* `uuid` If provided, the resultant instance will have this property as `_id`. It will not be "hashed".
16-
* `tags` Tags to be assigned to the instance when created. Tags are used by "sources" handler. For further info [read the `sources` documentation](../sources/api.md).
12+
* `defaultId` - `<String>` Used for debugging purposes. The `_id` of the resultant source will be a hash calculated using this default id and default value (only in case it is not a callback).
13+
* `defaultValue` - `<Any>` Resultant origin will have this value in the `value` property until data is fetched. If a `<Function>` is provided, it will be executed to obtain the default value, passing the current `query` as argument.
14+
* `options` - `<Object>` Object containing another options, such as:
15+
* `uuid` - `<String>` If provided, the resultant instance will have this property as `_id`. It will not be "hashed".
16+
* `tags` - `<String> or <Array of Strings>` Tags to be assigned to the instance when created. Tags are used by "sources" handler. For further info [read the `sources` documentation](../sources/api.md).
1717

1818
Crud methods will receive two arguments:
1919

docs/selector/testing.md

+79-16
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,40 @@
22

33
Selectors provides a test api for making easier developing unit tests.
44

5-
#### Testing custom queries
5+
#### Testing selector functions
66

77
```js
8-
booksCollection.addCustomQuery({
9-
myQuery: id => ({
8+
const mySelector = new Selector({
9+
booksCollection
10+
}, results => results[0]);
11+
```
12+
13+
```js
14+
expect(mySelector.test.selector(["foo"])).toEqual("foo");
15+
```
16+
17+
#### Testing selector queries
18+
19+
```js
20+
const mySelector = new Selector({
21+
source: booksCollection,
22+
query: id => {
1023
params: {
1124
id
1225
}
13-
})
14-
});
15-
26+
}
27+
}, result => result);
1628
```
1729

1830
```js
19-
expect(booksCollection.test.customQueries.myQuery("foo")).toEqual({
31+
expect(mySelector.test.queries[0]("foo")).toEqual({
2032
params: {
2133
id: "foo"
2234
}
2335
});
2436
```
2537

26-
#### Testing selector queries
38+
#### Testing selector sources "catch" methods
2739

2840
```js
2941
const mySelector = new Selector({
@@ -32,26 +44,77 @@ const mySelector = new Selector({
3244
params: {
3345
id
3446
}
35-
}
47+
},
48+
catch: err => err.message
3649
}, result => result);
3750
```
3851

3952
```js
40-
expect(mySelector.test.queries[0]("foo")).toEqual({
53+
expect(mySelector.test.catches[0](new Error("foo"))).toEqual("foo");
54+
```
55+
56+
#### Testing selector queries and catches when sources are concurrent
57+
58+
For concurrent sources, testing objects will be exposed inside an array in the same order than sources are declared.
59+
60+
```js
61+
const mySelector = new Selector(
62+
[{
63+
source: booksCollection,
64+
query: id => {
65+
params: {
66+
bookId
67+
}
68+
},
69+
catch: () => "Error retrieving books";
70+
},
71+
{
72+
source: authorsCollection,
73+
query: id => {
74+
params: {
75+
authorId
76+
}
77+
},
78+
catch: () => "Error retrieving authors";
79+
}]
80+
, result => result);
81+
```
82+
83+
```js
84+
expect(mySelector.test.queries[0][0]("foo")).toEqual({
4185
params: {
42-
id: "foo"
86+
bookId: "foo"
87+
}
88+
});
89+
90+
expect(mySelector.test.queries[0][1]("foo")).toEqual({
91+
params: {
92+
authorId: "foo"
4393
}
4494
});
95+
96+
expect(mySelector.test.catches[0][0]()).toEqual("Error retrieving books");
97+
98+
expect(mySelector.test.catches[0][1]()).toEqual("Error retrieving authors");
4599
```
46100

47-
#### Testing selector functions
101+
#### Testing custom queries
48102

49103
```js
50-
const mySelector = new Selector({
51-
booksCollection
52-
}, results => results[0]);
104+
booksCollection.addCustomQuery({
105+
myQuery: id => ({
106+
params: {
107+
id
108+
}
109+
})
110+
});
111+
53112
```
54113

55114
```js
56-
expect(mySelector.test.selector(["foo"])).toEqual("foo");
115+
expect(booksCollection.test.customQueries.myQuery("foo")).toEqual({
116+
params: {
117+
id: "foo"
118+
}
119+
});
57120
```

package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@xbyorange/mercury",
3-
"version": "1.2.0",
3+
"version": "1.3.0",
44
"description": "Mercury. Reactive CRUD data layer",
55
"keywords": [
66
"reactive",

src/Origin.js

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { isEqual, cloneDeep, merge } from "lodash";
2-
import { mergeCloned } from "./helpers";
1+
import { isEqual, cloneDeep, merge, isFunction } from "lodash";
32

43
import { Cache } from "./Cache";
54
import { EventEmitter } from "./EventEmitter";
@@ -17,7 +16,8 @@ import {
1716
ensureArray,
1817
removeFalsy,
1918
queriedUniqueId,
20-
isUndefined
19+
isUndefined,
20+
mergeCloned
2121
} from "./helpers";
2222

2323
let automaticIdCounter = 0;
@@ -32,7 +32,10 @@ export class Origin {
3232
this._eventEmitter = new EventEmitter();
3333
this._queries = {};
3434

35-
this._defaultValue = !isUndefined(defaultValue) ? cloneDeep(defaultValue) : defaultValue;
35+
this._defaultValue =
36+
!isUndefined(defaultValue) && !isFunction(defaultValue)
37+
? cloneDeep(defaultValue)
38+
: defaultValue;
3639
this._id = options.uuid || uniqueId(defaultId || getAutomaticId(), this._defaultValue);
3740
this._cache = new Cache(this._eventEmitter, this._id);
3841

@@ -178,7 +181,12 @@ export class Origin {
178181

179182
methods[methodName] = dispatchMethod;
180183
methods[methodName].dispatch = dispatchMethod;
181-
methods[methodName].value = methodName === READ_METHOD ? this._defaultValue : undefined;
184+
methods[methodName].value =
185+
methodName === READ_METHOD
186+
? isFunction(this._defaultValue)
187+
? this._defaultValue(query)
188+
: this._defaultValue
189+
: undefined;
182190
methods[methodName].error = null;
183191
methods[methodName].loading = false;
184192
methods[methodName]._source = methods;

src/Selector.js

+18-8
Original file line numberDiff line numberDiff line change
@@ -35,29 +35,39 @@ export class Selector extends Origin {
3535

3636
const sourceIds = [];
3737

38-
const getTestQueries = sourcesOfLevel => {
38+
const getTestObjects = sourcesOfLevel => {
3939
const queries = [];
40+
const catches = [];
4041
sourcesOfLevel.forEach(source => {
4142
if (Array.isArray(source)) {
42-
queries.push(getTestQueries(source));
43+
const testObjects = getTestObjects(source);
44+
queries.push(testObjects.queries);
45+
catches.push(testObjects.catches);
4346
} else {
44-
const hasQuery = !!source.source;
45-
sourceIds.push(hasQuery ? source.source._id : source._id);
46-
if (hasQuery) {
47+
const isSourceObject = !!source.source;
48+
sourceIds.push(isSourceObject ? source.source._id : source._id);
49+
if (isSourceObject && source.query) {
4750
queries.push(source.query);
4851
}
52+
if (isSourceObject && source.catch) {
53+
catches.push(source.catch);
54+
}
4955
}
5056
});
51-
return queries;
57+
return {
58+
queries,
59+
catches
60+
};
5261
};
5362

54-
const testQueries = getTestQueries(sources);
63+
const testObjects = getTestObjects(sources);
5564

5665
super(`select:${sourceIds.join(":")}`, defaultValue, options);
5766

5867
this._sources = sources;
5968
this._resultsParser = args[lastIndex];
60-
this.test.queries = testQueries;
69+
this.test.queries = testObjects.queries;
70+
this.test.catches = testObjects.catches;
6171
this.test.selector = this._resultsParser;
6272
}
6373

src/helpers.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { cloneDeep, merge } from "lodash";
1+
import { cloneDeep, merge, isFunction } from "lodash";
22

33
const CACHE_EVENT_PREFIX = "clean-cache-";
44
const CHANGE_EVENT_PREFIX = "change-";
@@ -64,7 +64,8 @@ export const queryId = query => (isUndefined(query) ? query : `(${JSON.stringify
6464

6565
export const dashJoin = arr => arr.filter(val => !isUndefined(val)).join("-");
6666

67-
export const uniqueId = (id, defaultValue) => hash(`${id}${JSON.stringify(defaultValue)}`);
67+
export const uniqueId = (id, defaultValue) =>
68+
hash(`${id}${isFunction(defaultValue) ? "" : JSON.stringify(defaultValue)}`);
6869

6970
export const queriedUniqueId = (uuid, queryUniqueId) => dashJoin([uuid, queryUniqueId]);
7071

test/Origin.defaultValueCallback.js

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
const test = require("mocha-sinon-chai");
2+
3+
const { Origin, sources } = require("../src/Origin");
4+
5+
test.describe("Origin defaultValue as callback", () => {
6+
let sandbox;
7+
8+
test.beforeEach(() => {
9+
sandbox = test.sinon.createSandbox();
10+
});
11+
12+
test.afterEach(() => {
13+
sandbox.restore();
14+
sources.clear();
15+
});
16+
17+
test.describe("when Origin has defaultValue callback defined", () => {
18+
test.describe("read method", () => {
19+
test.describe("without query", () => {
20+
test.it(
21+
"should return the result of defaultValue callback until real value is returned",
22+
() => {
23+
const TestOrigin = class extends Origin {
24+
constructor(id, defaultValue, options) {
25+
const getDefaultValue = () => {
26+
return defaultValue + 2;
27+
};
28+
super(id, getDefaultValue, options);
29+
}
30+
31+
_read() {
32+
return Promise.resolve(5);
33+
}
34+
};
35+
const testOrigin = new TestOrigin("", 4);
36+
test.expect(testOrigin.read.value).to.equal(6);
37+
return testOrigin.read().then(() => {
38+
return test.expect(testOrigin.read.value).to.equal(5);
39+
});
40+
}
41+
);
42+
});
43+
44+
test.describe("with query", () => {
45+
const QUERY = "foo-query";
46+
const TestOrigin = class extends Origin {
47+
constructor(id, defaultValue, options) {
48+
const getDefaultValue = query => {
49+
return query;
50+
};
51+
super(id, getDefaultValue, options);
52+
}
53+
54+
_read() {
55+
return Promise.resolve("foo-result");
56+
}
57+
};
58+
59+
test.describe("with simple query", () => {
60+
test.it(
61+
"should pass query to defaultValue callback, and return the result until real value is returned",
62+
() => {
63+
const testOrigin = new TestOrigin("", 4).query(QUERY);
64+
test.expect(testOrigin.read.value).to.equal("foo-query");
65+
return testOrigin.read().then(() => {
66+
return test.expect(testOrigin.read.value).to.equal("foo-result");
67+
});
68+
}
69+
);
70+
});
71+
72+
test.describe("with chained query", () => {
73+
test.it(
74+
"should pass chained query to defaultValue callback, and return the result until real value is returned",
75+
() => {
76+
const testOrigin = new TestOrigin("", 4)
77+
.query({
78+
foo: "foo"
79+
})
80+
.query({
81+
foo2: "foo2"
82+
});
83+
test.expect(testOrigin.read.value).to.deep.equal({
84+
foo: "foo",
85+
foo2: "foo2"
86+
});
87+
return testOrigin.read().then(() => {
88+
return test.expect(testOrigin.read.value).to.equal("foo-result");
89+
});
90+
}
91+
);
92+
});
93+
});
94+
});
95+
});
96+
});

0 commit comments

Comments
 (0)