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>
}