Skip to content

Commit

Permalink
refactor: transform test/components/Provider => ts (#1772)
Browse files Browse the repository at this point in the history
  • Loading branch information
myNameIsDu committed Jul 20, 2021
1 parent 1f4d970 commit 19ccf75
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 54 deletions.
2 changes: 2 additions & 0 deletions src/exports.ts
Expand Up @@ -27,6 +27,7 @@ import { useSelector, createSelectorHook } from './hooks/useSelector'
import { useStore, createStoreHook } from './hooks/useStore'

import shallowEqual from './utils/shallowEqual'
import type { Subscription } from '../src/utils/Subscription'

export * from './types'
export type {
Expand All @@ -46,6 +47,7 @@ export type {
MapDispatchToPropsNonObject,
MergeProps,
ReactReduxContextValue,
Subscription,
}
export {
Provider,
Expand Down
149 changes: 100 additions & 49 deletions test/components/Provider.spec.js → test/components/Provider.spec.tsx
@@ -1,10 +1,13 @@
/*eslint-disable react/prop-types*/

import React, { Component } from 'react'
import React, { Component, Dispatch } from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider, connect, ReactReduxContext } from '../../src/index'
import * as rtl from '@testing-library/react'
import type { ReactReduxContextValue } from '../../src'
import type { Store } from 'redux'

import '@testing-library/jest-dom/extend-expect'

const createExampleTextReducer =
Expand All @@ -21,7 +24,8 @@ describe('React', () => {
render() {
return (
<ReactReduxContext.Consumer>
{({ store }) => {
{(props) => {
let { store } = props as ReactReduxContextValue
let text = ''

if (store) {
Expand All @@ -46,10 +50,6 @@ describe('React', () => {
it('should not enforce a single child', () => {
const store = createStore(() => ({}))

// Ignore propTypes warnings
const propTypes = Provider.propTypes
Provider.propTypes = {}

const spy = jest.spyOn(console, 'error').mockImplementation(() => {})

expect(() =>
Expand All @@ -59,7 +59,7 @@ describe('React', () => {
</Provider>
)
).not.toThrow()

//@ts-expect-error
expect(() => rtl.render(<Provider store={store} />)).not.toThrow(
/children with exactly one child/
)
Expand All @@ -73,7 +73,6 @@ describe('React', () => {
)
).not.toThrow(/a single React element child/)
spy.mockRestore()
Provider.propTypes = propTypes
})

it('should add the store to context', () => {
Expand All @@ -94,14 +93,18 @@ describe('React', () => {
})

it('accepts new store in props', () => {
const store1 = createStore((state = 10) => state + 1)
const store2 = createStore((state = 10) => state * 2)
const store3 = createStore((state = 10) => state * state + 1)

let externalSetState
class ProviderContainer extends Component {
constructor() {
super()
const store1 = createStore((state: number = 10) => state + 1)
const store2 = createStore((state: number = 10) => state * 2)
const store3 = createStore((state: number = 10) => state * state + 1)

interface StateType {
store: Store
}

let externalSetState: Dispatch<StateType>
class ProviderContainer extends Component<unknown, StateType> {
constructor(props: {}) {
super(props)
this.state = { store: store1 }
externalSetState = this.setState.bind(this)
}
Expand Down Expand Up @@ -156,33 +159,47 @@ describe('React', () => {
})

it('should handle subscriptions correctly when there is nested Providers', () => {
const reducer = (state = 0, action) =>
interface ActionType {
type: string
}
interface TStateProps {
count: number
}
const reducer = (state = 0, action: ActionType) =>
action.type === 'INC' ? state + 1 : state

const innerStore = createStore(reducer)
const innerMapStateToProps = jest.fn((state) => ({ count: state }))
@connect(innerMapStateToProps)
class Inner extends Component {
render() {
const innerMapStateToProps = jest.fn<TStateProps, [number]>((state) => ({
count: state,
}))
class Inner extends Component<TStateProps> {
render(): JSX.Element {
return <div>{this.props.count}</div>
}
}

const WrapperInner = connect<TStateProps, unknown, unknown, number>(
innerMapStateToProps
)(Inner)

const outerStore = createStore(reducer)
@connect((state) => ({ count: state }))
class Outer extends Component {
render() {
return (
<Provider store={innerStore}>
<Inner />
<WrapperInner />
</Provider>
)
}
}

const WrapperOuter = connect<TStateProps, unknown, unknown, number>(
(state) => ({ count: state })
)(Outer)

rtl.render(
<Provider store={outerStore}>
<Outer />
<WrapperOuter />
</Provider>
)
expect(innerMapStateToProps).toHaveBeenCalledTimes(1)
Expand All @@ -195,50 +212,69 @@ describe('React', () => {
})

it('should pass state consistently to mapState', () => {
function stringBuilder(prev = '', action) {
interface ActionType {
type: string
body: string
}
function stringBuilder(prev = '', action: ActionType) {
return action.type === 'APPEND' ? prev + action.body : prev
}

const store = createStore(stringBuilder)
const store: Store = createStore(stringBuilder)

rtl.act(() => {
store.dispatch({ type: 'APPEND', body: 'a' })
})

let childMapStateInvokes = 0

@connect((state) => ({ state }))
class Container extends Component {
emitChange() {
store.dispatch({ type: 'APPEND', body: 'b' })
}
const childCalls: Array<Array<string>> = []

interface ChildContainerProps {
parentState: string
}
class ChildContainer extends Component<ChildContainerProps> {
render() {
return (
<div>
<button onClick={this.emitChange.bind(this)}>change</button>
<ChildContainer parentState={this.props.state} />
</div>
)
return <div />
}
}

const childCalls = []
@connect((state, parentProps) => {
const WrapperChildrenContainer = connect<
{},
unknown,
ChildContainerProps,
string
>((state, parentProps) => {
childMapStateInvokes++
childCalls.push([state, parentProps.parentState])
// The state from parent props should always be consistent with the current state
return {}
})
class ChildContainer extends Component {
})(ChildContainer)

interface TStateProps {
state: string
}
class Container extends Component<TStateProps> {
emitChange() {
store.dispatch({ type: 'APPEND', body: 'b' })
}

render() {
return <div />
return (
<div>
<button onClick={this.emitChange.bind(this)}>change</button>
<WrapperChildrenContainer parentState={this.props.state} />
</div>
)
}
}
const WrapperContainer = connect<TStateProps, unknown, unknown, string>(
(state) => ({ state })
)(Container)

const tester = rtl.render(
<Provider store={store}>
<Container />
<WrapperContainer />
</Provider>
)

Expand Down Expand Up @@ -320,37 +356,52 @@ describe('React', () => {
})

it('should handle store and children change in a the same render', () => {
interface PropsType {
value: string
}
interface StateType {
nestedA: PropsType
nestedB: PropsType
}
const reducerA = (state = { nestedA: { value: 'expectedA' } }) => state
const reducerB = (state = { nestedB: { value: 'expectedB' } }) => state

const storeA = createStore(reducerA)
const storeB = createStore(reducerB)

@connect((state) => ({ value: state.nestedA.value }))
class ComponentA extends Component {
class ComponentA extends Component<PropsType> {
render() {
return <div data-testid="value">{this.props.value}</div>
}
}

@connect((state) => ({ value: state.nestedB.value }))
class ComponentB extends Component {
const WrapperComponentA = connect<PropsType, unknown, unknown, StateType>(
(state) => ({
value: state.nestedA.value,
})
)(ComponentA)

class ComponentB extends Component<PropsType> {
render() {
return <div data-testid="value">{this.props.value}</div>
}
}

const WrapperComponentB = connect<PropsType, unknown, unknown, StateType>(
(state) => ({ value: state.nestedB.value })
)(ComponentB)

const { getByTestId, rerender } = rtl.render(
<Provider store={storeA}>
<ComponentA />
<WrapperComponentA />
</Provider>
)

expect(getByTestId('value')).toHaveTextContent('expectedA')

rerender(
<Provider store={storeB}>
<ComponentB />
<WrapperComponentB />
</Provider>
)

Expand Down
3 changes: 1 addition & 2 deletions test/hooks/useDispatch.spec.tsx
Expand Up @@ -6,8 +6,7 @@ import {
useDispatch,
createDispatchHook,
} from '../../src/index'
import type { ProviderProps } from '../../src/'
import type { ReactReduxContextValue } from '../../src/components/Context'
import type { ProviderProps, ReactReduxContextValue } from '../../src/'

const store = createStore((c: number = 1): number => c + 1)
const store2 = createStore((c: number = 1): number => c + 2)
Expand Down
9 changes: 6 additions & 3 deletions test/hooks/useSelector.spec.tsx
Expand Up @@ -14,9 +14,12 @@ import {
import { useReduxContext } from '../../src/hooks/useReduxContext'
import type { FunctionComponent, DispatchWithoutAction, ReactNode } from 'react'
import type { Store, AnyAction } from 'redux'
import type { ProviderProps, TypedUseSelectorHook } from '../../src/'
import type { Subscription } from '../../src/utils/Subscription'
import type { ReactReduxContextValue } from '../../src/components/Context'
import type {
ProviderProps,
TypedUseSelectorHook,
ReactReduxContextValue,
Subscription,
} from '../../src/'

describe('React', () => {
describe('hooks', () => {
Expand Down

0 comments on commit 19ccf75

Please sign in to comment.