Skip to content

Commit

Permalink
Feature/saga1.x (#10)
Browse files Browse the repository at this point in the history
feat: upgrade saga to 1.x and update unit test
  • Loading branch information
deepfunc authored Feb 26, 2020
1 parent 303561b commit 054d76c
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 46 deletions.
4 changes: 2 additions & 2 deletions .babelrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ module.exports = {
[
'@babel/env',
{
modules: BABEL_ENV === 'commonjs' ? 'cjs' : false,
modules: (BABEL_ENV === 'commonjs' || BABEL_ENV === 'wepy') ? 'cjs' : false,
loose: true,
targets: NODE_ENV === 'test' ? { node: 'current' } : {}
}
]
],
plugins: [
'@babel/plugin-transform-runtime',
BABEL_ENV !== 'wepy' && '@babel/plugin-transform-runtime',
// don't use `loose` mode here - need to copy symbols when spreading
'@babel/plugin-proposal-object-rest-spread',
NODE_ENV === 'test' && '@babel/transform-modules-commonjs'
Expand Down
36 changes: 28 additions & 8 deletions __tests__/model.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,35 +26,55 @@ describe('model', () => {
test('should dynamic load model', () => {
const app = balloon();
app.model({
namespace: 'a',
state: { count: 0 },
namespace: 'views.a',
state: { count: 1 },
reducers: {
'COUNT_A_ADD': (state, { payload }) => {
return Object.assign({}, state, { count: state.count + payload });
}
},
actions: {
addCountForA: ['COUNT_A_ADD']
},
selectors: () => {
console.log('a selectors');
const getA = state => state.views.a;

return { getA };
}
});
app.run();

console.log('app.state1:', app.getState());
console.log('app.selectors.getA:', app.selectors.getA(app.getState()));

app.model({
namespace: 'b',
state: { count: 0 },
namespace: 'views.b',
state: { count: 2 },
reducers: {
'COUNT_B_ADD': (state, { payload }) => {
return Object.assign({}, state, { count: state.count + payload });
}
},
actions: {
addCountForB: ['COUNT_B_ADD']
},
selectors: () => {
console.log('b selectors');
const getB = state => state.views.b;

return { getB };
}
});
const { store, actions } = app;

expect(store.getState()).toEqual({ a: { count: 0 }, b: { count: 0 } });
store.dispatch(actions.addCountForB(4));
expect(store.getState()).toEqual({ a: { count: 0 }, b: { count: 4 } });
console.log('app.state2:', app.getState());
console.log('app.selectors.getB:', app.selectors.getB(app.getState()));

// const { store, actions } = app;
//
// expect(store.getState()).toEqual({ a: { count: 0 }, b: { count: 0 } });
// store.dispatch(actions.addCountForB(4));
// expect(store.getState()).toEqual({ a: { count: 0 }, b: { count: 4 } });
});

test('should unload model', () => {
Expand Down
29 changes: 12 additions & 17 deletions __tests__/sagaModules.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { runSaga } from 'redux-saga';
import { runSaga, stdChannel } from 'redux-saga';
import EventEmitter from 'events';
import {
addSagaModule,
delSagaModule,
Expand Down Expand Up @@ -41,21 +42,15 @@ describe('sagaModules', () => {

describe('run sagas', () => {
function createEmitter() {
const listeners = [];
const emitter = new EventEmitter();
const channel = stdChannel();
emitter.on('action', channel.put);

return {
subscribe(callback) {
listeners.push(callback);
return () => {
const index = listeners.indexOf(callback);
if (index >= 0) {
listeners.splice(index, 1);
}
};
},
channel,

emit(action) {
listeners.forEach(callback => callback(action));
emitter.emit('action', action);
}
};
}
Expand Down Expand Up @@ -83,10 +78,10 @@ describe('sagaModules', () => {

test('should run sagas: sagas is plain object', (done) => {
const dispatched = [];
const { subscribe, emit } = createEmitter();
const { channel, emit } = createEmitter();
const runSagaMock = (saga) => {
return runSaga({
subscribe,
channel,
dispatch: (action) => {
dispatched.push(action);
}
Expand Down Expand Up @@ -121,16 +116,16 @@ describe('sagaModules', () => {
]));
done();
},
20
100
);
});

test('should run sagas: sagas is function, and return plain object', (done) => {
const dispatched = [];
const { subscribe, emit } = createEmitter();
const { channel, emit } = createEmitter();
const runSagaMock = (saga) => {
return runSaga({
subscribe,
channel,
dispatch: (action) => {
dispatched.push(action);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"react-dom": "^16.6.3",
"react-redux": "^6.0.0",
"react-router-dom": "^4.3.1",
"redux-balloon": "^0.13.0",
"redux-balloon": "^1.0.0",
"seamless-immutable": "^7.1.4",
"serve-favicon": "^2.5.0",
"throttle-debounce": "^2.0.1",
Expand Down
30 changes: 26 additions & 4 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,40 @@ const replace = require('gulp-replace');

gulp.task('buildWePY', function (callback) {
runSequence(
'buildBabel',
'copySrc',
'replaceSagaReference',
'buildBabel',
callback
);
});

gulp.task('buildBabel', shell.task([
'cross-env BABEL_ENV=commonjs babel src --out-dir wepy'
gulp.task('copySrc', shell.task([
'cp -r src wepy'
]));

gulp.task('replaceSagaReference', function () {
gulp.src('./wepy/sagaImports.js')
.pipe(replace('redux-saga', 'redux-saga/dist/redux-saga'))
.pipe(
replace(
'import * as ReduxSaga from \'redux-saga\';',
'import * as ReduxSaga from \'redux-saga/dist/redux-saga.umd\';'
)
)
.pipe(
replace(
'import * as effects from \'redux-saga/effects\';',
''
)
)
.pipe(
replace(
'const SagaEffects = effects;',
'const SagaEffects = ReduxSaga.effects;'
)
)
.pipe(gulp.dest('./wepy'));
});

gulp.task('buildBabel', shell.task([
'cross-env BABEL_ENV=wepy babel wepy --out-dir wepy'
]));
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "redux-balloon",
"version": "0.13.0",
"version": "1.0.0",
"description": "Lightweight business framework for Redux apps",
"license": "MIT",
"repository": {
Expand Down Expand Up @@ -54,7 +54,7 @@
"invariant": "^2.2.4",
"redux": "^4.0.1",
"redux-actions": "^2.6.4",
"redux-saga": "^0.16.2",
"redux-saga": "^1.1.3",
"reselect": "^4.0.0",
"seamless-immutable": "^7.1.4"
},
Expand Down
5 changes: 4 additions & 1 deletion src/sagaImports.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import * as ReduxSaga from 'redux-saga';
import * as effects from 'redux-saga/effects';

const SagaEffects = effects;
const createSagaMiddleware = ReduxSaga.default;
const reduxSagaExport = { ...ReduxSaga, effects: SagaEffects };

export {
createSagaMiddleware,
ReduxSaga
reduxSagaExport as ReduxSaga
};
30 changes: 19 additions & 11 deletions src/sagaModules.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function runSagaModules(modules, runSaga, opts, extras) {
forEachObjIndexed((mod, namespace) => {
const sagas = mod[0];
const saga = createSaga(sagas, namespace, opts, _extras);
runSaga(saga).done.catch(err => {
runSaga(saga).toPromise().catch(err => {
if (!(err instanceof SagaError)) {
err = new SagaError(err, { namespace });
}
Expand Down Expand Up @@ -69,21 +69,30 @@ function createWatcher(sagas, namespace, opts, extras) {
}

return function* () {
const { takeEvery, takeLatest, throttle } = sagaEffects;
const typeWhiteList = [
'takeEvery',
'takeLatest',
'takeLeading',
'throttle',
'debounce'
];
const keys = Object.keys(sagasObj);

for (const key of keys) {
// takeEvery is default
let type = 'takeEvery';
let ms;
let saga = sagasObj[key];
let opts;

if (isArray(saga)) {
saga = sagasObj[key][0];
const opts = sagasObj[key][1];
opts = sagasObj[key][1];
type = opts.type;

if (type === 'throttle') {
ms = opts.ms;
if (!typeWhiteList.includes(type)) {
throw new Error(
`only support these types: [${typeWhiteList}], but got: ${type}. namespace: ${namespace}, key: ${key}`
);
}
}
const handler = handleActionForHelper(
Expand All @@ -94,14 +103,13 @@ function createWatcher(sagas, namespace, opts, extras) {
);

switch (type) {
case 'takeLatest':
yield takeLatest(key, handler);
break;
case 'throttle':
yield throttle(ms, key, handler);
case 'debounce':
yield sagaEffects[type](opts.ms, key, handler);
break;
default:
yield takeEvery(key, handler);
// takeEvery, takeLatest, takeLeading
yield sagaEffects[type](key, handler);
}
}
};
Expand Down

0 comments on commit 054d76c

Please sign in to comment.