get most jsx tests passing

This commit is contained in:
Min RK
2024-03-06 01:26:34 +01:00
parent 87bf84d05f
commit f13e69b172
7 changed files with 206 additions and 156 deletions

View File

@@ -199,8 +199,8 @@ const DynamicTable = (props) => {
DynamicTable.propTypes = { DynamicTable.propTypes = {
current_keys: PropTypes.array, current_keys: PropTypes.array,
current_values: PropTypes.array, current_values: PropTypes.array,
setPropKeys: PropTypes.array, setPropKeys: PropTypes.func,
setPropValues: PropTypes.array, setPropValues: PropTypes.func,
setProp: PropTypes.func, setProp: PropTypes.func,
}; };
export default DynamicTable; export default DynamicTable;

View File

@@ -6,6 +6,7 @@ import userEvent from "@testing-library/user-event";
import { Provider, useSelector } from "react-redux"; import { Provider, useSelector } from "react-redux";
import { createStore } from "redux"; import { createStore } from "redux";
import { HashRouter } from "react-router-dom"; import { HashRouter } from "react-router-dom";
import { CompatRouter } from "react-router-dom-v5-compat";
// eslint-disable-next-line // eslint-disable-next-line
import regeneratorRuntime from "regenerator-runtime"; import regeneratorRuntime from "regenerator-runtime";
@@ -27,20 +28,22 @@ var okPacket = new Promise((resolve) => resolve(true));
var groupEditJsx = (callbackSpy) => ( var groupEditJsx = (callbackSpy) => (
<Provider store={createStore(() => {}, {})}> <Provider store={createStore(() => {}, {})}>
<HashRouter> <HashRouter>
<GroupEdit <CompatRouter>
location={{ <GroupEdit
state: { location={{
group_data: { users: ["foo"], name: "group" }, state: {
callback: () => {}, group_data: { users: ["foo"], name: "group" },
}, callback: () => {},
}} },
addToGroup={callbackSpy} }}
removeFromGroup={callbackSpy} addToGroup={callbackSpy}
deleteGroup={callbackSpy} removeFromGroup={callbackSpy}
history={{ push: () => callbackSpy }} deleteGroup={callbackSpy}
updateGroups={callbackSpy} history={{ push: () => callbackSpy }}
validateUser={jest.fn().mockImplementation(() => okPacket)} updateGroups={callbackSpy}
/> validateUser={jest.fn().mockImplementation(() => okPacket)}
/>
</CompatRouter>
</HashRouter> </HashRouter>
</Provider> </Provider>
); );

View File

@@ -3,7 +3,7 @@ import { useSelector, useDispatch } from "react-redux";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useSearchParams } from "react-router-dom-v5-compat"; import { usePaginationParams } from "../../util/paginationParams";
import PaginationFooter from "../PaginationFooter/PaginationFooter"; import PaginationFooter from "../PaginationFooter/PaginationFooter";
const Groups = (props) => { const Groups = (props) => {

View File

@@ -5,6 +5,7 @@ import { render, screen, fireEvent } from "@testing-library/react";
import { Provider, useDispatch, useSelector } from "react-redux"; import { Provider, useDispatch, useSelector } from "react-redux";
import { createStore } from "redux"; import { createStore } from "redux";
import { HashRouter } from "react-router-dom"; import { HashRouter } from "react-router-dom";
import { CompatRouter, useSearchParams } from "react-router-dom-v5-compat";
// eslint-disable-next-line // eslint-disable-next-line
import regeneratorRuntime from "regenerator-runtime"; import regeneratorRuntime from "regenerator-runtime";
@@ -16,13 +17,20 @@ jest.mock("react-redux", () => ({
useSelector: jest.fn(), useSelector: jest.fn(),
})); }));
jest.mock("react-router-dom-v5-compat", () => ({
...jest.requireActual("react-router-dom-v5-compat"),
useSearchParams: jest.fn(),
}));
var mockAsync = () => var mockAsync = () =>
jest.fn().mockImplementation(() => Promise.resolve({ key: "value" })); jest.fn().mockImplementation(() => Promise.resolve({ key: "value" }));
var groupsJsx = (callbackSpy) => ( var groupsJsx = (callbackSpy) => (
<Provider store={createStore(mockReducers, mockAppState())}> <Provider store={createStore(mockReducers, mockAppState())}>
<HashRouter> <HashRouter>
<Groups location={{ search: "0" }} updateGroups={callbackSpy} /> <CompatRouter>
<Groups location={{ search: "0" }} updateGroups={callbackSpy} />
</CompatRouter>
</HashRouter> </HashRouter>
</Provider> </Provider>
); );
@@ -50,11 +58,6 @@ var mockAppState = () =>
offset: 0, offset: 0,
limit: 2, limit: 2,
total: 4, total: 4,
next: {
offset: 2,
limit: 2,
url: "http://localhost:8000/hub/api/groups?offset=2&limit=2",
},
}, },
}); });
@@ -62,11 +65,15 @@ beforeEach(() => {
useSelector.mockImplementation((callback) => { useSelector.mockImplementation((callback) => {
return callback(mockAppState()); return callback(mockAppState());
}); });
useSearchParams.mockImplementation(() => {
return [new URLSearchParams(), jest.fn()];
});
}); });
afterEach(() => { afterEach(() => {
useSelector.mockClear(); useSelector.mockClear();
mockReducers.mockClear(); mockReducers.mockClear();
useSearchParams.mockClear();
}); });
test("Renders", async () => { test("Renders", async () => {
@@ -109,13 +116,23 @@ test("Renders nothing if required data is not available", async () => {
}); });
test("Interacting with PaginationFooter causes state update and refresh via useEffect call", async () => { test("Interacting with PaginationFooter causes state update and refresh via useEffect call", async () => {
let callbackSpy = mockAsync(); let upgradeGroupsSpy = mockAsync();
let setSearchParamsSpy = mockAsync();
let searchParams = new URLSearchParams({ limit: "2" });
useSearchParams.mockImplementation(() => [
searchParams,
(callback) => {
searchParams = callback(searchParams);
setSearchParamsSpy(searchParams.toString());
},
]);
let _, setSearchParams;
await act(async () => { await act(async () => {
render(groupsJsx(callbackSpy)); render(groupsJsx(upgradeGroupsSpy));
[_, setSearchParams] = useSearchParams();
}); });
expect(callbackSpy).toBeCalledWith(0, 2); expect(upgradeGroupsSpy).toBeCalledWith(0, 2);
var lastState = var lastState =
mockReducers.mock.results[mockReducers.mock.results.length - 1].value; mockReducers.mock.results[mockReducers.mock.results.length - 1].value;
@@ -123,12 +140,10 @@ test("Interacting with PaginationFooter causes state update and refresh via useE
expect(lastState.groups_page.limit).toEqual(2); expect(lastState.groups_page.limit).toEqual(2);
let next = screen.getByTestId("paginate-next"); let next = screen.getByTestId("paginate-next");
fireEvent.click(next); await act(async () => {
fireEvent.click(next);
lastState = });
mockReducers.mock.results[mockReducers.mock.results.length - 1].value; expect(setSearchParamsSpy).toBeCalledWith("limit=2&offset=2");
expect(lastState.groups_page.offset).toEqual(2);
expect(lastState.groups_page.limit).toEqual(2);
// FIXME: mocked useSelector, state seem to prevent updateGroups from being called // FIXME: mocked useSelector, state seem to prevent updateGroups from being called
// making the test environment not representative // making the test environment not representative

View File

@@ -55,13 +55,13 @@ const ServerDashboard = (props) => {
const [sortMethod, setSortMethod] = useState(null); const [sortMethod, setSortMethod] = useState(null);
const [collapseStates, setCollapseStates] = useState({}); const [collapseStates, setCollapseStates] = useState({});
const user_data = useSelector((state) => state.user_data); let user_data = useSelector((state) => state.user_data);
const user_page = useSelector((state) => state.user_page); const user_page = useSelector((state) => state.user_page);
const { setOffset, offset, setLimit, handleLimit, limit, setPagination } = const { setOffset, offset, setLimit, handleLimit, limit, setPagination } =
usePaginationParams(); usePaginationParams();
const name_filter = searchParams.get("name_filter"); const name_filter = searchParams.get("name_filter") || "";
const total = user_page ? user_page.total : undefined; const total = user_page ? user_page.total : undefined;

View File

@@ -10,6 +10,7 @@ import {
getAllByRole, getAllByRole,
} from "@testing-library/react"; } from "@testing-library/react";
import { HashRouter, Switch } from "react-router-dom"; import { HashRouter, Switch } from "react-router-dom";
import { CompatRouter, useSearchParams } from "react-router-dom-v5-compat";
import { Provider, useSelector } from "react-redux"; import { Provider, useSelector } from "react-redux";
import { createStore } from "redux"; import { createStore } from "redux";
// eslint-disable-next-line // eslint-disable-next-line
@@ -25,20 +26,26 @@ jest.mock("react-redux", () => ({
...jest.requireActual("react-redux"), ...jest.requireActual("react-redux"),
useSelector: jest.fn(), useSelector: jest.fn(),
})); }));
jest.mock("react-router-dom-v5-compat", () => ({
...jest.requireActual("react-router-dom-v5-compat"),
useSearchParams: jest.fn(),
}));
var serverDashboardJsx = (spy) => ( var serverDashboardJsx = (spy) => (
<Provider store={createStore(mockReducers, mockAppState())}> <Provider store={createStore(mockReducers, mockAppState())}>
<HashRouter> <HashRouter>
<Switch> <CompatRouter>
<ServerDashboard <Switch>
updateUsers={spy} <ServerDashboard
shutdownHub={spy} updateUsers={spy}
startServer={spy} shutdownHub={spy}
stopServer={spy} startServer={spy}
startAll={spy} stopServer={spy}
stopAll={spy} startAll={spy}
/> stopAll={spy}
</Switch> />
</Switch>
</CompatRouter>
</HashRouter> </HashRouter>
</Provider> </Provider>
); );
@@ -137,14 +144,25 @@ var mockReducers = jest.fn((state, action) => {
return state; return state;
}); });
let searchParams = new URLSearchParams();
beforeEach(() => { beforeEach(() => {
clock = sinon.useFakeTimers(); clock = sinon.useFakeTimers();
useSelector.mockImplementation((callback) => { useSelector.mockImplementation((callback) => {
return callback(mockAppState()); return callback(mockAppState());
}); });
searchParams = new URLSearchParams();
useSearchParams.mockImplementation(() => [
searchParams,
(callback) => {
searchParams = callback(searchParams);
},
]);
}); });
afterEach(() => { afterEach(() => {
useSearchParams.mockClear();
useSelector.mockClear(); useSelector.mockClear();
mockReducers.mockClear(); mockReducers.mockClear();
clock.restore(); clock.restore();
@@ -350,16 +368,16 @@ test("Shows server details with button click", async () => {
await act(async () => { await act(async () => {
fireEvent.click(button); fireEvent.click(button);
await clock.tick(400);
}); });
clock.tick(400);
expect(collapse).toHaveClass("collapse show"); expect(collapse).toHaveClass("collapse show");
expect(collapseBar).not.toHaveClass("show"); expect(collapseBar).not.toHaveClass("show");
await act(async () => { await act(async () => {
fireEvent.click(button); fireEvent.click(button);
await clock.tick(400);
}); });
clock.tick(400);
expect(collapse).toHaveClass("collapse"); expect(collapse).toHaveClass("collapse");
expect(collapse).not.toHaveClass("show"); expect(collapse).not.toHaveClass("show");
@@ -367,8 +385,8 @@ test("Shows server details with button click", async () => {
await act(async () => { await act(async () => {
fireEvent.click(button); fireEvent.click(button);
await clock.tick(400);
}); });
clock.tick(400);
expect(collapse).toHaveClass("collapse show"); expect(collapse).toHaveClass("collapse show");
expect(collapseBar).not.toHaveClass("show"); expect(collapseBar).not.toHaveClass("show");
@@ -398,16 +416,18 @@ test("Shows a UI error dialogue when start all servers fails", async () => {
render( render(
<Provider store={createStore(() => {}, {})}> <Provider store={createStore(() => {}, {})}>
<HashRouter> <HashRouter>
<Switch> <CompatRouter>
<ServerDashboard <Switch>
updateUsers={spy} <ServerDashboard
shutdownHub={spy} updateUsers={spy}
startServer={spy} shutdownHub={spy}
stopServer={spy} startServer={spy}
startAll={rejectSpy} stopServer={spy}
stopAll={spy} startAll={rejectSpy}
/> stopAll={spy}
</Switch> />
</Switch>
</CompatRouter>
</HashRouter> </HashRouter>
</Provider>, </Provider>,
); );
@@ -432,16 +452,18 @@ test("Shows a UI error dialogue when stop all servers fails", async () => {
render( render(
<Provider store={createStore(() => {}, {})}> <Provider store={createStore(() => {}, {})}>
<HashRouter> <HashRouter>
<Switch> <CompatRouter>
<ServerDashboard <Switch>
updateUsers={spy} <ServerDashboard
shutdownHub={spy} updateUsers={spy}
startServer={spy} shutdownHub={spy}
stopServer={spy} startServer={spy}
startAll={spy} stopServer={spy}
stopAll={rejectSpy} startAll={spy}
/> stopAll={rejectSpy}
</Switch> />
</Switch>
</CompatRouter>
</HashRouter> </HashRouter>
</Provider>, </Provider>,
); );
@@ -466,16 +488,18 @@ test("Shows a UI error dialogue when start user server fails", async () => {
render( render(
<Provider store={createStore(() => {}, {})}> <Provider store={createStore(() => {}, {})}>
<HashRouter> <HashRouter>
<Switch> <CompatRouter>
<ServerDashboard <Switch>
updateUsers={spy} <ServerDashboard
shutdownHub={spy} updateUsers={spy}
startServer={rejectSpy} shutdownHub={spy}
stopServer={spy} startServer={rejectSpy}
startAll={spy} stopServer={spy}
stopAll={spy} startAll={spy}
/> stopAll={spy}
</Switch> />
</Switch>
</CompatRouter>
</HashRouter> </HashRouter>
</Provider>, </Provider>,
); );
@@ -501,16 +525,18 @@ test("Shows a UI error dialogue when start user server returns an improper statu
render( render(
<Provider store={createStore(() => {}, {})}> <Provider store={createStore(() => {}, {})}>
<HashRouter> <HashRouter>
<Switch> <CompatRouter>
<ServerDashboard <Switch>
updateUsers={spy} <ServerDashboard
shutdownHub={spy} updateUsers={spy}
startServer={rejectSpy} shutdownHub={spy}
stopServer={spy} startServer={rejectSpy}
startAll={spy} stopServer={spy}
stopAll={spy} startAll={spy}
/> stopAll={spy}
</Switch> />
</Switch>
</CompatRouter>
</HashRouter> </HashRouter>
</Provider>, </Provider>,
); );
@@ -536,16 +562,18 @@ test("Shows a UI error dialogue when stop user servers fails", async () => {
render( render(
<Provider store={createStore(() => {}, {})}> <Provider store={createStore(() => {}, {})}>
<HashRouter> <HashRouter>
<Switch> <CompatRouter>
<ServerDashboard <Switch>
updateUsers={spy} <ServerDashboard
shutdownHub={spy} updateUsers={spy}
startServer={spy} shutdownHub={spy}
stopServer={rejectSpy} startServer={spy}
startAll={spy} stopServer={rejectSpy}
stopAll={spy} startAll={spy}
/> stopAll={spy}
</Switch> />
</Switch>
</CompatRouter>
</HashRouter> </HashRouter>
</Provider>, </Provider>,
); );
@@ -570,16 +598,18 @@ test("Shows a UI error dialogue when stop user server returns an improper status
render( render(
<Provider store={createStore(() => {}, {})}> <Provider store={createStore(() => {}, {})}>
<HashRouter> <HashRouter>
<Switch> <CompatRouter>
<ServerDashboard <Switch>
updateUsers={spy} <ServerDashboard
shutdownHub={spy} updateUsers={spy}
startServer={spy} shutdownHub={spy}
stopServer={rejectSpy} startServer={spy}
startAll={spy} stopServer={rejectSpy}
stopAll={spy} startAll={spy}
/> stopAll={spy}
</Switch> />
</Switch>
</CompatRouter>
</HashRouter> </HashRouter>
</Provider>, </Provider>,
); );
@@ -616,16 +646,18 @@ test("Search for user calls updateUsers with name filter", async () => {
render( render(
<Provider store={createStore(mockReducers, mockAppState())}> <Provider store={createStore(mockReducers, mockAppState())}>
<HashRouter> <HashRouter>
<Switch> <CompatRouter>
<ServerDashboard <Switch>
updateUsers={mockUpdateUsers} <ServerDashboard
shutdownHub={spy} updateUsers={mockUpdateUsers}
startServer={spy} shutdownHub={spy}
stopServer={spy} startServer={spy}
startAll={spy} stopServer={spy}
stopAll={spy} startAll={spy}
/> stopAll={spy}
</Switch> />
</Switch>
</CompatRouter>
</HashRouter> </HashRouter>
</Provider>, </Provider>,
); );
@@ -637,21 +669,20 @@ test("Search for user calls updateUsers with name filter", async () => {
userEvent.type(search, "a"); userEvent.type(search, "a");
expect(search.value).toEqual("a"); expect(search.value).toEqual("a");
clock.tick(400); await act(async () => {
expect(mockReducers.mock.calls).toHaveLength(3); await clock.tick(400);
var lastState = });
mockReducers.mock.results[mockReducers.mock.results.length - 1].value; expect(searchParams.get("name_filter")).toEqual("a");
expect(lastState.name_filter).toEqual("a"); // FIXME: useSelector mocks prevent updateUsers from being called
// TODO: this should // expect(mockUpdateUsers.mock.calls).toHaveLength(2);
expect(mockUpdateUsers.mock.calls).toHaveLength(1); // expect(mockUpdateUsers).toBeCalledWith(0, 100, "a");
userEvent.type(search, "b"); userEvent.type(search, "b");
expect(search.value).toEqual("ab"); expect(search.value).toEqual("ab");
clock.tick(400); await act(async () => {
expect(mockReducers.mock.calls).toHaveLength(4); jest.runAllTimers();
lastState = });
mockReducers.mock.results[mockReducers.mock.results.length - 1].value; expect(searchParams.get("name_filter")).toEqual("ab");
expect(lastState.name_filter).toEqual("ab"); // expect(mockUpdateUsers).toBeCalledWith(0, 100, "ab");
expect(lastState.user_page.offset).toEqual(0);
}); });
test("Interacting with PaginationFooter causes state update and refresh via useEffect call", async () => { test("Interacting with PaginationFooter causes state update and refresh via useEffect call", async () => {
@@ -661,24 +692,20 @@ test("Interacting with PaginationFooter causes state update and refresh via useE
render(serverDashboardJsx(callbackSpy)); render(serverDashboardJsx(callbackSpy));
}); });
expect(callbackSpy).toBeCalledWith(0, 2, ""); expect(callbackSpy).toBeCalledWith(0, 100, "");
expect(mockReducers.mock.results).toHaveLength(2); var n = 3;
lastState = expect(searchParams.get("offset")).toEqual(null);
mockReducers.mock.results[mockReducers.mock.results.length - 1].value; expect(searchParams.get("limit")).toEqual(null);
console.log(lastState);
expect(lastState.user_page.offset).toEqual(0);
expect(lastState.user_page.limit).toEqual(2);
let next = screen.getByTestId("paginate-next"); let next = screen.getByTestId("paginate-next");
fireEvent.click(next); await act(async () => {
clock.tick(400); fireEvent.click(next);
await clock.tick(400);
});
expect(mockReducers.mock.results).toHaveLength(3); expect(searchParams.get("offset")).toEqual("100");
var lastState = expect(searchParams.get("limit")).toEqual(null);
mockReducers.mock.results[mockReducers.mock.results.length - 1].value;
expect(lastState.user_page.offset).toEqual(2);
expect(lastState.user_page.limit).toEqual(2);
// FIXME: should call updateUsers, does in reality. // FIXME: should call updateUsers, does in reality.
// tests don't reflect reality due to mocked state/useSelector // tests don't reflect reality due to mocked state/useSelector
@@ -721,16 +748,18 @@ test("Start server and confirm pending state", async () => {
render( render(
<Provider store={createStore(mockReducers, {})}> <Provider store={createStore(mockReducers, {})}>
<HashRouter> <HashRouter>
<Switch> <CompatRouter>
<ServerDashboard <Switch>
updateUsers={mockUpdateUsers} <ServerDashboard
shutdownHub={spy} updateUsers={mockUpdateUsers}
startServer={mockStartServer} shutdownHub={spy}
stopServer={spy} startServer={mockStartServer}
startAll={spy} stopServer={spy}
stopAll={spy} startAll={spy}
/> stopAll={spy}
</Switch> />
</Switch>
</CompatRouter>
</HashRouter> </HashRouter>
</Provider>, </Provider>,
); );

View File

@@ -6,7 +6,7 @@ export const usePaginationParams = () => {
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const offset = parseInt(searchParams.get("offset", "0")) || 0; const offset = parseInt(searchParams.get("offset", "0")) || 0;
const limit = const limit =
parseInt(searchParams.get("limit", "0")) || window.api_page_limit; parseInt(searchParams.get("limit", "0")) || window.api_page_limit || 100;
const _setOffset = (params, offset) => { const _setOffset = (params, offset) => {
if (offset < 0) offset = 0; if (offset < 0) offset = 0;
@@ -26,6 +26,9 @@ export const usePaginationParams = () => {
}; };
const setPagination = (pagination) => { const setPagination = (pagination) => {
// update pagination in one // update pagination in one
if (!pagination) {
return;
}
setSearchParams((params) => { setSearchParams((params) => {
_setOffset(params, pagination.offset); _setOffset(params, pagination.offset);
_setLimit(params, pagination.limit); _setLimit(params, pagination.limit);