mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-13 21:13:01 +00:00
Update unit tests for pagination
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
"build": "yarn && webpack",
|
||||
"hot": "webpack && webpack-dev-server",
|
||||
"place": "cp -r build/admin-react.js ../share/jupyterhub/static/js/admin-react.js",
|
||||
"test": "jest",
|
||||
"test": "jest --verbose",
|
||||
"snap": "jest --updateSnapshot",
|
||||
"lint": "eslint --ext .jsx --ext .js src/",
|
||||
"lint:fix": "eslint --ext .jsx --ext .js src/ --fix"
|
||||
|
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import Enzyme, { mount } from "enzyme";
|
||||
import AddUser from "./AddUser";
|
||||
import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
|
||||
import { Provider, useDispatch } from "react-redux";
|
||||
import { Provider, useDispatch, useSelector } from "react-redux";
|
||||
import { createStore } from "redux";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
|
||||
@@ -11,6 +11,7 @@ Enzyme.configure({ adapter: new Adapter() });
|
||||
jest.mock("react-redux", () => ({
|
||||
...jest.requireActual("react-redux"),
|
||||
useDispatch: jest.fn(),
|
||||
useSelector: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("AddUser Component: ", () => {
|
||||
@@ -23,17 +24,24 @@ describe("AddUser Component: ", () => {
|
||||
<AddUser
|
||||
addUsers={callbackSpy}
|
||||
failRegexEvent={callbackSpy}
|
||||
refreshUserData={callbackSpy}
|
||||
updateUsers={callbackSpy}
|
||||
history={{ push: (a) => {} }}
|
||||
/>
|
||||
</HashRouter>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
var mockAppState = () => ({
|
||||
limit: 3,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
useDispatch.mockImplementation((callback) => {
|
||||
return () => {};
|
||||
});
|
||||
useSelector.mockImplementation((callback) => {
|
||||
return callback(mockAppState());
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import Enzyme, { mount } from "enzyme";
|
||||
import CreateGroup from "./CreateGroup";
|
||||
import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
|
||||
import { Provider, useDispatch } from "react-redux";
|
||||
import { Provider, useDispatch, useSelector } from "react-redux";
|
||||
import { createStore } from "redux";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
|
||||
@@ -11,6 +11,7 @@ Enzyme.configure({ adapter: new Adapter() });
|
||||
jest.mock("react-redux", () => ({
|
||||
...jest.requireActual("react-redux"),
|
||||
useDispatch: jest.fn(),
|
||||
useSelector: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("CreateGroup Component: ", () => {
|
||||
@@ -22,16 +23,23 @@ describe("CreateGroup Component: ", () => {
|
||||
<HashRouter>
|
||||
<CreateGroup
|
||||
createGroup={callbackSpy}
|
||||
refreshGroupsData={callbackSpy}
|
||||
updateGroups={callbackSpy}
|
||||
history={{ push: () => {} }}
|
||||
/>
|
||||
</HashRouter>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
var mockAppState = () => ({
|
||||
limit: 3,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
useDispatch.mockImplementation((callback) => {
|
||||
return () => {};
|
||||
return () => () => {};
|
||||
});
|
||||
useSelector.mockImplementation((callback) => {
|
||||
return callback(mockAppState());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,6 +60,6 @@ describe("CreateGroup Component: ", () => {
|
||||
input.simulate("change", { target: { value: "" } });
|
||||
submit.simulate("click");
|
||||
expect(callbackSpy).toHaveBeenNthCalledWith(1, "");
|
||||
expect(callbackSpy).toHaveBeenNthCalledWith(2);
|
||||
expect(callbackSpy).toHaveBeenNthCalledWith(2, 0, 3);
|
||||
});
|
||||
});
|
||||
|
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import Enzyme, { mount } from "enzyme";
|
||||
import EditUser from "./EditUser";
|
||||
import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
|
||||
import { Provider, useDispatch } from "react-redux";
|
||||
import { Provider, useDispatch, useSelector } from "react-redux";
|
||||
import { createStore } from "redux";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
|
||||
@@ -11,6 +11,7 @@ Enzyme.configure({ adapter: new Adapter() });
|
||||
jest.mock("react-redux", () => ({
|
||||
...jest.requireActual("react-redux"),
|
||||
useDispatch: jest.fn(),
|
||||
useSelector: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("EditUser Component: ", () => {
|
||||
@@ -27,7 +28,7 @@ describe("EditUser Component: ", () => {
|
||||
}
|
||||
deleteUser={callbackSpy}
|
||||
editUser={callbackSpy}
|
||||
refreshUserData={callbackSpy}
|
||||
updateUsers={callbackSpy}
|
||||
history={{ push: (a) => {} }}
|
||||
failRegexEvent={callbackSpy}
|
||||
noChangeEvent={callbackSpy}
|
||||
@@ -36,10 +37,17 @@ describe("EditUser Component: ", () => {
|
||||
</Provider>
|
||||
);
|
||||
|
||||
var mockAppState = () => ({
|
||||
limit: 3,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
useDispatch.mockImplementation((callback) => {
|
||||
return () => {};
|
||||
});
|
||||
useSelector.mockImplementation((callback) => {
|
||||
return callback(mockAppState());
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@@ -31,7 +31,7 @@ const GroupEdit = (props) => {
|
||||
removeFromGroup,
|
||||
deleteGroup,
|
||||
updateGroups,
|
||||
findUser,
|
||||
validateUser,
|
||||
history,
|
||||
location,
|
||||
} = props;
|
||||
@@ -41,9 +41,9 @@ const GroupEdit = (props) => {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
var { group_data, user_data, callback } = location.state;
|
||||
var { group_data, callback } = location.state;
|
||||
|
||||
if (!(group_data && user_data)) return <div></div>;
|
||||
if (!group_data) return <div></div>;
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
@@ -56,10 +56,7 @@ const GroupEdit = (props) => {
|
||||
</div>
|
||||
<GroupSelect
|
||||
users={group_data.users}
|
||||
validateUser={async (username) => {
|
||||
let user = await findUser(username);
|
||||
return user.status > 200 ? false : true;
|
||||
}}
|
||||
validateUser={validateUser}
|
||||
onChange={(selection) => {
|
||||
setSelected(selection);
|
||||
setChanged(true);
|
||||
@@ -139,7 +136,6 @@ GroupEdit.propTypes = {
|
||||
location: PropTypes.shape({
|
||||
state: PropTypes.shape({
|
||||
group_data: PropTypes.object,
|
||||
user_data: PropTypes.array,
|
||||
callback: PropTypes.func,
|
||||
}),
|
||||
}),
|
||||
|
@@ -5,6 +5,8 @@ import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
|
||||
import { Provider, useSelector } from "react-redux";
|
||||
import { createStore } from "redux";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
import ReactDOM from "react-dom";
|
||||
import { act } from "react-dom/test-utils";
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
|
||||
@@ -14,8 +16,9 @@ jest.mock("react-redux", () => ({
|
||||
}));
|
||||
|
||||
describe("GroupEdit Component: ", () => {
|
||||
var mockAsync = () =>
|
||||
jest.fn().mockImplementation(() => Promise.resolve({ key: "value" }));
|
||||
var mockAsync = () => jest.fn().mockImplementation(() => Promise.resolve());
|
||||
|
||||
var okPacket = new Promise((resolve) => resolve(true));
|
||||
|
||||
var groupEditJsx = (callbackSpy) => (
|
||||
<Provider store={createStore(() => {}, {})}>
|
||||
@@ -23,7 +26,6 @@ describe("GroupEdit Component: ", () => {
|
||||
<GroupEdit
|
||||
location={{
|
||||
state: {
|
||||
user_data: [{ name: "foo" }, { name: "bar" }],
|
||||
group_data: { users: ["foo"], name: "group" },
|
||||
callback: () => {},
|
||||
},
|
||||
@@ -32,16 +34,15 @@ describe("GroupEdit Component: ", () => {
|
||||
removeFromGroup={callbackSpy}
|
||||
deleteGroup={callbackSpy}
|
||||
history={{ push: (a) => callbackSpy }}
|
||||
refreshGroupsData={callbackSpy}
|
||||
updateGroups={callbackSpy}
|
||||
validateUser={jest.fn().mockImplementation(() => okPacket)}
|
||||
/>
|
||||
</HashRouter>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
var mockAppState = () => ({
|
||||
user_data: JSON.parse(
|
||||
'[{"kind":"user","name":"foo","admin":true,"groups":[],"server":"/user/foo/","pending":null,"created":"2020-12-07T18:46:27.112695Z","last_activity":"2020-12-07T21:00:33.336354Z","servers":{"":{"name":"","last_activity":"2020-12-07T20:58:02.437408Z","started":"2020-12-07T20:58:01.508266Z","pending":null,"ready":true,"state":{"pid":28085},"url":"/user/foo/","user_options":{},"progress_url":"/hub/api/users/foo/server/progress"}}},{"kind":"user","name":"bar","admin":false,"groups":[],"server":null,"pending":null,"created":"2020-12-07T18:46:27.115528Z","last_activity":"2020-12-07T20:43:51.013613Z","servers":{}}]'
|
||||
),
|
||||
limit: 3,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -54,24 +55,39 @@ describe("GroupEdit Component: ", () => {
|
||||
useSelector.mockClear();
|
||||
});
|
||||
|
||||
it("Adds a newly selected user to group on submit", () => {
|
||||
it("Adds user from input to user selectables on button click", async () => {
|
||||
let callbackSpy = mockAsync(),
|
||||
component = mount(groupEditJsx(callbackSpy)),
|
||||
unselected = component.find(".unselected"),
|
||||
input = component.find("#username-input"),
|
||||
validateUser = component.find("#validate-user"),
|
||||
submit = component.find("#submit");
|
||||
unselected.simulate("click");
|
||||
|
||||
input.simulate("change", { target: { value: "bar" } });
|
||||
validateUser.simulate("click");
|
||||
await act(() => okPacket);
|
||||
submit.simulate("click");
|
||||
expect(callbackSpy).toHaveBeenCalledWith(["bar"], "group");
|
||||
expect(callbackSpy).toHaveBeenNthCalledWith(1, ["bar"], "group");
|
||||
});
|
||||
|
||||
it("Removes a user from group on submit", () => {
|
||||
it("Removes a user recently added from input from the selectables list", () => {
|
||||
let callbackSpy = mockAsync(),
|
||||
component = mount(groupEditJsx(callbackSpy)),
|
||||
selected = component.find(".selected"),
|
||||
submit = component.find("#submit");
|
||||
selected.simulate("click");
|
||||
unsubmittedUser = component.find(".item.selected").last();
|
||||
unsubmittedUser.simulate("click");
|
||||
expect(component.find(".item").length).toBe(1);
|
||||
});
|
||||
|
||||
it("Grays out a user, already in the group, when unselected and calls deleteUser on submit", () => {
|
||||
let callbackSpy = mockAsync(),
|
||||
component = mount(groupEditJsx(callbackSpy)),
|
||||
groupUser = component.find(".item.selected").first();
|
||||
groupUser.simulate("click");
|
||||
expect(component.find(".item.unselected").length).toBe(1);
|
||||
expect(component.find(".item").length).toBe(1);
|
||||
// test deleteUser call
|
||||
let submit = component.find("#submit");
|
||||
submit.simulate("click");
|
||||
expect(callbackSpy).toHaveBeenCalledWith(["foo"], "group");
|
||||
expect(callbackSpy).toHaveBeenNthCalledWith(1, ["foo"], "group");
|
||||
});
|
||||
|
||||
it("Calls deleteGroup on button click", () => {
|
||||
@@ -80,6 +96,5 @@ describe("GroupEdit Component: ", () => {
|
||||
deleteGroup = component.find("#delete-group").first();
|
||||
deleteGroup.simulate("click");
|
||||
expect(callbackSpy).toHaveBeenNthCalledWith(1, "group");
|
||||
expect(callbackSpy).toHaveBeenNthCalledWith(2);
|
||||
});
|
||||
});
|
||||
|
@@ -23,6 +23,7 @@ const GroupSelect = (props) => {
|
||||
<div className="col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2 text-left">
|
||||
<div className="input-group">
|
||||
<input
|
||||
id="username-input"
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Add by username"
|
||||
@@ -33,10 +34,10 @@ const GroupSelect = (props) => {
|
||||
/>
|
||||
<span className="input-group-btn">
|
||||
<button
|
||||
id="validate-user"
|
||||
className="btn btn-default"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
// check user exists then
|
||||
validateUser(username).then((exists) => {
|
||||
if (exists && !selected.includes(username)) {
|
||||
let updated_selection = selected.concat([username]);
|
||||
|
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import Enzyme, { mount } from "enzyme";
|
||||
import Groups from "./Groups";
|
||||
import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
|
||||
import { Provider, useSelector } from "react-redux";
|
||||
import { Provider, useDispatch, useSelector } from "react-redux";
|
||||
import { createStore } from "redux";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
|
||||
@@ -15,10 +15,13 @@ jest.mock("react-redux", () => ({
|
||||
}));
|
||||
|
||||
describe("Groups Component: ", () => {
|
||||
var groupsJsx = () => (
|
||||
var mockAsync = () =>
|
||||
jest.fn().mockImplementation(() => Promise.resolve({ key: "value" }));
|
||||
|
||||
var groupsJsx = (callbackSpy) => (
|
||||
<Provider store={createStore(() => {}, {})}>
|
||||
<HashRouter>
|
||||
<Groups />
|
||||
<Groups location={{ search: "0" }} updateGroups={callbackSpy} />
|
||||
</HashRouter>
|
||||
</Provider>
|
||||
);
|
||||
@@ -36,6 +39,9 @@ describe("Groups Component: ", () => {
|
||||
useSelector.mockImplementation((callback) => {
|
||||
return callback(mockAppState());
|
||||
});
|
||||
useDispatch.mockImplementation((callback) => {
|
||||
return () => {};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -43,8 +49,9 @@ describe("Groups Component: ", () => {
|
||||
});
|
||||
|
||||
it("Renders groups_data prop into links", () => {
|
||||
let component = mount(groupsJsx()),
|
||||
links = component.find(".group-edit-link");
|
||||
let callbackSpy = mockAsync(),
|
||||
component = mount(groupsJsx(callbackSpy)),
|
||||
links = component.find("li");
|
||||
expect(links.length).toBe(2);
|
||||
});
|
||||
|
||||
|
@@ -1,56 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import "./multi-select.css";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const Multiselect = (props) => {
|
||||
var { onChange, options, value } = props;
|
||||
|
||||
var [selected, setSelected] = useState(value);
|
||||
|
||||
if (!options) return null;
|
||||
|
||||
return (
|
||||
<div className="multi-container">
|
||||
<div>
|
||||
{selected.map((e, i) => (
|
||||
<div
|
||||
key={"selected" + i}
|
||||
className="item selected"
|
||||
onClick={() => {
|
||||
let updated_selection = selected
|
||||
.slice(0, i)
|
||||
.concat(selected.slice(i + 1));
|
||||
onChange(updated_selection, options);
|
||||
setSelected(updated_selection);
|
||||
}}
|
||||
>
|
||||
{e}
|
||||
</div>
|
||||
))}
|
||||
{options.map((e, i) =>
|
||||
selected.includes(e) ? undefined : (
|
||||
<div
|
||||
key={"unselected" + i}
|
||||
className="item unselected"
|
||||
onClick={() => {
|
||||
let updated_selection = selected.concat([e]);
|
||||
onChange(updated_selection, options);
|
||||
setSelected(updated_selection);
|
||||
}}
|
||||
>
|
||||
{e}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Multiselect.propTypes = {
|
||||
value: PropTypes.array,
|
||||
onChange: PropTypes.func,
|
||||
options: PropTypes.array,
|
||||
};
|
||||
|
||||
export default Multiselect;
|
@@ -1,51 +0,0 @@
|
||||
import React from "react";
|
||||
import Enzyme, { shallow } from "enzyme";
|
||||
import Multiselect from "./Multiselect";
|
||||
import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
|
||||
describe("Multiselect Component: ", () => {
|
||||
var multiselectJsx = () => (
|
||||
<Multiselect
|
||||
options={["foo", "bar", "wombat"]}
|
||||
s
|
||||
value={["wombat"]}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
it("Renders with initial value selected", () => {
|
||||
let component = shallow(multiselectJsx()),
|
||||
selected = component.find(".item.selected").first();
|
||||
expect(selected.text()).toBe("wombat");
|
||||
});
|
||||
|
||||
it("Deselects a value when div.item.selected is clicked", () => {
|
||||
let component = shallow(multiselectJsx()),
|
||||
selected = component.find(".item.selected").first();
|
||||
selected.simulate("click");
|
||||
expect(component.find(".item.selected").length).toBe(0);
|
||||
});
|
||||
|
||||
it("Selects a an option when div.item.unselected is clicked", () => {
|
||||
let component = shallow(multiselectJsx()),
|
||||
unselected = component.find(".item.unselected").first();
|
||||
unselected.simulate("click");
|
||||
expect(component.find(".item.selected").length).toBe(2);
|
||||
});
|
||||
|
||||
it("Triggers callback on any sort of change", () => {
|
||||
let callbackSpy = jest.fn(),
|
||||
component = shallow(
|
||||
<Multiselect
|
||||
options={["foo", "bar", "wombat"]}
|
||||
value={["wombat"]}
|
||||
onChange={callbackSpy}
|
||||
/>
|
||||
),
|
||||
selected = component.find(".item.selected").first();
|
||||
selected.simulate("click");
|
||||
expect(callbackSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
@@ -1,40 +0,0 @@
|
||||
@import url(../../style/root.css);
|
||||
|
||||
.multi-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
padding: 5px;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
.multi-container div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.multi-container .item {
|
||||
padding: 3px;
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
transition: 30ms ease-in all;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
border: solid 1px #dfdfdf;
|
||||
}
|
||||
|
||||
.multi-container .item.unselected {
|
||||
background-color: #f7f7f7;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.multi-container .item.selected {
|
||||
background-color: orange;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.multi-container .item:hover {
|
||||
opacity: 0.7;
|
||||
}
|
@@ -36,8 +36,6 @@ const ServerDashboard = (props) => {
|
||||
limit = useSelector((state) => state.limit),
|
||||
page = parseInt(new URLSearchParams(props.location.search).get("page"));
|
||||
|
||||
console.log(user_page);
|
||||
|
||||
page = isNaN(page) ? 0 : page;
|
||||
var slice = [page * limit, limit];
|
||||
|
||||
|
@@ -33,6 +33,10 @@ const withAPI = withProps((props) => ({
|
||||
}),
|
||||
deleteUser: (username) => jhapiRequest("/users/" + username, "DELETE"),
|
||||
findUser: (username) => jhapiRequest("/users/" + username, "GET"),
|
||||
validateUser: (username) =>
|
||||
findUser(username)
|
||||
.then((data) => data.status)
|
||||
.then((data) => (data > 200 ? false : true)),
|
||||
failRegexEvent: () =>
|
||||
alert(
|
||||
"Cannot change username - either contains special characters or is too short."
|
||||
|
Reference in New Issue
Block a user