-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1842 from bloody-ux/master
support app.replaceModel method
- Loading branch information
Showing
6 changed files
with
335 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,13 @@ | ||
|
||
export isPlainObject from 'is-plain-object'; | ||
export const isArray = Array.isArray.bind(Array); | ||
export const isFunction = o => typeof o === 'function'; | ||
export const returnSelf = m => m; | ||
// avoid es6 array.prototype.findIndex polyfill | ||
export const noop = () => {}; | ||
export const findIndex = (array, predicate) => { | ||
for (let i = 0, length = array.length; i < length; i++) { | ||
if (predicate(array[i], i)) return i; | ||
} | ||
|
||
return -1; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
import expect from 'expect'; | ||
import EventEmitter from 'events'; | ||
import { create } from '../src/index'; | ||
|
||
describe('app.replaceModel', () => { | ||
it('should not be available before app.start() get called', () => { | ||
const app = create(); | ||
|
||
expect('replaceModel' in app).toEqual(false); | ||
}); | ||
|
||
it("should add model if it doesn't exist", () => { | ||
const app = create(); | ||
app.start(); | ||
|
||
const oldCount = app._models.length; | ||
|
||
app.replaceModel({ | ||
namespace: 'users', | ||
state: [], | ||
reducers: { | ||
add(state, { payload }) { | ||
return [...state, payload]; | ||
}, | ||
}, | ||
}); | ||
|
||
expect(app._models.length).toEqual(oldCount + 1); | ||
|
||
app._store.dispatch({ type: 'users/add', payload: 'jack' }); | ||
const state = app._store.getState(); | ||
expect(state.users).toEqual(['jack']); | ||
}); | ||
|
||
it('should run new reducers if model exists', () => { | ||
const app = create(); | ||
app.model({ | ||
namespace: 'users', | ||
state: ['foo'], | ||
reducers: { | ||
add(state, { payload }) { | ||
return [...state, payload]; | ||
}, | ||
}, | ||
}); | ||
app.start(); | ||
|
||
const oldCount = app._models.length; | ||
|
||
app.replaceModel({ | ||
namespace: 'users', | ||
state: ['bar'], | ||
reducers: { | ||
add(state, { payload }) { | ||
return [...state, 'world', payload]; | ||
}, | ||
clear() { | ||
return []; | ||
}, | ||
}, | ||
}); | ||
|
||
expect(app._models.length).toEqual(oldCount); | ||
let state = app._store.getState(); | ||
expect(state.users).toEqual(['foo']); | ||
|
||
app._store.dispatch({ type: 'users/add', payload: 'jack' }); | ||
state = app._store.getState(); | ||
expect(state.users).toEqual(['foo', 'world', 'jack']); | ||
|
||
// test new added action | ||
app._store.dispatch({ type: 'users/clear' }); | ||
|
||
state = app._store.getState(); | ||
expect(state.users).toEqual([]); | ||
}); | ||
|
||
it('should run new effects if model exists', () => { | ||
const app = create(); | ||
app.model({ | ||
namespace: 'users', | ||
state: [], | ||
reducers: { | ||
setter(state, { payload }) { | ||
return [...state, payload]; | ||
}, | ||
}, | ||
effects: { | ||
*add({ payload }, { put }) { | ||
yield put({ | ||
type: 'setter', | ||
payload, | ||
}); | ||
}, | ||
}, | ||
}); | ||
app.start(); | ||
|
||
app.replaceModel({ | ||
namespace: 'users', | ||
state: [], | ||
reducers: { | ||
setter(state, { payload }) { | ||
return [...state, payload]; | ||
}, | ||
}, | ||
effects: { | ||
*add(_, { put }) { | ||
yield put({ | ||
type: 'setter', | ||
payload: 'mock', | ||
}); | ||
}, | ||
}, | ||
}); | ||
|
||
app._store.dispatch({ type: 'users/add', payload: 'jack' }); | ||
const state = app._store.getState(); | ||
expect(state.users).toEqual(['mock']); | ||
}); | ||
|
||
it('should run subscriptions after replaceModel', () => { | ||
const app = create(); | ||
app.model({ | ||
namespace: 'users', | ||
state: [], | ||
reducers: { | ||
add(state, { payload }) { | ||
return [...state, payload]; | ||
}, | ||
}, | ||
subscriptions: { | ||
setup({ dispatch }) { | ||
// should return unlistener but omitted here | ||
dispatch({ type: 'add', payload: 1 }); | ||
}, | ||
}, | ||
}); | ||
app.start(); | ||
|
||
app.replaceModel({ | ||
namespace: 'users', | ||
state: [], | ||
reducers: { | ||
add(state, { payload }) { | ||
return [...state, payload]; | ||
}, | ||
}, | ||
subscriptions: { | ||
setup({ dispatch }) { | ||
// should return unlistener but omitted here | ||
dispatch({ type: 'add', payload: 2 }); | ||
}, | ||
}, | ||
}); | ||
|
||
const state = app._store.getState(); | ||
// This should be an issue but can't be avoided with dva | ||
// To avoid, in client code, setup method should be idempotent when running multiple times | ||
expect(state.users).toEqual([1, 2]); | ||
}); | ||
|
||
it('should remove old subscription listeners after replaceModel', () => { | ||
const app = create(); | ||
const emitter = new EventEmitter(); | ||
let emitterCount = 0; | ||
|
||
app.model({ | ||
namespace: 'users', | ||
state: [], | ||
subscriptions: { | ||
setup() { | ||
emitter.on('event', () => { | ||
emitterCount += 1; | ||
}); | ||
return () => { | ||
emitter.removeAllListeners(); | ||
}; | ||
}, | ||
}, | ||
}); | ||
app.start(); | ||
|
||
emitter.emit('event'); | ||
|
||
app.replaceModel({ | ||
namespace: 'users', | ||
state: [], | ||
}); | ||
|
||
emitter.emit('event'); | ||
|
||
expect(emitterCount).toEqual(1); | ||
}); | ||
|
||
it('should trigger onError if error is thown after replaceModel', () => { | ||
let triggeredError = false; | ||
const app = create({ | ||
onError() { | ||
triggeredError = true; | ||
}, | ||
}); | ||
app.model({ | ||
namespace: 'users', | ||
state: [], | ||
}); | ||
app.start(); | ||
|
||
app.replaceModel({ | ||
namespace: 'users', | ||
state: [], | ||
effects: { | ||
*add() { | ||
yield 'fake'; | ||
|
||
throw new Error('fake error'); | ||
}, | ||
}, | ||
}); | ||
|
||
app._store.dispatch({ | ||
type: 'users/add', | ||
}); | ||
|
||
expect(triggeredError).toEqual(true); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import expect from 'expect'; | ||
import { findIndex } from '../src/utils'; | ||
|
||
describe('utils', () => { | ||
describe('#findIndex', () => { | ||
it('should return -1 when no item matches', () => { | ||
const array = [1, 2, 3]; | ||
const action = i => i === 4; | ||
|
||
expect(findIndex(array, action)).toEqual(-1); | ||
}); | ||
|
||
it('should return index of the match item in array', () => { | ||
const array = ['a', 'b', 'c']; | ||
const action = i => i === 'b'; | ||
|
||
const actualValue = findIndex(array, action); | ||
const expectedValue = 1; | ||
|
||
expect(actualValue).toEqual(expectedValue); | ||
}); | ||
|
||
it('should return the first match if more than one items match', () => { | ||
const target = { | ||
id: 1, | ||
}; | ||
|
||
const array = [target, { id: 1 }]; | ||
const action = i => i.id === 1; | ||
|
||
expect(findIndex(array, action)).toEqual(0); | ||
}); | ||
}); | ||
}); |