diff --git a/jsx/src/components/DynamicTable/DynamicTable.jsx b/jsx/src/components/DynamicTable/DynamicTable.jsx
index 4645e892..8e87662d 100644
--- a/jsx/src/components/DynamicTable/DynamicTable.jsx
+++ b/jsx/src/components/DynamicTable/DynamicTable.jsx
@@ -199,8 +199,8 @@ const DynamicTable = (props) => {
DynamicTable.propTypes = {
current_keys: PropTypes.array,
current_values: PropTypes.array,
- setPropKeys: PropTypes.array,
- setPropValues: PropTypes.array,
+ setPropKeys: PropTypes.func,
+ setPropValues: PropTypes.func,
setProp: PropTypes.func,
};
export default DynamicTable;
diff --git a/jsx/src/components/GroupEdit/GroupEdit.test.jsx b/jsx/src/components/GroupEdit/GroupEdit.test.jsx
index 62505b31..c4bbea4d 100644
--- a/jsx/src/components/GroupEdit/GroupEdit.test.jsx
+++ b/jsx/src/components/GroupEdit/GroupEdit.test.jsx
@@ -6,6 +6,7 @@ import userEvent from "@testing-library/user-event";
import { Provider, useSelector } from "react-redux";
import { createStore } from "redux";
import { HashRouter } from "react-router-dom";
+import { CompatRouter } from "react-router-dom-v5-compat";
// eslint-disable-next-line
import regeneratorRuntime from "regenerator-runtime";
@@ -27,20 +28,22 @@ var okPacket = new Promise((resolve) => resolve(true));
var groupEditJsx = (callbackSpy) => (
{}, {})}>
- {},
- },
- }}
- addToGroup={callbackSpy}
- removeFromGroup={callbackSpy}
- deleteGroup={callbackSpy}
- history={{ push: () => callbackSpy }}
- updateGroups={callbackSpy}
- validateUser={jest.fn().mockImplementation(() => okPacket)}
- />
+
+ {},
+ },
+ }}
+ addToGroup={callbackSpy}
+ removeFromGroup={callbackSpy}
+ deleteGroup={callbackSpy}
+ history={{ push: () => callbackSpy }}
+ updateGroups={callbackSpy}
+ validateUser={jest.fn().mockImplementation(() => okPacket)}
+ />
+
);
diff --git a/jsx/src/components/Groups/Groups.jsx b/jsx/src/components/Groups/Groups.jsx
index 257caf6c..c6add7d3 100644
--- a/jsx/src/components/Groups/Groups.jsx
+++ b/jsx/src/components/Groups/Groups.jsx
@@ -3,7 +3,7 @@ import { useSelector, useDispatch } from "react-redux";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
-import { useSearchParams } from "react-router-dom-v5-compat";
+import { usePaginationParams } from "../../util/paginationParams";
import PaginationFooter from "../PaginationFooter/PaginationFooter";
const Groups = (props) => {
diff --git a/jsx/src/components/Groups/Groups.test.js b/jsx/src/components/Groups/Groups.test.js
index ed24c8bc..8b67cc59 100644
--- a/jsx/src/components/Groups/Groups.test.js
+++ b/jsx/src/components/Groups/Groups.test.js
@@ -5,6 +5,7 @@ import { render, screen, fireEvent } from "@testing-library/react";
import { Provider, useDispatch, useSelector } from "react-redux";
import { createStore } from "redux";
import { HashRouter } from "react-router-dom";
+import { CompatRouter, useSearchParams } from "react-router-dom-v5-compat";
// eslint-disable-next-line
import regeneratorRuntime from "regenerator-runtime";
@@ -16,13 +17,20 @@ jest.mock("react-redux", () => ({
useSelector: jest.fn(),
}));
+jest.mock("react-router-dom-v5-compat", () => ({
+ ...jest.requireActual("react-router-dom-v5-compat"),
+ useSearchParams: jest.fn(),
+}));
+
var mockAsync = () =>
jest.fn().mockImplementation(() => Promise.resolve({ key: "value" }));
var groupsJsx = (callbackSpy) => (
-
+
+
+
);
@@ -50,11 +58,6 @@ var mockAppState = () =>
offset: 0,
limit: 2,
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) => {
return callback(mockAppState());
});
+ useSearchParams.mockImplementation(() => {
+ return [new URLSearchParams(), jest.fn()];
+ });
});
afterEach(() => {
useSelector.mockClear();
mockReducers.mockClear();
+ useSearchParams.mockClear();
});
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 () => {
- 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 () => {
- render(groupsJsx(callbackSpy));
+ render(groupsJsx(upgradeGroupsSpy));
+ [_, setSearchParams] = useSearchParams();
});
- expect(callbackSpy).toBeCalledWith(0, 2);
+ expect(upgradeGroupsSpy).toBeCalledWith(0, 2);
var lastState =
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);
let next = screen.getByTestId("paginate-next");
- fireEvent.click(next);
-
- lastState =
- mockReducers.mock.results[mockReducers.mock.results.length - 1].value;
- expect(lastState.groups_page.offset).toEqual(2);
- expect(lastState.groups_page.limit).toEqual(2);
+ await act(async () => {
+ fireEvent.click(next);
+ });
+ expect(setSearchParamsSpy).toBeCalledWith("limit=2&offset=2");
// FIXME: mocked useSelector, state seem to prevent updateGroups from being called
// making the test environment not representative
diff --git a/jsx/src/components/ServerDashboard/ServerDashboard.jsx b/jsx/src/components/ServerDashboard/ServerDashboard.jsx
index d2bdaee2..c21d6e40 100644
--- a/jsx/src/components/ServerDashboard/ServerDashboard.jsx
+++ b/jsx/src/components/ServerDashboard/ServerDashboard.jsx
@@ -55,13 +55,13 @@ const ServerDashboard = (props) => {
const [sortMethod, setSortMethod] = useState(null);
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 { setOffset, offset, setLimit, handleLimit, limit, setPagination } =
usePaginationParams();
- const name_filter = searchParams.get("name_filter");
+ const name_filter = searchParams.get("name_filter") || "";
const total = user_page ? user_page.total : undefined;
diff --git a/jsx/src/components/ServerDashboard/ServerDashboard.test.js b/jsx/src/components/ServerDashboard/ServerDashboard.test.js
index 11709812..4e579c26 100644
--- a/jsx/src/components/ServerDashboard/ServerDashboard.test.js
+++ b/jsx/src/components/ServerDashboard/ServerDashboard.test.js
@@ -10,6 +10,7 @@ import {
getAllByRole,
} from "@testing-library/react";
import { HashRouter, Switch } from "react-router-dom";
+import { CompatRouter, useSearchParams } from "react-router-dom-v5-compat";
import { Provider, useSelector } from "react-redux";
import { createStore } from "redux";
// eslint-disable-next-line
@@ -25,20 +26,26 @@ jest.mock("react-redux", () => ({
...jest.requireActual("react-redux"),
useSelector: jest.fn(),
}));
+jest.mock("react-router-dom-v5-compat", () => ({
+ ...jest.requireActual("react-router-dom-v5-compat"),
+ useSearchParams: jest.fn(),
+}));
var serverDashboardJsx = (spy) => (
-
-
-
+
+
+
+
+
);
@@ -137,14 +144,25 @@ var mockReducers = jest.fn((state, action) => {
return state;
});
+let searchParams = new URLSearchParams();
+
beforeEach(() => {
clock = sinon.useFakeTimers();
useSelector.mockImplementation((callback) => {
return callback(mockAppState());
});
+ searchParams = new URLSearchParams();
+
+ useSearchParams.mockImplementation(() => [
+ searchParams,
+ (callback) => {
+ searchParams = callback(searchParams);
+ },
+ ]);
});
afterEach(() => {
+ useSearchParams.mockClear();
useSelector.mockClear();
mockReducers.mockClear();
clock.restore();
@@ -350,16 +368,16 @@ test("Shows server details with button click", async () => {
await act(async () => {
fireEvent.click(button);
+ await clock.tick(400);
});
- clock.tick(400);
expect(collapse).toHaveClass("collapse show");
expect(collapseBar).not.toHaveClass("show");
await act(async () => {
fireEvent.click(button);
+ await clock.tick(400);
});
- clock.tick(400);
expect(collapse).toHaveClass("collapse");
expect(collapse).not.toHaveClass("show");
@@ -367,8 +385,8 @@ test("Shows server details with button click", async () => {
await act(async () => {
fireEvent.click(button);
+ await clock.tick(400);
});
- clock.tick(400);
expect(collapse).toHaveClass("collapse show");
expect(collapseBar).not.toHaveClass("show");
@@ -398,16 +416,18 @@ test("Shows a UI error dialogue when start all servers fails", async () => {
render(
{}, {})}>
-
-
-
+
+
+
+
+
,
);
@@ -432,16 +452,18 @@ test("Shows a UI error dialogue when stop all servers fails", async () => {
render(
{}, {})}>
-
-
-
+
+
+
+
+
,
);
@@ -466,16 +488,18 @@ test("Shows a UI error dialogue when start user server fails", async () => {
render(
{}, {})}>
-
-
-
+
+
+
+
+
,
);
@@ -501,16 +525,18 @@ test("Shows a UI error dialogue when start user server returns an improper statu
render(
{}, {})}>
-
-
-
+
+
+
+
+
,
);
@@ -536,16 +562,18 @@ test("Shows a UI error dialogue when stop user servers fails", async () => {
render(
{}, {})}>
-
-
-
+
+
+
+
+
,
);
@@ -570,16 +598,18 @@ test("Shows a UI error dialogue when stop user server returns an improper status
render(
{}, {})}>
-
-
-
+
+
+
+
+
,
);
@@ -616,16 +646,18 @@ test("Search for user calls updateUsers with name filter", async () => {
render(
-
-
-
+
+
+
+
+
,
);
@@ -637,21 +669,20 @@ test("Search for user calls updateUsers with name filter", async () => {
userEvent.type(search, "a");
expect(search.value).toEqual("a");
- clock.tick(400);
- expect(mockReducers.mock.calls).toHaveLength(3);
- var lastState =
- mockReducers.mock.results[mockReducers.mock.results.length - 1].value;
- expect(lastState.name_filter).toEqual("a");
- // TODO: this should
- expect(mockUpdateUsers.mock.calls).toHaveLength(1);
+ await act(async () => {
+ await clock.tick(400);
+ });
+ expect(searchParams.get("name_filter")).toEqual("a");
+ // FIXME: useSelector mocks prevent updateUsers from being called
+ // expect(mockUpdateUsers.mock.calls).toHaveLength(2);
+ // expect(mockUpdateUsers).toBeCalledWith(0, 100, "a");
userEvent.type(search, "b");
expect(search.value).toEqual("ab");
- clock.tick(400);
- expect(mockReducers.mock.calls).toHaveLength(4);
- lastState =
- mockReducers.mock.results[mockReducers.mock.results.length - 1].value;
- expect(lastState.name_filter).toEqual("ab");
- expect(lastState.user_page.offset).toEqual(0);
+ await act(async () => {
+ jest.runAllTimers();
+ });
+ expect(searchParams.get("name_filter")).toEqual("ab");
+ // expect(mockUpdateUsers).toBeCalledWith(0, 100, "ab");
});
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));
});
- expect(callbackSpy).toBeCalledWith(0, 2, "");
+ expect(callbackSpy).toBeCalledWith(0, 100, "");
- expect(mockReducers.mock.results).toHaveLength(2);
- lastState =
- mockReducers.mock.results[mockReducers.mock.results.length - 1].value;
- console.log(lastState);
- expect(lastState.user_page.offset).toEqual(0);
- expect(lastState.user_page.limit).toEqual(2);
+ var n = 3;
+ expect(searchParams.get("offset")).toEqual(null);
+ expect(searchParams.get("limit")).toEqual(null);
let next = screen.getByTestId("paginate-next");
- fireEvent.click(next);
- clock.tick(400);
+ await act(async () => {
+ fireEvent.click(next);
+ await clock.tick(400);
+ });
- expect(mockReducers.mock.results).toHaveLength(3);
- var lastState =
- mockReducers.mock.results[mockReducers.mock.results.length - 1].value;
- expect(lastState.user_page.offset).toEqual(2);
- expect(lastState.user_page.limit).toEqual(2);
+ expect(searchParams.get("offset")).toEqual("100");
+ expect(searchParams.get("limit")).toEqual(null);
// FIXME: should call updateUsers, does in reality.
// tests don't reflect reality due to mocked state/useSelector
@@ -721,16 +748,18 @@ test("Start server and confirm pending state", async () => {
render(
-
-
-
+
+
+
+
+
,
);
diff --git a/jsx/src/util/paginationParams.js b/jsx/src/util/paginationParams.js
index 7fe97381..49fa207b 100644
--- a/jsx/src/util/paginationParams.js
+++ b/jsx/src/util/paginationParams.js
@@ -6,7 +6,7 @@ export const usePaginationParams = () => {
const [searchParams, setSearchParams] = useSearchParams();
const offset = parseInt(searchParams.get("offset", "0")) || 0;
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) => {
if (offset < 0) offset = 0;
@@ -26,6 +26,9 @@ export const usePaginationParams = () => {
};
const setPagination = (pagination) => {
// update pagination in one
+ if (!pagination) {
+ return;
+ }
setSearchParams((params) => {
_setOffset(params, pagination.offset);
_setLimit(params, pagination.limit);