From 6ee5077c889d9e4ae9ad6fe5bf683ff3deac01fc Mon Sep 17 00:00:00 2001 From: Jerko Steiner Date: Sun, 20 Jan 2019 22:47:15 +0100 Subject: [PATCH] Add documentation --- .../src/middleware/PromiseMiddleware.ts | 13 +++++++++ packages/client/src/redux/Connector.ts | 27 +++++++++++++++++-- packages/client/src/redux/IStateSelector.ts | 3 +++ .../client/src/test-utils/HTTPClientMock.ts | 22 +++++++++++++++ packages/client/src/test-utils/TestUtils.tsx | 7 +++++ packages/client/src/test-utils/getError.ts | 5 ++++ 6 files changed, 75 insertions(+), 2 deletions(-) diff --git a/packages/client/src/middleware/PromiseMiddleware.ts b/packages/client/src/middleware/PromiseMiddleware.ts index aab2d35..79e9bcd 100644 --- a/packages/client/src/middleware/PromiseMiddleware.ts +++ b/packages/client/src/middleware/PromiseMiddleware.ts @@ -6,6 +6,19 @@ function isPromise(value: any): value is Promise { 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 { constructor( readonly pendingExtension = '_PENDING', diff --git a/packages/client/src/redux/Connector.ts b/packages/client/src/redux/Connector.ts index bb52e1b..ccc97b0 100644 --- a/packages/client/src/redux/Connector.ts +++ b/packages/client/src/redux/Connector.ts @@ -3,11 +3,34 @@ import {connect, Omit} from 'react-redux' import {Dispatch} from 'redux' 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 { + /** + * 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( - getLocalState: IStateSelector, + selectState: IStateSelector, ): ComponentType protected wrap< diff --git a/packages/client/src/redux/IStateSelector.ts b/packages/client/src/redux/IStateSelector.ts index f75dc98..54a095d 100644 --- a/packages/client/src/redux/IStateSelector.ts +++ b/packages/client/src/redux/IStateSelector.ts @@ -1,2 +1,5 @@ +/* + * Select and return a part of the state + */ export type IStateSelector = (state: GlobalState) => StateSlice diff --git a/packages/client/src/test-utils/HTTPClientMock.ts b/packages/client/src/test-utils/HTTPClientMock.ts index 4ed1276..bf26b70 100644 --- a/packages/client/src/test-utils/HTTPClientMock.ts +++ b/packages/client/src/test-utils/HTTPClientMock.ts @@ -28,6 +28,9 @@ export class HTTPClientMock extends HTTPClient { super() } + /** + * Mock the http client. + */ createRequestor() { return { request: (req: IRequest): Promise => { @@ -62,11 +65,19 @@ export class HTTPClientMock extends HTTPClient { 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 { this.mocks[this.serialize(req)] = {data, status} return this } + /** + * Clear all mocks and recorded requests + */ mockClear(): this { this.requests = [] this.mocks = {} @@ -86,6 +97,17 @@ export class HTTPClientMock extends HTTPClient { 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 { expect(this.waitPromise).toBe(undefined) return new Promise((resolve, reject) => { diff --git a/packages/client/src/test-utils/TestUtils.tsx b/packages/client/src/test-utils/TestUtils.tsx index d3ea043..688bd91 100644 --- a/packages/client/src/test-utils/TestUtils.tsx +++ b/packages/client/src/test-utils/TestUtils.tsx @@ -44,6 +44,9 @@ export class TestUtils { return combineReducers(reducers) } + /** + * Create a redux store + */ createStore = AnyAction>( params: IStoreParams, ): Store { @@ -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 = AnyAction>( params: IRenderParams, ) { diff --git a/packages/client/src/test-utils/getError.ts b/packages/client/src/test-utils/getError.ts index 6c38bf8..0c13b11 100644 --- a/packages/client/src/test-utils/getError.ts +++ b/packages/client/src/test-utils/getError.ts @@ -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): Promise { let error: Error try {