Add PromiseMiddleware
This commit is contained in:
parent
eedd57c527
commit
c1d21350a8
16
packages/client/jest.config.js
Normal file
16
packages/client/jest.config.js
Normal file
@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
roots: [
|
||||
'<rootDir>/src'
|
||||
],
|
||||
transform: {
|
||||
'^.+\\.tsx?$': 'ts-jest'
|
||||
},
|
||||
testRegex: '(/__tests__/.*|\\.(test|spec))\\.tsx?$',
|
||||
moduleFileExtensions: [
|
||||
'ts',
|
||||
'tsx',
|
||||
'js',
|
||||
'jsx'
|
||||
],
|
||||
setupFiles: ['<rootDir>/jest.setup.js']
|
||||
}
|
||||
0
packages/client/jest.setup.js
Normal file
0
packages/client/jest.setup.js
Normal file
76
packages/client/src/middleware/PromiseMiddleware.test.ts
Normal file
76
packages/client/src/middleware/PromiseMiddleware.test.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import {createStore, applyMiddleware, Store} from 'redux'
|
||||
import {PromiseMiddleware} from './PromiseMiddleware'
|
||||
|
||||
describe('PromiseMiddleware', () => {
|
||||
|
||||
async function getError(promise: Promise<any>): Promise<Error> {
|
||||
let error: Error
|
||||
try {
|
||||
await promise
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
expect(error!).toBeTruthy()
|
||||
return error!
|
||||
}
|
||||
|
||||
describe('constructor', () => {
|
||||
it('throws an error when action types are the same', () => {
|
||||
expect(() => new PromiseMiddleware('a', 'a', 'a')).toThrowError()
|
||||
expect(new PromiseMiddleware('a', 'b', 'c')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
let store!: Store
|
||||
beforeEach(() => {
|
||||
const middleware = new PromiseMiddleware()
|
||||
store = createStore((state: any[] = [], action) => {
|
||||
state.push(action)
|
||||
return state
|
||||
}, applyMiddleware(middleware.handle))
|
||||
})
|
||||
|
||||
it('does nothing when payload is not a promise', () => {
|
||||
const action = {type: 'test'}
|
||||
store.dispatch(action)
|
||||
expect(store.getState().slice(1)).toEqual([action])
|
||||
})
|
||||
|
||||
it('dispatches pending and fulfilled action', async () => {
|
||||
const value = 123
|
||||
const type = 'TEST'
|
||||
const action = {
|
||||
payload: Promise.resolve(value),
|
||||
type,
|
||||
}
|
||||
const result = store.dispatch(action)
|
||||
expect(result).toBe(action)
|
||||
await result.payload
|
||||
expect(store.getState().slice(1)).toEqual([{
|
||||
type: `${type}_PENDING`,
|
||||
}, {
|
||||
payload: value,
|
||||
type,
|
||||
}])
|
||||
})
|
||||
|
||||
it('dispatches pending and rejected action on error', async () => {
|
||||
const error = new Error('test')
|
||||
const type = 'TEST'
|
||||
const action = {
|
||||
payload: Promise.reject(error),
|
||||
type,
|
||||
}
|
||||
const result = store.dispatch(action)
|
||||
expect(result).toBe(action)
|
||||
const err = await getError(result.payload)
|
||||
expect(err).toBe(error)
|
||||
expect(store.getState().slice(1)).toEqual([{
|
||||
type: `${type}_PENDING`,
|
||||
}, {
|
||||
error,
|
||||
type: `${type}_REJECTED`,
|
||||
}])
|
||||
})
|
||||
|
||||
})
|
||||
45
packages/client/src/middleware/PromiseMiddleware.ts
Normal file
45
packages/client/src/middleware/PromiseMiddleware.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import assert from 'assert'
|
||||
import {AnyAction, Middleware} from 'redux'
|
||||
|
||||
function isPromise(value: any): value is Promise<any> {
|
||||
return value && typeof value === 'object' &&
|
||||
typeof (value as any).then === 'function'
|
||||
}
|
||||
|
||||
export class PromiseMiddleware {
|
||||
constructor(
|
||||
readonly pendingExtension = '_PENDING',
|
||||
readonly fulfilledExtension = '',
|
||||
readonly rejectedExtension = '_REJECTED',
|
||||
) {
|
||||
assert(
|
||||
this.pendingExtension !== this.fulfilledExtension &&
|
||||
this.fulfilledExtension !== this.rejectedExtension &&
|
||||
this.pendingExtension !== this.rejectedExtension,
|
||||
'Pending, fulfilled and rejected extensions must be unique')
|
||||
}
|
||||
handle: Middleware = store => next => (action: AnyAction) => {
|
||||
const {payload, type} = action
|
||||
if (!isPromise(payload)) {
|
||||
next(action)
|
||||
return
|
||||
}
|
||||
store.dispatch({type: type + this.pendingExtension})
|
||||
|
||||
payload
|
||||
.then(result => {
|
||||
store.dispatch({
|
||||
payload: result,
|
||||
type,
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
store.dispatch({
|
||||
error: err,
|
||||
type: type + this.rejectedExtension,
|
||||
})
|
||||
})
|
||||
|
||||
return action
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../tsconfig.common.json"
|
||||
"extends": "../tsconfig.common.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// following settings required for referenced projects
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user