Skip to content

Commit

Permalink
Merge pull request #28 from XbyOrange/v1.3.0
Browse files Browse the repository at this point in the history
V1.3.0
  • Loading branch information
javierbrea authored Oct 14, 2019
2 parents f4aee87 + 00b3bd3 commit 54cabf2
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 53 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [TO BE DEPRECATED]
- 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.

## [1.3.0] - 2019-10-14
### Added
- 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.
- Add utility for testing catch functions of selector sources.

## [1.2.0] - 2019-10-14
### Added
- Accept options object in Origin constructor as last argument.
Expand Down
10 changes: 5 additions & 5 deletions docs/origin/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ If you don't define one of this methods, the correspondant CRUD method will not
Call `super` from your own constructor, passing:
* `super([id, defaultValue, options])`
* Arguments:
* `defaultId` Used for debugging purposes. The `_id` of the resultant source will be a hash calculated using this default id and default value.
* `defaultValue` Resultant origin will have this value in the `value` property until data is fetched.
* `options` Object containing another options, such as:
* `uuid` If provided, the resultant instance will have this property as `_id`. It will not be "hashed".
* `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).
* `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).
* `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.
* `options` - `<Object>` Object containing another options, such as:
* `uuid` - `<String>` If provided, the resultant instance will have this property as `_id`. It will not be "hashed".
* `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).

Crud methods will receive two arguments:

Expand Down
95 changes: 79 additions & 16 deletions docs/selector/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,40 @@

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

#### Testing custom queries
#### Testing selector functions

```js
booksCollection.addCustomQuery({
myQuery: id => ({
const mySelector = new Selector({
booksCollection
}, results => results[0]);
```

```js
expect(mySelector.test.selector(["foo"])).toEqual("foo");
```

#### Testing selector queries

```js
const mySelector = new Selector({
source: booksCollection,
query: id => {
params: {
id
}
})
});

}
}, result => result);
```

```js
expect(booksCollection.test.customQueries.myQuery("foo")).toEqual({
expect(mySelector.test.queries[0]("foo")).toEqual({
params: {
id: "foo"
}
});
```

#### Testing selector queries
#### Testing selector sources "catch" methods

```js
const mySelector = new Selector({
Expand All @@ -32,26 +44,77 @@ const mySelector = new Selector({
params: {
id
}
}
},
catch: err => err.message
}, result => result);
```

```js
expect(mySelector.test.queries[0]("foo")).toEqual({
expect(mySelector.test.catches[0](new Error("foo"))).toEqual("foo");
```

#### Testing selector queries and catches when sources are concurrent

For concurrent sources, testing objects will be exposed inside an array in the same order than sources are declared.

```js
const mySelector = new Selector(
[{
source: booksCollection,
query: id => {
params: {
bookId
}
},
catch: () => "Error retrieving books";
},
{
source: authorsCollection,
query: id => {
params: {
authorId
}
},
catch: () => "Error retrieving authors";
}]
, result => result);
```

```js
expect(mySelector.test.queries[0][0]("foo")).toEqual({
params: {
id: "foo"
bookId: "foo"
}
});

expect(mySelector.test.queries[0][1]("foo")).toEqual({
params: {
authorId: "foo"
}
});

expect(mySelector.test.catches[0][0]()).toEqual("Error retrieving books");

expect(mySelector.test.catches[0][1]()).toEqual("Error retrieving authors");
```

#### Testing selector functions
#### Testing custom queries

```js
const mySelector = new Selector({
booksCollection
}, results => results[0]);
booksCollection.addCustomQuery({
myQuery: id => ({
params: {
id
}
})
});

```

```js
expect(mySelector.test.selector(["foo"])).toEqual("foo");
expect(booksCollection.test.customQueries.myQuery("foo")).toEqual({
params: {
id: "foo"
}
});
```
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xbyorange/mercury",
"version": "1.2.0",
"version": "1.3.0",
"description": "Mercury. Reactive CRUD data layer",
"keywords": [
"reactive",
Expand Down
18 changes: 13 additions & 5 deletions src/Origin.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { isEqual, cloneDeep, merge } from "lodash";
import { mergeCloned } from "./helpers";
import { isEqual, cloneDeep, merge, isFunction } from "lodash";

import { Cache } from "./Cache";
import { EventEmitter } from "./EventEmitter";
Expand All @@ -17,7 +16,8 @@ import {
ensureArray,
removeFalsy,
queriedUniqueId,
isUndefined
isUndefined,
mergeCloned
} from "./helpers";

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

this._defaultValue = !isUndefined(defaultValue) ? cloneDeep(defaultValue) : defaultValue;
this._defaultValue =
!isUndefined(defaultValue) && !isFunction(defaultValue)
? cloneDeep(defaultValue)
: defaultValue;
this._id = options.uuid || uniqueId(defaultId || getAutomaticId(), this._defaultValue);
this._cache = new Cache(this._eventEmitter, this._id);

Expand Down Expand Up @@ -178,7 +181,12 @@ export class Origin {

methods[methodName] = dispatchMethod;
methods[methodName].dispatch = dispatchMethod;
methods[methodName].value = methodName === READ_METHOD ? this._defaultValue : undefined;
methods[methodName].value =
methodName === READ_METHOD
? isFunction(this._defaultValue)
? this._defaultValue(query)
: this._defaultValue
: undefined;
methods[methodName].error = null;
methods[methodName].loading = false;
methods[methodName]._source = methods;
Expand Down
26 changes: 18 additions & 8 deletions src/Selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,39 @@ export class Selector extends Origin {

const sourceIds = [];

const getTestQueries = sourcesOfLevel => {
const getTestObjects = sourcesOfLevel => {
const queries = [];
const catches = [];
sourcesOfLevel.forEach(source => {
if (Array.isArray(source)) {
queries.push(getTestQueries(source));
const testObjects = getTestObjects(source);
queries.push(testObjects.queries);
catches.push(testObjects.catches);
} else {
const hasQuery = !!source.source;
sourceIds.push(hasQuery ? source.source._id : source._id);
if (hasQuery) {
const isSourceObject = !!source.source;
sourceIds.push(isSourceObject ? source.source._id : source._id);
if (isSourceObject && source.query) {
queries.push(source.query);
}
if (isSourceObject && source.catch) {
catches.push(source.catch);
}
}
});
return queries;
return {
queries,
catches
};
};

const testQueries = getTestQueries(sources);
const testObjects = getTestObjects(sources);

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

this._sources = sources;
this._resultsParser = args[lastIndex];
this.test.queries = testQueries;
this.test.queries = testObjects.queries;
this.test.catches = testObjects.catches;
this.test.selector = this._resultsParser;
}

Expand Down
5 changes: 3 additions & 2 deletions src/helpers.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cloneDeep, merge } from "lodash";
import { cloneDeep, merge, isFunction } from "lodash";

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

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

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

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

Expand Down
96 changes: 96 additions & 0 deletions test/Origin.defaultValueCallback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
const test = require("mocha-sinon-chai");

const { Origin, sources } = require("../src/Origin");

test.describe("Origin defaultValue as callback", () => {
let sandbox;

test.beforeEach(() => {
sandbox = test.sinon.createSandbox();
});

test.afterEach(() => {
sandbox.restore();
sources.clear();
});

test.describe("when Origin has defaultValue callback defined", () => {
test.describe("read method", () => {
test.describe("without query", () => {
test.it(
"should return the result of defaultValue callback until real value is returned",
() => {
const TestOrigin = class extends Origin {
constructor(id, defaultValue, options) {
const getDefaultValue = () => {
return defaultValue + 2;
};
super(id, getDefaultValue, options);
}

_read() {
return Promise.resolve(5);
}
};
const testOrigin = new TestOrigin("", 4);
test.expect(testOrigin.read.value).to.equal(6);
return testOrigin.read().then(() => {
return test.expect(testOrigin.read.value).to.equal(5);
});
}
);
});

test.describe("with query", () => {
const QUERY = "foo-query";
const TestOrigin = class extends Origin {
constructor(id, defaultValue, options) {
const getDefaultValue = query => {
return query;
};
super(id, getDefaultValue, options);
}

_read() {
return Promise.resolve("foo-result");
}
};

test.describe("with simple query", () => {
test.it(
"should pass query to defaultValue callback, and return the result until real value is returned",
() => {
const testOrigin = new TestOrigin("", 4).query(QUERY);
test.expect(testOrigin.read.value).to.equal("foo-query");
return testOrigin.read().then(() => {
return test.expect(testOrigin.read.value).to.equal("foo-result");
});
}
);
});

test.describe("with chained query", () => {
test.it(
"should pass chained query to defaultValue callback, and return the result until real value is returned",
() => {
const testOrigin = new TestOrigin("", 4)
.query({
foo: "foo"
})
.query({
foo2: "foo2"
});
test.expect(testOrigin.read.value).to.deep.equal({
foo: "foo",
foo2: "foo2"
});
return testOrigin.read().then(() => {
return test.expect(testOrigin.read.value).to.equal("foo-result");
});
}
);
});
});
});
});
});
Loading

0 comments on commit 54cabf2

Please sign in to comment.