diff --git a/test/app.js b/test/app.js index 58b9074..08c54c8 100644 --- a/test/app.js +++ b/test/app.js @@ -1,5 +1,6 @@ var test = require('tape') var feathers = require('feathers') +var { keys, any, pipe } = require('ramda') var memory = require('feathers-memory') var {createStore, applyMiddleware} = require('redux') var { createEpicMiddleware, combineEpics } = require('redux-observable') @@ -12,7 +13,7 @@ var createModule = require('../') const serviceNames = ['cats', 'dogs'] -test('app works', function (t) { +test('create, update, patch and remove update the store', function (t) { const app = createApp(serviceNames) const {cats, dogs} = createModule(serviceNames) const catActions = cats.actions @@ -26,41 +27,77 @@ test('app works', function (t) { const store = createStore(reducer, applyMiddleware(epicMiddleware)) - const cidCreate = Cid() - const cidUpdate = Cid() - const cidPatch = Cid() - const cidRemove = Cid() + const createCid = Cid() + const updateCid = Cid() + const patchCid = Cid() + const removeCid = Cid() Store$(store) - .filter((store) => store.cats && store.cats.cats[0]) + .filter((store) => store.cats.cats[0]) .take(1) .mergeMap(({cats}) => { t.equal(cats.cats[0].name, 'fluffy') - store.dispatch(catActions.update(cidUpdate, 0, {name: 'tick'})) + store.dispatch(catActions.update(Cid(), 0, {name: 'tick'})) return Store$(store) }) - .filter((store) => store.cats && store.cats.cats[0] && store.cats.cats[0].name === 'tick') + .filter((store) => store.cats.cats[0].name === 'tick') .take(1) .mergeMap(({cats}) => { t.equal(cats.cats[0].name, 'tick') - store.dispatch(catActions.patch(cidPatch, 0, {nickName: 'fatboy'})) + store.dispatch(catActions.patch(Cid(), 0, {nickName: 'fatboy'})) return Store$(store) }) - .filter((store) => store.cats && store.cats.cats[0] && store.cats.cats[0].nickName === 'fatboy') + .filter((store) => store.cats.cats[0] && store.cats.cats[0].nickName === 'fatboy') .take(1) .mergeMap(({cats}) => { t.equal(cats.cats[0].nickName, 'fatboy') - store.dispatch(catActions.remove(cidRemove, 0)) + store.dispatch(catActions.remove(Cid(), 0)) return Store$(store) }) - .filter((store) => store.cats && !store.cats.cats[0]) + .filter((store) => !store.cats.cats[0]) .take(1) .subscribe(() => { t.pass() t.end() }) - store.dispatch(cats.actions.create(cidCreate, {name: 'fluffy'})) + store.dispatch(cats.actions.create(createCid, {name: 'fluffy'})) +}) + +test('create optimistically updates store and then sets the id when request succeeds', function (t) { + const app = createApp(serviceNames) + const {cats, dogs} = createModule(serviceNames) + const catActions = cats.actions + + const updaters = reduxFp.combine({cats: cats.updater, dogs: dogs.updater}) + const epics = combineEpics(cats.epic, dogs.epic) + + const reducer = (state, action) => updaters(action)(state) + + const epicMiddleware = createEpicMiddleware(epics, {dependencies: {feathers: app}}) + + const store = createStore(reducer, applyMiddleware(epicMiddleware)) + + const createCid = Cid() + const keysHaveCidKey = pipe(keys, any(key => key === createCid)) + + Store$(store) + .filter((store) => store.cats.cats[createCid]) + .take(1) + .subscribe(({cats}) => { + t.equal(cats.cats[createCid].name, 'fluffy', 'Cat is created optimistically') + }) + + Store$(store) + .filter((store) => store.cats.cats[0]) + .take(1) + .subscribe(({cats}) => { + t.equal(cats.cats[0].name, 'fluffy', 'Cat is set at the expected id') + t.false(keysHaveCidKey(cats.cats), 'Temporary cid key is deleted') + t.end() + }) + + store.dispatch(cats.actions.create(createCid, {name: 'fluffy'})) }) function Store$ (store) { diff --git a/test/epic.js b/test/epic.js index fa1cc6a..7d7f5d9 100644 --- a/test/epic.js +++ b/test/epic.js @@ -27,14 +27,15 @@ function createCid () { test('find', function (t) { const cid = createCid() const action$ = Action$.of(cats.actions.find(cid)) - const feathers = { + const service = { find: () => Rx.Observable.of(values(catsData)) } + const feathers = { + service: () => service + } const expected = [ - actionCreators.start(cid, { service, method: 'find', args: { params: {} } }), - actionCreators.set(cid, 0, catsData[0]), - actionCreators.set(cid, 1, catsData[1]), - actionCreators.set(cid, 2, catsData[2]), + actionCreators.start(cid, { service: 'cats', method: 'find', args: { params: {} } }), + actionCreators.setAll(cid, values(catsData)), actionCreators.complete(cid) ] cats.epic(action$, undefined, { feathers }) @@ -53,33 +54,35 @@ test('find with cancel', function (t) { observer.next(cats.actions.complete(cid)) }) })) + const service = { + find: () => Rx.Observable.of(values(catsData)) + } const feathers = { - find: () => Rx.Observable.create(observer => { - observer.next(values(catsData)) - }) + service: () => service } const expected = [ - actionCreators.start(cid, { service, method: 'find', args: { params: {} } }), - actionCreators.set(cid, 0, catsData[0]), - actionCreators.set(cid, 1, catsData[1]), - actionCreators.set(cid, 2, catsData[2]) + actionCreators.start(cid, { service: 'cats', method: 'find', args: { params: {} } }), + actionCreators.setAll(cid, values(catsData)), ] var i = 0 cats.epic(action$, undefined, { feathers }) .subscribe((action) => { t.deepEqual(action, expected[i++]) - if (i === 4) t.end() + if (i === 2) t.end() }) }) test('get', function (t) { const cid = createCid() const action$ = Action$.of(cats.actions.get(cid, 0)) - const feathers = { + const service = { get: (id) => Rx.Observable.of(catsData[id]) } + const feathers = { + service: () => service + } const expected = [ - actionCreators.start(cid, { service, method: 'get', args: { id: 0, params: {} } }), + actionCreators.start(cid, { service: 'cats', method: 'get', args: { id: 0, params: {} } }), actionCreators.set(cid, 0, catsData[0]), actionCreators.complete(cid) ] @@ -94,11 +97,14 @@ test('get', function (t) { test('create', function (t) { const cid = createCid() const action$ = Action$.of(cats.actions.create(cid, newCat)) - const feathers = { + const service = { create: () => Rx.Observable.of(catsData[0]) } + const feathers = { + service: () => service + } const expected = [ - actionCreators.start(cid, { service, method: 'create', args: { data: newCat, params: {} } }), + actionCreators.start(cid, { service: 'cats', method: 'create', args: { data: newCat, params: {} } }), actionCreators.set(cid, cid, newCat), actionCreators.set(cid, cid, undefined), actionCreators.set(cid, 0, catsData[0]), @@ -116,11 +122,14 @@ test('create with rollback', function (t) { const cid = createCid() const err = new Error('oh no') const action$ = Action$.of(cats.actions.create(cid, newCat)) - const feathers = { + const service = { create: () => Rx.Observable.throw(err) } + const feathers = { + service: () => service + } const expected = [ - actionCreators.start(cid, { service, method: 'create', args: { data: newCat, params: {} } }), + actionCreators.start(cid, { service: 'cats', method: 'create', args: { data: newCat, params: {} } }), actionCreators.set(cid, cid, newCat), actionCreators.set(cid, cid, undefined), actionCreators.error(cid, err) @@ -136,14 +145,17 @@ test('create with rollback', function (t) { test('update', function (t) { const cid = createCid() const action$ = Action$.of(cats.actions.update(cid, 0, nextCat)) - const feathers = { + const service = { update: () => Rx.Observable.of(merge({ id: 0, feathers: true }, nextCat)) } + const feathers = { + service: () => service + } const store = { getState: () => ({ cats: catsData }) } const expected = [ - actionCreators.start(cid, { service, method: 'update', args: { id: 0, data: nextCat, params: {} } }), + actionCreators.start(cid, { service: 'cats', method: 'update', args: { id: 0, data: nextCat, params: {} } }), actionCreators.set(cid, 0, merge({ id: 0 }, nextCat)), actionCreators.set(cid, 0, merge({ id: 0, feathers: true }, nextCat)), actionCreators.complete(cid) @@ -160,14 +172,17 @@ test('update with rollback', function (t) { const cid = createCid() const err = new Error('oh no') const action$ = Action$.of(cats.actions.update(cid, 0, nextCat)) - const feathers = { + const service = { update: () => Rx.Observable.throw(err) } + const feathers = { + service: () => service + } const store = { getState: () => ({ cats: catsData }) } const expected = [ - actionCreators.start(cid, { service, method: 'update', args: { id: 0, data: nextCat, params: {} } }), + actionCreators.start(cid, { service: 'cats', method: 'update', args: { id: 0, data: nextCat, params: {} } }), actionCreators.set(cid, 0, merge({ id: 0 }, nextCat)), actionCreators.set(cid, 0, catsData[0]), actionCreators.error(cid, err) @@ -183,14 +198,17 @@ test('update with rollback', function (t) { test('patch', function (t) { const cid = createCid() const action$ = Action$.of(cats.actions.patch(cid, 0, nextCat)) - const feathers = { + const service = { patch: () => Rx.Observable.of(mergeAll([catsData[0], { feathers: true }, nextCat])) } + const feathers = { + service: () => service + } const store = { getState: () => ({ cats: catsData }) } const expected = [ - actionCreators.start(cid, { service, method: 'patch', args: { id: 0, data: nextCat, params: {} } }), + actionCreators.start(cid, { service: 'cats', method: 'patch', args: { id: 0, data: nextCat, params: {} } }), actionCreators.set(cid, 0, merge(catsData[0], nextCat)), actionCreators.set(cid, 0, mergeAll([catsData[0], { feathers: true }, nextCat])), actionCreators.complete(cid) @@ -207,14 +225,17 @@ test('patch with rollback', function (t) { const cid = createCid() const err = new Error('oh no') const action$ = Action$.of(cats.actions.patch(cid, 0, nextCat)) - const feathers = { + const service = { patch: () => Rx.Observable.throw(err) } + const feathers = { + service: () => service + } const store = { getState: () => ({ cats: catsData }) } const expected = [ - actionCreators.start(cid, { service, method: 'patch', args: { id: 0, data: nextCat, params: {} } }), + actionCreators.start(cid, { service: 'cats', method: 'patch', args: { id: 0, data: nextCat, params: {} } }), actionCreators.set(cid, 0, merge(catsData[0], nextCat)), actionCreators.set(cid, 0, catsData[0]), actionCreators.error(cid, err) @@ -230,14 +251,17 @@ test('patch with rollback', function (t) { test('remove', function (t) { const cid = createCid() const action$ = Action$.of(cats.actions.remove(cid, 0)) - const feathers = { + const service = { remove: () => Rx.Observable.of(catsData[0]) } + const feathers = { + service: () => service + } const store = { getState: () => ({ cats: catsData }) } const expected = [ - actionCreators.start(cid, { service, method: 'remove', args: { id: 0, params: {} } }), + actionCreators.start(cid, { service: 'cats', method: 'remove', args: { id: 0, params: {} } }), actionCreators.set(cid, 0, undefined), actionCreators.set(cid, 0, undefined), actionCreators.complete(cid) @@ -254,14 +278,17 @@ test('remove with rollback', function (t) { const cid = createCid() const err = new Error('oh no') const action$ = Action$.of(cats.actions.remove(cid, 0)) - const feathers = { + const service = { remove: () => Rx.Observable.throw(err) } + const feathers = { + service: () => service + } const store = { getState: () => ({ cats: catsData }) } const expected = [ - actionCreators.start(cid, { service, method: 'remove', args: { id: 0, params: {} } }), + actionCreators.start(cid, { service: 'cats', method: 'remove', args: { id: 0, params: {} } }), actionCreators.set(cid, 0, undefined), actionCreators.set(cid, 0, catsData[0]), actionCreators.error(cid, err) diff --git a/updater.js b/updater.js index 09cd02b..cbd8e62 100644 --- a/updater.js +++ b/updater.js @@ -3,6 +3,7 @@ const assoc = require('ramda/src/assoc') const assocPath = require('ramda/src/assocPath') const dissoc = require('ramda/src/dissoc') +const keys = require('ramda/src/keys') const pipe = require('ramda/src/pipe') const reduce = require('ramda/src/reduce') const __ = require('ramda/src/__')