Add documentation
This commit is contained in:
parent
380732d28a
commit
6ee5077c88
@ -6,6 +6,19 @@ function isPromise(value: any): value is Promise<any> {
|
|||||||
typeof (value as any).then === 'function'
|
typeof (value as any).then === 'function'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles promises returned from Actions.
|
||||||
|
*
|
||||||
|
* If `action.payload` is a `Promise`, it will be handled by this class. It
|
||||||
|
* differs from other promise middlewares for redux because by default it does
|
||||||
|
* not add an extension to action dispatched after a promise is fulfilled. This
|
||||||
|
* makes it easier to infer types from the API endpoints so they can be used in
|
||||||
|
* both Action creators and Reducers.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* const middleware = applyMiddleware(new PromiseMiddleware().handle)
|
||||||
|
*/
|
||||||
export class PromiseMiddleware {
|
export class PromiseMiddleware {
|
||||||
constructor(
|
constructor(
|
||||||
readonly pendingExtension = '_PENDING',
|
readonly pendingExtension = '_PENDING',
|
||||||
|
|||||||
@ -3,11 +3,34 @@ import {connect, Omit} from 'react-redux'
|
|||||||
import {Dispatch} from 'redux'
|
import {Dispatch} from 'redux'
|
||||||
import {ComponentType} from 'react'
|
import {ComponentType} from 'react'
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/54277411
|
/**
|
||||||
|
* Helps isolate the component along with actions and reducer from the global
|
||||||
|
* application state.
|
||||||
|
*
|
||||||
|
* The connect() function requires a state selector, which can be used to
|
||||||
|
* select a slice of the current state to the component, which will be passed
|
||||||
|
* on to `mapStateToProps()`.
|
||||||
|
*
|
||||||
|
* Classes that extend Connector can provide dependencies in the constructor
|
||||||
|
* and then bind them to dispatch in mapDispatchToProps. This is useful to
|
||||||
|
* build components which do not depend on a "global" singletons. For example,
|
||||||
|
* the Actions class might depend on the HTTPClient class, and then it becomes
|
||||||
|
* easy to mock it during tests, or swap out different dependencies for
|
||||||
|
* different applications.
|
||||||
|
*/
|
||||||
export abstract class Connector {
|
export abstract class Connector {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects a component using redux. The `selectState` method is used to
|
||||||
|
* select a subset of state to map to the component.
|
||||||
|
*
|
||||||
|
* It returns a component with `any` props. Ideally this could be changed to
|
||||||
|
* required props.
|
||||||
|
*
|
||||||
|
* https://stackoverflow.com/questions/54277411
|
||||||
|
*/
|
||||||
abstract connect<State, LocalState>(
|
abstract connect<State, LocalState>(
|
||||||
getLocalState: IStateSelector<State, LocalState>,
|
selectState: IStateSelector<State, LocalState>,
|
||||||
): ComponentType<any>
|
): ComponentType<any>
|
||||||
|
|
||||||
protected wrap<
|
protected wrap<
|
||||||
|
|||||||
@ -1,2 +1,5 @@
|
|||||||
|
/*
|
||||||
|
* Select and return a part of the state
|
||||||
|
*/
|
||||||
export type IStateSelector<GlobalState, StateSlice>
|
export type IStateSelector<GlobalState, StateSlice>
|
||||||
= (state: GlobalState) => StateSlice
|
= (state: GlobalState) => StateSlice
|
||||||
|
|||||||
@ -28,6 +28,9 @@ export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
|
|||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock the http client.
|
||||||
|
*/
|
||||||
createRequestor() {
|
createRequestor() {
|
||||||
return {
|
return {
|
||||||
request: (req: IRequest): Promise<IResponse> => {
|
request: (req: IRequest): Promise<IResponse> => {
|
||||||
@ -62,11 +65,19 @@ export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
|
|||||||
return JSON.stringify(req, null, ' ')
|
return JSON.stringify(req, null, ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new mock. If a mock with the same signature exists, it will be
|
||||||
|
* replaced. The signature is calculated using the `serialize()` method,
|
||||||
|
* which just does a `JSON.stringify(req)`.
|
||||||
|
*/
|
||||||
mockAdd(req: IRequest, data: any, status = 200): this {
|
mockAdd(req: IRequest, data: any, status = 200): this {
|
||||||
this.mocks[this.serialize(req)] = {data, status}
|
this.mocks[this.serialize(req)] = {data, status}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all mocks and recorded requests
|
||||||
|
*/
|
||||||
mockClear(): this {
|
mockClear(): this {
|
||||||
this.requests = []
|
this.requests = []
|
||||||
this.mocks = {}
|
this.mocks = {}
|
||||||
@ -86,6 +97,17 @@ export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
|
|||||||
waitPromise.resolve(r)
|
waitPromise.resolve(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new promise which will be resolve/rejected as soon as the next
|
||||||
|
* HTTP promise is resolved or rejected. Useful during testing, when the
|
||||||
|
* actual request promise is inaccessible.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
*
|
||||||
|
* TestUtils.Simulate.submit(node) // This triggers a HTTP request
|
||||||
|
* const {req, res} = await httpMock.wait()
|
||||||
|
* expect(req).toEqual({method:'get', url:'/auth/post', data: {...}})
|
||||||
|
*/
|
||||||
async wait(): Promise<IReqRes> {
|
async wait(): Promise<IReqRes> {
|
||||||
expect(this.waitPromise).toBe(undefined)
|
expect(this.waitPromise).toBe(undefined)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
@ -44,6 +44,9 @@ export class TestUtils {
|
|||||||
return combineReducers(reducers)
|
return combineReducers(reducers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a redux store
|
||||||
|
*/
|
||||||
createStore<State, A extends Action<any> = AnyAction>(
|
createStore<State, A extends Action<any> = AnyAction>(
|
||||||
params: IStoreParams<State, A>,
|
params: IStoreParams<State, A>,
|
||||||
): Store<State, A> {
|
): Store<State, A> {
|
||||||
@ -55,6 +58,10 @@ export class TestUtils {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a redux store, connects a component, and provides the `render`
|
||||||
|
* method to render the connected component with a `Provider`.
|
||||||
|
*/
|
||||||
withProvider<State, A extends Action<any> = AnyAction>(
|
withProvider<State, A extends Action<any> = AnyAction>(
|
||||||
params: IRenderParams<State>,
|
params: IRenderParams<State>,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -1,3 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Waits for a promise be rejected and return the error. If a promise resolves
|
||||||
|
* it will throw an error. To be used during testing since
|
||||||
|
* `expect(...).toThrowError()` only works with synchronous calls
|
||||||
|
*/
|
||||||
export async function getError(promise: Promise<any>): Promise<Error> {
|
export async function getError(promise: Promise<any>): Promise<Error> {
|
||||||
let error: Error
|
let error: Error
|
||||||
try {
|
try {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user