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": {
|
"compilerOptions": {
|
||||||
"outDir": "lib",
|
"outDir": "lib",
|
||||||
"rootDir": "src"
|
"rootDir": "src"
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// following settings required for referenced projects
|
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user