142 lines
3.5 KiB
TypeScript
142 lines
3.5 KiB
TypeScript
import React from 'react'
|
|
import ReactDOM from 'react-dom'
|
|
import T from 'react-dom/test-utils'
|
|
import {createStore, TStateSelector, WaitMiddleware} from '@rondo.dev/redux'
|
|
import {Provider} from 'react-redux'
|
|
import {
|
|
Action,
|
|
AnyAction,
|
|
DeepPartial,
|
|
Reducer,
|
|
ReducersMapObject,
|
|
combineReducers,
|
|
Store as ReduxStore,
|
|
Unsubscribe,
|
|
} from 'redux'
|
|
import { format } from 'util'
|
|
|
|
interface IRenderParams<State, LocalState> {
|
|
reducers: ReducersMapObject<State, any>
|
|
select: TStateSelector<State, LocalState>
|
|
// getComponent: (
|
|
// select: TStateSelector<State, LocalState>) => React.ComponentType<Props>,
|
|
// customJSX?: (
|
|
// Component: React.ComponentType<Props>,
|
|
// props: Props,
|
|
// ) => JSX.Element
|
|
}
|
|
|
|
export class TestUtils {
|
|
/**
|
|
* Create a redux store
|
|
*/
|
|
readonly createStore = createStore
|
|
|
|
render(jsx: JSX.Element) {
|
|
const $div = document.createElement('div')
|
|
const component = ReactDOM.render(
|
|
<div>{jsx}</div>, $div) as unknown as React.Component<any>
|
|
const node = (ReactDOM.findDOMNode(component) as Element).children[0]
|
|
return {
|
|
component,
|
|
node,
|
|
}
|
|
}
|
|
|
|
combineReducers<S>(reducers: ReducersMapObject<S, any>): Reducer<S>
|
|
combineReducers<S, A extends Action = AnyAction>(
|
|
reducers: ReducersMapObject<S, A>,
|
|
): Reducer<S, A> {
|
|
return combineReducers(reducers)
|
|
}
|
|
|
|
/**
|
|
* Creates a redux store, connects a component, and provides the `render`
|
|
* method to render the connected component with a `Provider`.
|
|
*/
|
|
withProvider<State, LocalState, A extends Action<any> = AnyAction>(
|
|
params: IRenderParams<State, LocalState>,
|
|
) {
|
|
const {reducers, select} = params
|
|
|
|
const waitMiddleware = new WaitMiddleware()
|
|
|
|
let store = this.createStore({
|
|
reducer: this.combineReducers(reducers),
|
|
extraMiddleware: [waitMiddleware.handle],
|
|
})()
|
|
|
|
const withState = (state: DeepPartial<State>) => {
|
|
store = this.createStore({
|
|
reducer: this.combineReducers(reducers),
|
|
})(state)
|
|
|
|
return {withComponent}
|
|
}
|
|
|
|
const withComponent = <Props extends {}>(
|
|
getComponent: (select: TStateSelector<State, LocalState>) =>
|
|
React.ComponentType<Props>,
|
|
) => {
|
|
const Component = getComponent(select)
|
|
|
|
type CreateJSX = (
|
|
Component: React.ComponentType<Props>,
|
|
props: Props,
|
|
) => JSX.Element
|
|
|
|
let createJSX: CreateJSX | undefined
|
|
|
|
const render = (props: Props) => {
|
|
const recorder = waitMiddleware.record()
|
|
|
|
const jsx = createJSX
|
|
? createJSX(Component, props)
|
|
: <Component {...props} />
|
|
|
|
const result = this.render(
|
|
<Provider store={store}>
|
|
{jsx}
|
|
</Provider>,
|
|
)
|
|
return {
|
|
...result,
|
|
async waitForActions(timeout = 2000) {
|
|
await waitMiddleware.waitForRecorded(recorder)
|
|
},
|
|
}
|
|
}
|
|
|
|
const withJSX = (localCreateJSX: CreateJSX) => {
|
|
createJSX = localCreateJSX
|
|
return self
|
|
}
|
|
|
|
const self: ISelf<
|
|
Props, typeof store, typeof Component, CreateJSX
|
|
> = {
|
|
render,
|
|
store,
|
|
Component,
|
|
withJSX,
|
|
}
|
|
|
|
return self
|
|
}
|
|
|
|
return {withState, withComponent}
|
|
}
|
|
}
|
|
|
|
interface ISelf<Props, Store, Component, CreateJSX> {
|
|
render: (props: Props) => {
|
|
component: React.Component<any>
|
|
node: Element
|
|
waitForActions(timeout?: number): Promise<void>
|
|
}
|
|
store: Store
|
|
Component: Component
|
|
withJSX: (localCreateJSX: CreateJSX)
|
|
=> ISelf<Props, Store, Component, CreateJSX>
|
|
}
|