Skip to content

Commit

Permalink
test: create connect
Browse files Browse the repository at this point in the history
  • Loading branch information
iamogbz committed Sep 14, 2020
1 parent 201f892 commit f4207e7
Show file tree
Hide file tree
Showing 13 changed files with 363 additions and 97 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-test-renderer": "^16.13.1",
"reselect": "^4.0.0",
"rxjs": "^6.6.3",
"semantic-release": "^17.1.1",
"stylelint": "^13.7.1",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from "react";

const PlaceholderContext = React.createContext(null);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export let GlobalContext: any = PlaceholderContext;
export let GlobalContext: React.Context<any> = PlaceholderContext;

export function setGlobalContext<T>(Context: React.Context<T>): void {
if (GlobalContext !== PlaceholderContext) {
Expand Down
5 changes: 2 additions & 3 deletions src/createConnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { connect } from "./utils/connect";
export function createConnect<S, I, T extends string, P, K>(
Context?: Context<S, T, P>,
) {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
return function connectContext(
mapStateToProps?: MapStateToProps<S, I, K>,
mapDispatchToProps?:
Expand All @@ -14,9 +15,7 @@ export function createConnect<S, I, T extends string, P, K>(
ownProps?: I,
) => I & K & ActionDispatcherMapping<T, P>,
options?: ConnectOptions<S, T, P, I, K>,
): (
component: ReactComponent<I & K & ActionDispatcherMapping<T, P>>,
) => React.FunctionComponent<I> {
) {
return connect(mapStateToProps, mapDispatchToProps, mergeProps, {
...options,
context: Context,
Expand Down
3 changes: 1 addition & 2 deletions src/hooks/useDispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { GlobalContext } from "..";
export function useDispatch<S, T extends string, P>(
actionCreator: ActionCreator<T, P>,
Context?: Context<S, T, P>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): (...args: any[]) => void {
): (...args: P[]) => void {
const { dispatch } = React.useContext(Context ?? GlobalContext);
// eslint-disable-next-line react-hooks/exhaustive-deps
return React.useCallback(bindActionCreator(actionCreator, dispatch), [
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { Provider } from "./components/Provider";
export { applyMiddleware } from "./utils/applyMiddleware";
export { createAction } from "./createAction";
export { createContext } from "./createContext";
export { createConnect } from "./createConnect";
export { createDuck } from "./createDuck";
export { createReducer } from "./createReducer";
export { createRootDuck } from "./createRootDuck";
Expand Down
6 changes: 3 additions & 3 deletions src/utils/bindActionCreators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
export function bindActionCreator<T extends string, P>(
actionCreator: ActionCreator<T, P>,
dispatch: ContextDispatch<T, P>,
): ActionDispatcher<P> {
): ActionDispatcher<T, P> {
return function dispatchAction(...args: P[]): void {
dispatch(actionCreator(...args));
};
Expand All @@ -17,12 +17,12 @@ export function bindActionCreator<T extends string, P>(
export function bindActionCreators<T extends string, P>(
actionCreator: ActionCreator<T, P>,
dispatch: ContextDispatch<T, P>,
): ActionDispatcher<P>;
): ActionDispatcher<T, P>;

export function bindActionCreators<T extends string, P>(
actionCreator: ActionCreator<T, P>,
dispatch: ContextDispatch<T, P>,
): ActionDispatcher<P>;
): ActionDispatcher<T, P>;

export function bindActionCreators<T extends string, P, S>(
actionCreators: ActionCreatorMapping<T, P, S>,
Expand Down
31 changes: 24 additions & 7 deletions src/utils/connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,26 +45,33 @@ export function connect<
ownProps?: I,
) => J,
options?: ConnectOptions<S, T, P, I, K, J>,
): (component: ReactComponent<J>) => React.FunctionComponent<I> {
): (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
component: ReactComponent<any>,
) =>
| React.FunctionComponent<I>
| React.ForwardRefExoticComponent<
React.PropsWithoutRef<I> & React.RefAttributes<unknown>
> {
const mapDispatchToPropsFn = isFunction<MapDispatchToProps<T, P, I>>(
mapDispatchToProps,
)
? mapDispatchToProps
: asMapDispatchToPropsFn(mapDispatchToProps);

return function wrapWithConnect(
componentToWrap: ReactComponent<J>,
): React.FunctionComponent<I> {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
return function wrapWithConnect(componentToWrap: ReactComponent<J>) {
const WrappedComponent = (options?.pure
? React.memo(componentToWrap)
: componentToWrap) as ReactComponent<J>;
const wrappedComponentName =
componentToWrap.displayName || componentToWrap.name || "Component";
const displayName = `Connect(${wrappedComponentName})`;

function WrapperComponent(
function WrapperComponent<R = unknown>(
props: I,
): React.ReactElement<J, typeof WrappedComponent> {
ref?: React.Ref<R>,
): React.ReactElement {
const { state, dispatch } = React.useContext(
options?.context ?? GlobalContext,
);
Expand All @@ -85,11 +92,21 @@ export function connect<
),
[dispatchProps, props, stateProps],
);
return <WrappedComponent {...mergedProps} />;
const finalProps = React.useMemo(
() =>
options?.forwardRef ? { ...mergedProps, ref } : mergedProps,
[mergedProps, ref],
);
return <WrappedComponent {...finalProps} />;
}

WrapperComponent.WrappedComponent = WrappedComponent;
WrapperComponent.displayName = displayName;

if (options?.forwardRef) {
return React.forwardRef(WrapperComponent);
}

return WrapperComponent;
};
}
11 changes: 3 additions & 8 deletions tests/__mocks__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,8 @@ import {
} from "src";
import { ActionTypes } from "src/utils/actionTypes";

export function createMocks(): {
EnhancedContext: Context<Record<string, unknown>>;
ErrorContext: Context<Record<string, unknown>>;
Example: React.FunctionComponent;
RootProvider: React.FunctionComponent;
increment: jest.MockedFunction<(s: number) => number>;
init: jest.MockedFunction<() => boolean>;
} {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createMocks() {
const dummyMiddleware: Middleware<
Record<string, unknown>,
string,
Expand Down Expand Up @@ -129,5 +123,6 @@ export function createMocks(): {
RootProvider,
increment,
init,
rootDuck,
};
}
52 changes: 50 additions & 2 deletions tests/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`e2e renders with root provider and updates on action dispatch 1`] = `
exports[`e2e createConnect correctly connects action creators to props 1`] = `
<body>
<div>
<div>
Expand All @@ -16,7 +16,55 @@ exports[`e2e renders with root provider and updates on action dispatch 1`] = `
</body>
`;

exports[`e2e renders without root provider 1`] = `
exports[`e2e createConnect correctly connects state and dispatch to props 1`] = `
<body>
<div>
<div>
Count:
<span>
2
</span>
<button>
increment
</button>
</div>
</div>
</body>
`;

exports[`e2e createConnect correctly uses mergeProps function 1`] = `
<body>
<div>
<div>
Count:
<span>
Replaces count with a static value
</span>
<button>
increment
</button>
</div>
</div>
</body>
`;

exports[`e2e createContext renders with root provider and updates on action dispatch 1`] = `
<body>
<div>
<div>
Count:
<span>
2
</span>
<button>
increment
</button>
</div>
</div>
</body>
`;

exports[`e2e createContext renders without root provider 1`] = `
<body>
<div>
<div>
Expand Down

0 comments on commit f4207e7

Please sign in to comment.