-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Josh Goldberg
committed
May 25, 2020
1 parent
2d74f2e
commit ccd630c
Showing
7 changed files
with
241 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import * as React from "react"; | ||
import type { MapStateToProps, MapDispatchToProps } from "react-redux"; | ||
import mapValues from "lodash.mapvalues"; | ||
|
||
type AnyAction = (payload: any) => void; | ||
|
||
const defaultMergeProps = (...sources: unknown[]) => Object.assign({}, ...sources); | ||
|
||
export function mockConnect<State>(getDispatch: () => jest.Mock, getState: () => State) { | ||
return function <OwnProps, StateProps, DispatchProps extends Record<string, AnyAction>>( | ||
mapStateToProps?: MapStateToProps<StateProps, OwnProps, State>, | ||
mapDispatchToProps?: MapDispatchToProps<DispatchProps, OwnProps>, | ||
mergeProps = defaultMergeProps, | ||
) { | ||
const createStateProps = (ownProps: OwnProps) => mapStateToProps?.(getState(), ownProps) ?? {}; | ||
|
||
const createDispatchProps = (ownProps: OwnProps) => { | ||
if (!mapDispatchToProps) { | ||
return {}; | ||
} | ||
|
||
const dispatch = getDispatch(); | ||
|
||
return mapDispatchToProps instanceof Function | ||
? mapDispatchToProps(dispatch, ownProps) | ||
: mapValues( | ||
mapDispatchToProps, | ||
(action): AnyAction => (payload) => dispatch(action(payload)), | ||
); | ||
}; | ||
|
||
return function mockConnectComponent(Component: React.ComponentType<any>) { | ||
return function MockConnectedComponent(ownProps: OwnProps) { | ||
return ( | ||
<Component | ||
{...mergeProps(createStateProps(ownProps), createDispatchProps(ownProps), ownProps)} | ||
/> | ||
); | ||
}; | ||
}; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,152 @@ | ||
import "mock-redux"; | ||
import { render } from "@testing-library/react"; | ||
import { mockRedux } from "mock-redux"; | ||
import React, { useEffect } from "react"; | ||
import { connect } from "react-redux"; | ||
import { act } from "react-dom/test-utils"; | ||
|
||
const value = "Hi!"; | ||
const payload = { value }; | ||
const action = (payload: unknown) => ({ payload, type: "ACTION" }); | ||
|
||
type WithAction = { action: typeof action }; | ||
type WithValue = { value: string }; | ||
|
||
describe("connect", () => { | ||
it("throws an error when mock-redux has been imported", async () => { | ||
expect(connect).toThrowError("connect is not supported when using mock-redux."); | ||
it("passes through to the component when there are no mappings", async () => { | ||
const ConnectedRendersValue = connect()((props: { text: string }) => <>{props.text}</>); | ||
|
||
mockRedux(); | ||
|
||
const view = render(<ConnectedRendersValue text={value} />); | ||
|
||
view.getByText(value); | ||
}); | ||
|
||
describe("mapStateToProps", () => { | ||
const RendersValue = ({ value }: WithValue) => { | ||
return <>{value}</>; | ||
}; | ||
|
||
it("throws an error when a connected component is rendered without a mock state", () => { | ||
const selectValue = (state: WithValue) => state.value; | ||
const mapStateToProps = (state: WithValue) => ({ value: selectValue(state) }); | ||
const ConnectedRendersValue = connect(mapStateToProps)(RendersValue); | ||
|
||
mockRedux(); | ||
|
||
expect(() => ConnectedRendersValue({})).toThrowError( | ||
"You included mock-redux but didn't set state before rendering a connect() component.", | ||
); | ||
}); | ||
|
||
it("provides a selector value when mapStateToProps calls a selector", async () => { | ||
const selectValue = (state: WithValue) => state.value; | ||
const mapStateToProps = (state: WithValue) => ({ value: selectValue(state) }); | ||
const ConnectedRendersValue = connect(mapStateToProps)(RendersValue); | ||
|
||
mockRedux().state({ value }); | ||
|
||
const view = render(<ConnectedRendersValue />); | ||
|
||
view.getByText(value); | ||
}); | ||
}); | ||
|
||
describe("mapDispatchToProps", () => { | ||
const FiresAction = ({ action }: WithAction) => { | ||
useEffect(() => { | ||
action(payload); | ||
}, []); | ||
return null; | ||
}; | ||
|
||
it("throws an error when a connected component is rendered without having called mockRedux", () => { | ||
const ConnectedFiresAction = connect(null, { action })(FiresAction); | ||
|
||
expect(() => ConnectedFiresAction({})).toThrowError( | ||
"You included mock-redux but didn't call mockRedux() before calling useDispatch from react-redux.", | ||
); | ||
}); | ||
|
||
it("mocks an action dispatch when mapDispatchToProps is used in the object form", async () => { | ||
const ConnectedFiresAction = connect(null, { action })(FiresAction); | ||
const { dispatch } = mockRedux(); | ||
|
||
act(() => { | ||
render(<ConnectedFiresAction />); | ||
}); | ||
|
||
expect(dispatch).toHaveBeenCalledWith(action(payload)); | ||
}); | ||
|
||
it("mocks an action dispatch when mapDispatchToProps is used in the function form", async () => { | ||
const ConnectedFiresAction = connect(null, (dispatch) => ({ | ||
action: () => dispatch(action(payload)), | ||
}))(FiresAction); | ||
const { dispatch } = mockRedux(); | ||
|
||
act(() => { | ||
render(<ConnectedFiresAction />); | ||
}); | ||
|
||
expect(dispatch).toHaveBeenCalledWith(action(payload)); | ||
}); | ||
}); | ||
|
||
describe("mergeProps", () => { | ||
type TracksPropsProps = Record<string, unknown> & { | ||
spy: jest.Mock; | ||
}; | ||
|
||
const TracksProps = (props: TracksPropsProps) => { | ||
useEffect(() => { | ||
props.spy(props); | ||
}, []); | ||
return null; | ||
}; | ||
|
||
const fromState = { from: "state" }; | ||
const fromDispatch = { from: "dispatch" }; | ||
|
||
const mapStateToProps = () => ({ fromState }); | ||
const mapDispatchToProps = { fromDispatch }; | ||
|
||
it("defaults to a basic merge when not provided", () => { | ||
const spy = jest.fn(); | ||
const ConnectedTracksProps = connect(mapStateToProps, mapDispatchToProps)(TracksProps); | ||
|
||
mockRedux().state({}); | ||
|
||
act(() => { | ||
render(<ConnectedTracksProps spy={spy} />); | ||
}); | ||
|
||
expect(spy).toHaveBeenCalledWith({ | ||
fromDispatch: expect.any(Function), | ||
fromState, | ||
spy, | ||
}); | ||
}); | ||
|
||
it("is used when provided", () => { | ||
const spy = jest.fn(); | ||
const ConnectedTracksProps = connect( | ||
mapStateToProps, | ||
mapDispatchToProps, | ||
(stateProps, dispatchProps, ownProps) => ({ stateProps, dispatchProps, ...ownProps }), | ||
)(TracksProps); | ||
|
||
mockRedux().state({}); | ||
|
||
act(() => { | ||
render(<ConnectedTracksProps spy={spy} />); | ||
}); | ||
|
||
expect(spy).toHaveBeenCalledWith({ | ||
dispatchProps: { fromDispatch: expect.any(Function) }, | ||
stateProps: { fromState }, | ||
spy, | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters