Skip to content

Commit

Permalink
refactor: refactor waitForStoreChange to use assertion callback
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The method waitForStoreChange now uses an assertion callback as first parameter.
The callback's argument is the current state. And you can perform any assertions with it.
  • Loading branch information
jabro86 committed May 27, 2019
1 parent 9dbfce3 commit 624089c
Show file tree
Hide file tree
Showing 19 changed files with 130 additions and 105 deletions.
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@
"@commitlint/config-conventional"
]
},
"dependencies": {
"react-testing-library": "^7.0.1"
},
"devDependencies": {
"@commitlint/cli": "^7.6.1",
"@commitlint/config-conventional": "^7.6.0",
Expand All @@ -105,8 +108,9 @@
"react-redux": "^7.0.3",
"redux": "^4.0.1",
"replace-in-file": "^4.1.0",
"reselect": "^4.0.0",
"rimraf": "^2.6.3",
"rollup": "^1.12.3",
"rollup": "^1.12.4",
"rollup-plugin-commonjs": "^10.0.0",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-node-resolve": "^5.0.0",
Expand All @@ -116,7 +120,7 @@
"shelljs": "^0.8.3",
"travis-deploy-once": "^5.0.11",
"ts-jest": "^24.0.2",
"ts-node": "^8.1.1",
"ts-node": "^8.2.0",
"tslint": "^5.16.0",
"tslint-config-prettier": "^1.18.0",
"tslint-config-standard": "^8.0.1",
Expand All @@ -125,8 +129,5 @@
},
"peerDependencies": {
"redux": "^4.0.1"
},
"dependencies": {
"react-testing-library": "^7.0.1"
}
}
9 changes: 4 additions & 5 deletions src/observeStore.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { Store, Unsubscribe } from "redux";

export interface Options {
store: Store;
select?(state: object): any;
readonly store: Store;
onChange(currentState: object): void;
}

export default function observeStore({ store, onChange, select }: Options): Unsubscribe {
export default function observeStore({ store, onChange }: Options): Unsubscribe {
let currentState: object;

function handleChange() {
const nextState = select ? select(store.getState()) : store.getState();
function handleChange(): void {
const nextState = store.getState();
if (nextState !== currentState) {
currentState = nextState;
onChange(currentState);
Expand Down
2 changes: 1 addition & 1 deletion src/redux-testing-library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function customRender(ui: React.ReactElement<any>, store: Store, options?: Rende
const result = render(ui, options);
return {
...result,
reduxStore: store,
store,
waitForStoreChange: waitForStoreChange(store)
};
}
Expand Down
30 changes: 12 additions & 18 deletions src/waitFor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,39 @@ interface Options {
}

export type WaitFor = (
callback: (state: any) => { result: boolean; error: Error } | boolean,
assertionCallback: (state: any) => void,
options?: Options
) => Promise<boolean | undefined>;

export default function waitFor(store: Store<object, AnyAction>): WaitFor {
return (callback, options = {}) =>
return (assertionCallback, options = {}) =>
new Promise((resolve, reject) => {
let lastError: Error;
const { timeout = 4500 } = options;
const timer = setTimeout(onTimeout, timeout);
const unsubscribe = observeStore({ store, onChange });

function onDone({ error, result }: { error?: Error; result?: boolean }) {
function onDone({ error, result }: { error?: Error; result?: boolean }): void {
clearTimeout(timer);
setImmediate(() => {
unsubscribe();
});
if (error) {
reject(error);
} else {
if (result) {
resolve(result);
} else {
reject(error);
}
}

function onChange(currentState: object) {
function onChange(currentState: object): void {
try {
const response = callback(currentState);
const { result, error } =
typeof response === "boolean" ? { result: response, error: undefined } : response;
if (result) {
onDone({ result });
} else if (error) {
lastError = error;
}
} catch (err) {
lastError = err;
assertionCallback(currentState);
onDone({ result: true });
} catch (error) {
lastError = error;
}
}
function onTimeout() {
function onTimeout(): void {
onDone({ error: lastError || new Error("Timed out in waitForElement.") });
}
onChange(store.getState());
Expand Down
10 changes: 3 additions & 7 deletions test/example/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { VisibilityFilter } from "../reducers";

let nextTodoId = 0;
export const addTodo = (text: string) => ({
type: "ADD_TODO",
id: nextTodoId++,
text
});

export const setVisibilityFilter = (filter: string) => ({
export const setVisibilityFilter = (filter: VisibilityFilter) => ({
type: "SET_VISIBILITY_FILTER",
filter
});
Expand All @@ -14,9 +16,3 @@ export const toggleTodo = (id: number) => ({
type: "TOGGLE_TODO",
id
});

export const VisibilityFilters = {
SHOW_ALL: "SHOW_ALL",
SHOW_COMPLETED: "SHOW_COMPLETED",
SHOW_ACTIVE: "SHOW_ACTIVE"
};
7 changes: 3 additions & 4 deletions test/example/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import * as React from "react";

import { VisibilityFilters } from "../actions";
import FilterLink from "../containers/FilterLink";

const Footer: React.ComponentType = () => (
<div>
<span>Show: </span>
<FilterLink filter={VisibilityFilters.SHOW_ALL}>All</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>Active</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>Completed</FilterLink>
<FilterLink filter="SHOW_ALL">All</FilterLink>
<FilterLink filter="SHOW_ACTIVE">Active</FilterLink>
<FilterLink filter="SHOW_COMPLETED">Completed</FilterLink>
</div>
);

Expand Down
12 changes: 4 additions & 8 deletions test/example/components/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as React from "react";

import { VisibilityFilter } from "../reducers";

export interface OwnProps {
filter: string;
filter: VisibilityFilter;
children?: React.ReactNode;
}

Expand All @@ -16,13 +18,7 @@ export interface DispatchProps {
type Props = OwnProps & StateProps & DispatchProps;

const Link: React.ComponentType<Props> = ({ active, children, onClick }) => (
<button
onClick={onClick}
disabled={active}
style={{
marginLeft: "4px"
}}
>
<button onClick={onClick} disabled={active} style={{ marginLeft: "4px" }}>
{children}
</button>
);
Expand Down
7 changes: 1 addition & 6 deletions test/example/components/Todo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@ interface Props {
}

const Todo: React.ComponentType<Props> = ({ onClick, completed, text }) => (
<li
onClick={onClick}
style={{
textDecoration: completed ? "line-through" : "none"
}}
>
<li onClick={onClick} style={{ textDecoration: completed ? "line-through" : "none" }}>
{text}
</li>
);
Expand Down
7 changes: 1 addition & 6 deletions test/example/components/TodoList.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import * as React from "react";

import { TodoItem } from "../reducers";
import Todo from "./Todo";

export interface TodoItem {
id: number;
completed: boolean;
text: string;
}

export interface StateProps {
todos: TodoItem[];
}
Expand Down
5 changes: 3 additions & 2 deletions test/example/containers/FilterLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { connect } from "react-redux";

import { setVisibilityFilter } from "../actions";
import Link, { DispatchProps, OwnProps, StateProps } from "../components/Link";
import * as TodoSelectors from "../selectors";

export default connect<StateProps, DispatchProps, OwnProps>(
(state: any, ownProps) => ({
active: ownProps.filter === state.visibilityFilter
(state, ownProps) => ({
active: ownProps.filter === TodoSelectors.getVisibilityFilter(state)
}),
(dispatch, ownProps) => ({
onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
Expand Down
32 changes: 20 additions & 12 deletions test/example/containers/VisibleTodoList.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
import { connect } from "react-redux";

import { toggleTodo, VisibilityFilters } from "../actions";
import TodoList, { StateProps, DispatchProps, OwnProps, TodoItem } from "../components/TodoList";
import { toggleTodo } from "../actions";
import TodoList, { DispatchProps, OwnProps, StateProps } from "../components/TodoList";
import { TodoItem, VisibilityFilter } from "../reducers";
import * as TodoSelectors from "../selectors";

const getVisibleTodos = (todos: TodoItem[], filter: string) => {
const getVisibleTodos = (todos: TodoItem[], filter: VisibilityFilter) => {
switch (filter) {
case VisibilityFilters.SHOW_ALL:
case "SHOW_ALL":
return todos;
case VisibilityFilters.SHOW_COMPLETED:
case "SHOW_COMPLETED":
return todos.filter(t => t.completed);
case VisibilityFilters.SHOW_ACTIVE:
case "SHOW_ACTIVE":
return todos.filter(t => !t.completed);
default:
throw new Error("Unknown filter: " + filter);
}
};

export default connect<StateProps, DispatchProps, OwnProps>(
(state: any) => ({
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}),
dispatch => ({
toggleTodo: id => dispatch(toggleTodo(id))
})
function mapStateToProps(state, ownProps) {
const todos = TodoSelectors.getTodos(state);
const visibilityFilter = TodoSelectors.getVisibilityFilter(state);
return {
todos: getVisibleTodos(todos, visibilityFilter)
};
},
function mapDispatchToProps(dispatch, ownProps): DispatchProps {
return {
toggleTodo: id => dispatch(toggleTodo(id))
};
}
)(TodoList);
4 changes: 4 additions & 0 deletions test/example/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import { createStore } from "redux";

import App from "./components/App";
import rootReducer from "./reducers";
import * as TodoActions from "./actions";
import * as TodoSelectors from "./selectors";

export const store = createStore(rootReducer);
export const TodoApp: React.ComponentType = () => (
<Provider store={store}>
<App />
</Provider>
);

export { TodoActions, TodoSelectors };
6 changes: 4 additions & 2 deletions test/example/reducers/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { combineReducers } from "redux";

import todos from "./todos";
import visibilityFilter from "./visibilityFilter";
import todos, { TodoItem } from "./todos";
import visibilityFilter, { VisibilityFilter } from "./visibilityFilter";

export default combineReducers({
todos,
visibilityFilter
});

export { TodoItem, VisibilityFilter };
8 changes: 6 additions & 2 deletions test/example/reducers/todos.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { AnyAction } from "redux";

import { TodoItem } from "../components/TodoList";
export interface TodoItem {
id: number;
text: string;
completed: boolean;
}

const todos = (state: TodoItem[] = [], action: AnyAction) => {
const todos = (state: TodoItem[] = [], action: AnyAction): TodoItem[] => {
switch (action.type) {
case "ADD_TODO":
return [
Expand Down
7 changes: 5 additions & 2 deletions test/example/reducers/visibilityFilter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { AnyAction } from "redux";

import { VisibilityFilters } from "../actions";
export type VisibilityFilter = "SHOW_ALL" | "SHOW_COMPLETED" | "SHOW_ACTIVE";

const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action: AnyAction) => {
const visibilityFilter = (
state: VisibilityFilter = "SHOW_ALL",
action: AnyAction
): VisibilityFilter => {
switch (action.type) {
case "SET_VISIBILITY_FILTER":
return action.filter;
Expand Down
25 changes: 25 additions & 0 deletions test/example/selectors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createSelector } from "reselect";

import { TodoItem, VisibilityFilter } from "../reducers";

export function getVisibilityFilter(state: any): VisibilityFilter {
return state.visibilityFilter;
}

export function getTodos(state: any): TodoItem[] {
return state.todos;
}

export const getVisibleTodos = createSelector(
[getVisibilityFilter, getTodos],
(visibilityFilter, todos) => {
switch (visibilityFilter) {
case "SHOW_ALL":
return todos;
case "SHOW_COMPLETED":
return todos.filter(t => t.completed);
case "SHOW_ACTIVE":
return todos.filter(t => !t.completed);
}
}
);
Loading

0 comments on commit 624089c

Please sign in to comment.