Skip to content

Commit

Permalink
feat(async interop): can dispatch functions that return promises, obs…
Browse files Browse the repository at this point in the history
…ervable-like objects, and iterables such as generators
  • Loading branch information
benlesh committed Apr 29, 2016
1 parent 2fdfbda commit d20c411
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 9 deletions.
5 changes: 4 additions & 1 deletion package.json
Expand Up @@ -37,12 +37,15 @@
"babel-plugin-transform-es2015-modules-commonjs": "^6.7.4",
"babel-plugin-transform-function-bind": "^6.5.2",
"babel-plugin-transform-object-rest-spread": "^6.6.5",
"babel-polyfill": "^6.7.4",
"babel-preset-es2015": "^6.6.0",
"babel-register": "^6.7.2",
"chai": "^3.5.0",
"eslint": "^2.8.0",
"mocha": "^2.4.5",
"promise": "^7.1.1",
"redux": "^3.5.1",
"rxjs": "^5.0.0-beta.6"
"rxjs": "^5.0.0-beta.6",
"symbol-observable": "^0.2.4"
}
}
3 changes: 2 additions & 1 deletion src/rxDucksMiddleware.js
@@ -1,12 +1,13 @@
import { Subject } from 'rxjs/Subject';
import { from } from 'rxjs/observable/from';

export function rxDucksMiddleware() {
let actions = new Subject();

let middleware = (store) => (next) => {
return (action) => {
if (typeof action === 'function') {
let obs = action(actions, store);
let obs = from(action(actions, store));
let sub = obs.subscribe(next);
actions.next(action);
return sub;
Expand Down
97 changes: 90 additions & 7 deletions test/rxDucksMiddleware-spec.js
Expand Up @@ -3,32 +3,115 @@ import { expect } from 'chai';
import { createStore, applyMiddleware } from 'redux';
import { rxDucksMiddleware } from '../';
import * as Rx from 'rxjs';
import Promise from 'promise';
import 'babel-polyfill';
import $$observable from 'symbol-observable';

const { Observable } = Rx;

describe('rxDucksMiddleware', () => {
it('should exist', () => {
expect(rxDucksMiddleware).to.be.a('function');
it('should intercept and process actions', (done) => {
const reducer = (state = [], action) => state.concat(action);

const middleware = rxDucksMiddleware();

const store = createStore(reducer, applyMiddleware(middleware));

store.dispatch(() => Observable.of({ type: 'ASYNC_ACTION_1' }).delay(10));
store.dispatch(() => Observable.of({ type: 'ASYNC_ACTION_2' }).delay(20));

// HACKY: but should work until we use TestScheduler.
setTimeout(() => {
expect(store.getState()).to.deep.equal([
{ type: '@@redux/INIT' },
{ type: 'ASYNC_ACTION_1' },
{ type: 'ASYNC_ACTION_2' }
]);
done();
}, 100);
});

it('should intercept and process actions', (done) => {
it('should work dispatched functions that return a promise', (done) => {
const reducer = (state = [], action) => state.concat(action);

const middleware = rxDucksMiddleware();

const store = createStore(reducer, applyMiddleware(middleware));

store.dispatch(() => Observable.of({ type: 'TEST1_HANDLED' }).delay(10));
store.dispatch(() => Observable.of({ type: 'TEST2_HANDLED' }).delay(20));
store.dispatch(() => Promise.resolve({ type: 'ASYNC_ACTION_1' }));
store.dispatch(() => Promise.resolve({ type: 'ASYNC_ACTION_2' }));

// HACKY: but should work until we use TestScheduler.
setTimeout(() => {
expect(store.getState()).to.deep.equal([
{ type: '@@redux/INIT' },
{ type: 'TEST1_HANDLED' },
{ type: 'TEST2_HANDLED' }
{ type: 'ASYNC_ACTION_1' },
{ type: 'ASYNC_ACTION_2' }
]);
done();
}, 100);
});

it('should work with iterators/generators', (done) => {
const reducer = (state = [], action) => state.concat(action);

const middleware = rxDucksMiddleware();

const store = createStore(reducer, applyMiddleware(middleware));

store.dispatch(() => (function *() {
yield { type: 'ASYNC_ACTION_1' };
yield { type: 'ASYNC_ACTION_2' };
})());

// HACKY: but should work until we use TestScheduler.
setTimeout(() => {
expect(store.getState()).to.deep.equal([
{ type: '@@redux/INIT' },
{ type: 'ASYNC_ACTION_1' },
{ type: 'ASYNC_ACTION_2' }
]);
done();
}, 100);
});

it('should work with observablesque arguments', (done) => {
const reducer = (state = [], action) => state.concat(action);

const middleware = rxDucksMiddleware();

const store = createStore(reducer, applyMiddleware(middleware));

let finalized = false;

store.dispatch(() => ({
[$$observable]() {
return {
subscribe(observer) {
observer.next({ type: 'ASYNC_ACTION_1' });
observer.next({ type: 'ASYNC_ACTION_2' });
observer.complete();

return {
unsubscribe() {
finalized = true;
}
};
}
};
}
}));

// HACKY: but should work until we use TestScheduler.
setTimeout(() => {
expect(store.getState()).to.deep.equal([
{ type: '@@redux/INIT' },
{ type: 'ASYNC_ACTION_1' },
{ type: 'ASYNC_ACTION_2' }
]);

expect(finalized).to.equal(true);
done();
}, 100);
});
});

0 comments on commit d20c411

Please sign in to comment.