mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-08 02:24:08 +00:00
Combine API props, update tests for redux hooks
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -29,3 +29,4 @@ htmlcov
|
||||
pip-wheel-metadata
|
||||
docs/source/reference/metrics.rst
|
||||
oldest-requirements.txt
|
||||
jupyterhub-proxy.pid
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": ["plugin:react/recommended"],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
|
@@ -2,11 +2,11 @@ import React, { Component, useEffect } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import { createStore } from "redux";
|
||||
import { Button } from "react-bootstrap";
|
||||
import { compose } from "recompose";
|
||||
import { initialState, reducers } from "./Store";
|
||||
import { jhapiRequest } from "./util/jhapiUtil";
|
||||
import withAPI from "./util/withAPI";
|
||||
import { HashRouter, Switch, Route, Link } from "react-router-dom";
|
||||
import { createBrowserHistory } from "history";
|
||||
|
||||
import ServerDashboard from "./components/ServerDashboard/ServerDashboard";
|
||||
import Groups from "./components/Groups/Groups";
|
||||
@@ -37,12 +37,32 @@ const App = (props) => {
|
||||
<Provider store={store}>
|
||||
<HashRouter>
|
||||
<Switch>
|
||||
<Route exact path="/" component={ServerDashboard} />
|
||||
<Route exact path="/groups" component={Groups} />
|
||||
<Route exact path="/group-edit" component={GroupEdit} />
|
||||
<Route exact path="/create-group" component={CreateGroup} />
|
||||
<Route exact path="/add-users" component={AddUser} />
|
||||
<Route exact path="/edit-user" component={EditUser} />
|
||||
<Route
|
||||
exact
|
||||
path="/"
|
||||
component={compose(withAPI)(ServerDashboard)}
|
||||
/>
|
||||
<Route exact path="/groups" component={compose(withAPI)(Groups)} />
|
||||
<Route
|
||||
exact
|
||||
path="/group-edit"
|
||||
component={compose(withAPI)(GroupEdit)}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/create-group"
|
||||
component={compose(withAPI)(CreateGroup)}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/add-users"
|
||||
component={compose(withAPI)(AddUser)}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/edit-user"
|
||||
component={compose(withAPI)(EditUser)}
|
||||
/>
|
||||
</Switch>
|
||||
</HashRouter>
|
||||
</Provider>
|
||||
|
@@ -107,17 +107,4 @@ AddUser.propTypes = {
|
||||
}),
|
||||
};
|
||||
|
||||
const withUserAPI = withProps((props) => ({
|
||||
addUsers: (usernames, admin) =>
|
||||
jhapiRequest("/users", "POST", { usernames, admin }),
|
||||
failRegexEvent: () =>
|
||||
alert(
|
||||
"Removed " +
|
||||
JSON.stringify(removed_users) +
|
||||
" for either containing special characters or being too short."
|
||||
),
|
||||
refreshUserData: () =>
|
||||
jhapiRequest("/users", "GET").then((data) => data.json()),
|
||||
}));
|
||||
|
||||
export default compose(withUserAPI)(AddUser);
|
||||
export default AddUser;
|
||||
|
@@ -1,31 +1,53 @@
|
||||
import React from "react";
|
||||
import Enzyme, { shallow } from "enzyme";
|
||||
import AddUser from "./AddUser.pre";
|
||||
import Enzyme, { mount } from "enzyme";
|
||||
import AddUser from "./AddUser";
|
||||
import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
|
||||
import { Provider, useDispatch } from "react-redux";
|
||||
import { createStore } from "redux";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
|
||||
jest.mock("react-redux", () => ({
|
||||
...jest.requireActual("react-redux"),
|
||||
useDispatch: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("AddUser Component: ", () => {
|
||||
var mockAsync = () =>
|
||||
jest.fn().mockImplementation(() => Promise.resolve({ key: "value" }));
|
||||
|
||||
var addUserJsx = (callbackSpy) => (
|
||||
<Provider store={createStore(() => {}, {})}>
|
||||
<HashRouter>
|
||||
<AddUser
|
||||
addUsers={callbackSpy}
|
||||
failRegexEvent={callbackSpy}
|
||||
refreshUserData={callbackSpy}
|
||||
history={{ push: (a) => {} }}
|
||||
/>
|
||||
</HashRouter>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
useDispatch.mockImplementation((callback) => {
|
||||
return () => {};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useDispatch.mockClear();
|
||||
});
|
||||
|
||||
it("Renders", () => {
|
||||
let component = shallow(addUserJsx(mockAsync()));
|
||||
let component = mount(addUserJsx(mockAsync()));
|
||||
expect(component.find(".container").length).toBe(1);
|
||||
});
|
||||
|
||||
it("Removes users when they fail Regex", () => {
|
||||
let callbackSpy = mockAsync(),
|
||||
component = shallow(addUserJsx(callbackSpy)),
|
||||
component = mount(addUserJsx(callbackSpy)),
|
||||
textarea = component.find("textarea").first();
|
||||
textarea.simulate("blur", { target: { value: "foo\nbar\n!!*&*" } });
|
||||
let submit = component.find("#submit");
|
||||
@@ -35,7 +57,7 @@ describe("AddUser Component: ", () => {
|
||||
|
||||
it("Correctly submits admin", () => {
|
||||
let callbackSpy = mockAsync(),
|
||||
component = shallow(addUserJsx(callbackSpy)),
|
||||
component = mount(addUserJsx(callbackSpy)),
|
||||
input = component.find("input").first();
|
||||
input.simulate("change", { target: { checked: true } });
|
||||
let submit = component.find("#submit");
|
||||
|
@@ -81,16 +81,4 @@ CreateGroup.propTypes = {
|
||||
}),
|
||||
};
|
||||
|
||||
const withGroupsAPI = withProps((props) => ({
|
||||
createGroup: (groupName) => jhapiRequest("/groups/" + groupName, "POST"),
|
||||
failRegexEvent: () =>
|
||||
alert(
|
||||
"Removed " +
|
||||
JSON.stringify(removed_users) +
|
||||
" for either containing special characters or being too short."
|
||||
),
|
||||
refreshGroupsData: () =>
|
||||
jhapiRequest("/groups", "GET").then((data) => data.json()),
|
||||
}));
|
||||
|
||||
export default compose(withGroupsAPI)(CreateGroup);
|
||||
export default CreateGroup;
|
||||
|
@@ -1,30 +1,52 @@
|
||||
import React from "react";
|
||||
import Enzyme, { mount, shallow } from "enzyme";
|
||||
import CreateGroup from "./CreateGroup.pre";
|
||||
import Enzyme, { mount } from "enzyme";
|
||||
import CreateGroup from "./CreateGroup";
|
||||
import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
|
||||
import { Provider, useDispatch } from "react-redux";
|
||||
import { createStore } from "redux";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
|
||||
jest.mock("react-redux", () => ({
|
||||
...jest.requireActual("react-redux"),
|
||||
useDispatch: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("CreateGroup Component: ", () => {
|
||||
var mockAsync = () =>
|
||||
jest.fn().mockImplementation(() => Promise.resolve({ key: "value" }));
|
||||
|
||||
var createGroupJsx = (callbackSpy) => (
|
||||
<Provider store={createStore(() => {}, {})}>
|
||||
<HashRouter>
|
||||
<CreateGroup
|
||||
createGroup={callbackSpy}
|
||||
refreshGroupsData={callbackSpy}
|
||||
history={{ push: () => {} }}
|
||||
/>
|
||||
</HashRouter>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
useDispatch.mockImplementation((callback) => {
|
||||
return () => {};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useDispatch.mockClear();
|
||||
});
|
||||
|
||||
it("Renders", () => {
|
||||
let component = shallow(createGroupJsx());
|
||||
let component = mount(createGroupJsx());
|
||||
expect(component.find(".container").length).toBe(1);
|
||||
});
|
||||
|
||||
it("Calls createGroup and refreshGroupsData on submit", () => {
|
||||
let callbackSpy = mockAsync(),
|
||||
component = shallow(createGroupJsx(callbackSpy)),
|
||||
component = mount(createGroupJsx(callbackSpy)),
|
||||
input = component.find("input").first(),
|
||||
submit = component.find("#submit").first();
|
||||
input.simulate("change", { target: { value: "" } });
|
||||
|
@@ -1,12 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { compose, withProps } from "recompose";
|
||||
import { useDispatch } from "react-redux";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { jhapiRequest } from "../../util/jhapiUtil";
|
||||
|
||||
const EditUser = (props) => {
|
||||
var dispatch = useDispatch();
|
||||
|
||||
@@ -160,22 +156,4 @@ EditUser.propTypes = {
|
||||
refreshUserData: PropTypes.func,
|
||||
};
|
||||
|
||||
const withUserAPI = withProps((props) => ({
|
||||
editUser: (username, updated_username, admin) =>
|
||||
jhapiRequest("/users/" + username, "PATCH", {
|
||||
name: updated_username,
|
||||
admin,
|
||||
}),
|
||||
deleteUser: (username) => jhapiRequest("/users/" + username, "DELETE"),
|
||||
failRegexEvent: () =>
|
||||
alert(
|
||||
"Cannot change username - either contains special characters or is too short."
|
||||
),
|
||||
noChangeEvent: () => {
|
||||
returns;
|
||||
},
|
||||
refreshUserData: () =>
|
||||
jhapiRequest("/users", "GET").then((data) => data.json()),
|
||||
}));
|
||||
|
||||
export default compose(withUserAPI)(EditUser);
|
||||
export default EditUser;
|
||||
|
@@ -1,30 +1,54 @@
|
||||
import React from "react";
|
||||
import Enzyme, { shallow } from "enzyme";
|
||||
import EditUser from "./EditUser.pre";
|
||||
import Enzyme, { mount } from "enzyme";
|
||||
import EditUser from "./EditUser";
|
||||
import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
|
||||
import { Provider, useDispatch } from "react-redux";
|
||||
import { createStore } from "redux";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
|
||||
jest.mock("react-redux", () => ({
|
||||
...jest.requireActual("react-redux"),
|
||||
useDispatch: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("EditUser Component: ", () => {
|
||||
var mockAsync = () =>
|
||||
jest.fn().mockImplementation(() => Promise.resolve({ key: "value" }));
|
||||
var mockSync = () => jest.fn();
|
||||
|
||||
var editUserJsx = (callbackSpy) => (
|
||||
var editUserJsx = (callbackSpy, empty) => (
|
||||
<Provider store={createStore(() => {}, {})}>
|
||||
<HashRouter>
|
||||
<EditUser
|
||||
location={{ state: { username: "foo", has_admin: false } }}
|
||||
location={
|
||||
empty ? {} : { state: { username: "foo", has_admin: false } }
|
||||
}
|
||||
deleteUser={callbackSpy}
|
||||
editUser={callbackSpy}
|
||||
refreshUserData={mockSync()}
|
||||
refreshUserData={callbackSpy}
|
||||
history={{ push: (a) => {} }}
|
||||
failRegexEvent={callbackSpy}
|
||||
noChangeEvent={callbackSpy}
|
||||
/>
|
||||
</HashRouter>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
useDispatch.mockImplementation((callback) => {
|
||||
return () => {};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useDispatch.mockClear();
|
||||
});
|
||||
|
||||
it("Calls the delete user function when the button is pressed", () => {
|
||||
let callbackSpy = mockAsync(),
|
||||
component = shallow(editUserJsx(callbackSpy)),
|
||||
component = mount(editUserJsx(callbackSpy)),
|
||||
deleteUser = component.find("#delete-user");
|
||||
deleteUser.simulate("click");
|
||||
expect(callbackSpy).toHaveBeenCalled();
|
||||
@@ -32,9 +56,15 @@ describe("EditUser Component: ", () => {
|
||||
|
||||
it("Submits the edits when the button is pressed", () => {
|
||||
let callbackSpy = mockSync(),
|
||||
component = shallow(editUserJsx(callbackSpy)),
|
||||
component = mount(editUserJsx(callbackSpy)),
|
||||
submit = component.find("#submit");
|
||||
submit.simulate("click");
|
||||
expect(callbackSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Doesn't render when no data is provided", () => {
|
||||
let callbackSpy = mockSync(),
|
||||
component = mount(editUserJsx(callbackSpy, true));
|
||||
expect(component.find(".container").length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
@@ -137,14 +137,4 @@ GroupEdit.propTypes = {
|
||||
refreshGroupsData: PropTypes.func,
|
||||
};
|
||||
|
||||
const withGroupsAPI = withProps((props) => ({
|
||||
addToGroup: (users, groupname) =>
|
||||
jhapiRequest("/groups/" + groupname + "/users", "POST", { users }),
|
||||
removeFromGroup: (users, groupname) =>
|
||||
jhapiRequest("/groups/" + groupname + "/users", "DELETE", { users }),
|
||||
deleteGroup: (name) => jhapiRequest("/groups/" + name, "DELETE"),
|
||||
refreshGroupsData: () =>
|
||||
jhapiRequest("/groups", "GET").then((data) => data.json()),
|
||||
}));
|
||||
|
||||
export default compose(withGroupsAPI)(GroupEdit);
|
||||
export default GroupEdit;
|
||||
|
@@ -1,16 +1,25 @@
|
||||
import React from "react";
|
||||
import Enzyme, { mount, shallow } from "enzyme";
|
||||
import GroupEdit from "./GroupEdit.pre";
|
||||
import GroupEdit from "./GroupEdit";
|
||||
import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
|
||||
import { Provider, useSelector } from "react-redux";
|
||||
import { createStore } from "redux";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
|
||||
jest.mock("react-redux", () => ({
|
||||
...jest.requireActual("react-redux"),
|
||||
useSelector: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("GroupEdit Component: ", () => {
|
||||
var mockAsync = () =>
|
||||
jest.fn().mockImplementation(() => Promise.resolve({ key: "value" }));
|
||||
|
||||
var groupEditJsx = (callbackSpy) => (
|
||||
<Provider store={createStore(() => {}, {})}>
|
||||
<HashRouter>
|
||||
<GroupEdit
|
||||
location={{
|
||||
state: {
|
||||
@@ -23,17 +32,31 @@ describe("GroupEdit Component: ", () => {
|
||||
removeFromGroup={callbackSpy}
|
||||
deleteGroup={callbackSpy}
|
||||
history={{ push: (a) => callbackSpy }}
|
||||
refreshGroupsData={() => {}}
|
||||
refreshGroupsData={callbackSpy}
|
||||
/>
|
||||
</HashRouter>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
var deepGroupEditJsx = (callbackSpy) => (
|
||||
<HashRouter>{groupEditJsx(callbackSpy)}</HashRouter>
|
||||
);
|
||||
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":{}}]'
|
||||
),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
useSelector.mockImplementation((callback) => {
|
||||
return callback(mockAppState());
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useSelector.mockClear();
|
||||
});
|
||||
|
||||
it("Adds a newly selected user to group on submit", () => {
|
||||
let callbackSpy = mockAsync(),
|
||||
component = mount(deepGroupEditJsx(callbackSpy)),
|
||||
component = mount(groupEditJsx(callbackSpy)),
|
||||
unselected = component.find(".unselected"),
|
||||
submit = component.find("#submit");
|
||||
unselected.simulate("click");
|
||||
@@ -43,7 +66,7 @@ describe("GroupEdit Component: ", () => {
|
||||
|
||||
it("Removes a user from group on submit", () => {
|
||||
let callbackSpy = mockAsync(),
|
||||
component = mount(deepGroupEditJsx(callbackSpy)),
|
||||
component = mount(groupEditJsx(callbackSpy)),
|
||||
selected = component.find(".selected"),
|
||||
submit = component.find("#submit");
|
||||
selected.simulate("click");
|
||||
@@ -53,9 +76,10 @@ describe("GroupEdit Component: ", () => {
|
||||
|
||||
it("Calls deleteGroup on button click", () => {
|
||||
let callbackSpy = mockAsync(),
|
||||
component = shallow(groupEditJsx(callbackSpy)),
|
||||
component = mount(groupEditJsx(callbackSpy)),
|
||||
deleteGroup = component.find("#delete-group").first();
|
||||
deleteGroup.simulate("click");
|
||||
expect(callbackSpy).toHaveBeenCalled();
|
||||
expect(callbackSpy).toHaveBeenNthCalledWith(1, "group");
|
||||
expect(callbackSpy).toHaveBeenNthCalledWith(2);
|
||||
});
|
||||
});
|
||||
|
@@ -102,21 +102,4 @@ Groups.propTypes = {
|
||||
}),
|
||||
};
|
||||
|
||||
const withGroupsAPI = withProps((props) => ({
|
||||
refreshGroupsData: () =>
|
||||
jhapiRequest("/groups", "GET").then((data) => data.json()),
|
||||
refreshUserData: () =>
|
||||
jhapiRequest("/users", "GET").then((data) => data.json()),
|
||||
addUsersToGroup: (name, new_users) =>
|
||||
jhapiRequest("/groups/" + name + "/users", "POST", {
|
||||
body: { users: new_users },
|
||||
json: true,
|
||||
}),
|
||||
removeUsersFromGroup: (name, removed_users) =>
|
||||
jhapiRequest("/groups/" + name + "/users", "DELETE", {
|
||||
body: { users: removed_users },
|
||||
json: true,
|
||||
}),
|
||||
}));
|
||||
|
||||
export default compose(withGroupsAPI)(Groups);
|
||||
export default Groups;
|
||||
|
@@ -1,30 +1,58 @@
|
||||
import React from "react";
|
||||
import Enzyme, { shallow } from "enzyme";
|
||||
import Groups from "./Groups.pre";
|
||||
import Enzyme, { mount } from "enzyme";
|
||||
import Groups from "./Groups";
|
||||
import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
|
||||
import { Provider, useSelector } from "react-redux";
|
||||
import { createStore } from "redux";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
|
||||
jest.mock("react-redux", () => ({
|
||||
...jest.requireActual("react-redux"),
|
||||
useSelector: jest.fn(),
|
||||
useDispatch: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("Groups Component: ", () => {
|
||||
var groupsJsx = () => (
|
||||
<Groups
|
||||
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":{}}]'
|
||||
)}
|
||||
groups_data={JSON.parse(
|
||||
'[{"kind":"group","name":"testgroup","users":[]}, {"kind":"group","name":"testgroup2","users":["foo", "bar"]}]'
|
||||
)}
|
||||
/>
|
||||
<Provider store={createStore(() => {}, {})}>
|
||||
<HashRouter>
|
||||
<Groups />
|
||||
</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":{}}]'
|
||||
),
|
||||
groups_data: JSON.parse(
|
||||
'[{"kind":"group","name":"testgroup","users":[]}, {"kind":"group","name":"testgroup2","users":["foo", "bar"]}]'
|
||||
),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
useSelector.mockImplementation((callback) => {
|
||||
return callback(mockAppState());
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useSelector.mockClear();
|
||||
});
|
||||
|
||||
it("Renders groups_data prop into links", () => {
|
||||
let component = shallow(groupsJsx()),
|
||||
let component = mount(groupsJsx()),
|
||||
links = component.find(".group-edit-link");
|
||||
expect(links.length).toBe(2);
|
||||
});
|
||||
|
||||
it("Renders nothing if required data is not available", () => {
|
||||
let component = shallow(<Groups />);
|
||||
useSelector.mockImplementation((callback) => {
|
||||
return callback({});
|
||||
});
|
||||
let component = mount(groupsJsx());
|
||||
expect(component.html()).toBe("<div></div>");
|
||||
});
|
||||
});
|
||||
|
@@ -155,7 +155,7 @@ const ServerDashboard = (props) => {
|
||||
{/* Shutdown Jupyterhub */}
|
||||
<Button
|
||||
variant="danger"
|
||||
className="shutdown-button"
|
||||
id="shutdown-button"
|
||||
onClick={shutdownHub}
|
||||
>
|
||||
Shutdown Hub
|
||||
@@ -288,15 +288,4 @@ SortHandler.propTypes = {
|
||||
callback: PropTypes.func,
|
||||
};
|
||||
|
||||
const withHubActions = withProps((props) => ({
|
||||
updateUsers: (cb) => jhapiRequest("/users", "GET"),
|
||||
shutdownHub: () => jhapiRequest("/shutdown", "POST"),
|
||||
startServer: (name) => jhapiRequest("/users/" + name + "/server", "POST"),
|
||||
stopServer: (name) => jhapiRequest("/users/" + name + "/server", "DELETE"),
|
||||
startAll: (names) =>
|
||||
names.map((e) => jhapiRequest("/users/" + e + "/server", "POST")),
|
||||
stopAll: (names) =>
|
||||
names.map((e) => jhapiRequest("/users/" + e + "/server", "DELETE")),
|
||||
}));
|
||||
|
||||
export default compose(withHubActions)(ServerDashboard);
|
||||
export default ServerDashboard;
|
||||
|
@@ -1,44 +1,34 @@
|
||||
import React from "react";
|
||||
import Enzyme, { shallow, mount } from "enzyme";
|
||||
import ServerDashboard from "./ServerDashboard.pre";
|
||||
import ServerDashboard from "./ServerDashboard";
|
||||
import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
|
||||
import { HashRouter, Switch } from "react-router-dom";
|
||||
import { Provider, useSelector } from "react-redux";
|
||||
import { createStore } from "redux";
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
|
||||
jest.mock("react-redux", () => ({
|
||||
...jest.requireActual("react-redux"),
|
||||
useSelector: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("ServerDashboard Component: ", () => {
|
||||
var serverDashboardJsx = (callbackSpy) => (
|
||||
<ServerDashboard
|
||||
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":{}}]'
|
||||
)}
|
||||
updateUsers={callbackSpy}
|
||||
shutdownHub={callbackSpy}
|
||||
startServer={callbackSpy}
|
||||
stopServer={callbackSpy}
|
||||
startAll={callbackSpy}
|
||||
stopAll={callbackSpy}
|
||||
dispatch={callbackSpy}
|
||||
/>
|
||||
);
|
||||
|
||||
var deepServerDashboardJsx = (callbackSpy) => (
|
||||
<Provider store={createStore(() => {}, {})}>
|
||||
<HashRouter>
|
||||
<Switch>
|
||||
<ServerDashboard
|
||||
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":{}}]'
|
||||
)}
|
||||
updateUsers={callbackSpy}
|
||||
shutdownHub={callbackSpy}
|
||||
startServer={callbackSpy}
|
||||
stopServer={callbackSpy}
|
||||
startAll={callbackSpy}
|
||||
stopAll={callbackSpy}
|
||||
dispatch={callbackSpy}
|
||||
/>
|
||||
</Switch>
|
||||
</HashRouter>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
var mockAsync = () =>
|
||||
@@ -48,14 +38,30 @@ describe("ServerDashboard Component: ", () => {
|
||||
Promise.resolve({ json: () => Promise.resolve({ k: "v" }) })
|
||||
);
|
||||
|
||||
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":{}}]'
|
||||
),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
useSelector.mockImplementation((callback) => {
|
||||
return callback(mockAppState());
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useSelector.mockClear();
|
||||
});
|
||||
|
||||
it("Renders users from props.user_data into table", () => {
|
||||
let component = shallow(serverDashboardJsx(jest.fn())),
|
||||
let component = mount(serverDashboardJsx(jest.fn())),
|
||||
userRows = component.find(".user-row");
|
||||
expect(userRows.length).toBe(2);
|
||||
});
|
||||
|
||||
it("Renders correctly the status of a single-user server", () => {
|
||||
let component = shallow(serverDashboardJsx(jest.fn())),
|
||||
let component = mount(serverDashboardJsx(jest.fn())),
|
||||
userRows = component.find(".user-row");
|
||||
// Renders .stop-button when server is started
|
||||
// Should be 1 since user foo is started
|
||||
@@ -67,7 +73,7 @@ describe("ServerDashboard Component: ", () => {
|
||||
|
||||
it("Invokes the startServer event on button click", () => {
|
||||
let callbackSpy = mockAsync(),
|
||||
component = shallow(serverDashboardJsx(callbackSpy)),
|
||||
component = mount(serverDashboardJsx(callbackSpy)),
|
||||
startBtn = component.find(".start-button");
|
||||
startBtn.simulate("click");
|
||||
expect(callbackSpy).toHaveBeenCalled();
|
||||
@@ -75,7 +81,7 @@ describe("ServerDashboard Component: ", () => {
|
||||
|
||||
it("Invokes the stopServer event on button click", () => {
|
||||
let callbackSpy = mockAsync(),
|
||||
component = shallow(serverDashboardJsx(callbackSpy)),
|
||||
component = mount(serverDashboardJsx(callbackSpy)),
|
||||
stopBtn = component.find(".stop-button");
|
||||
stopBtn.simulate("click");
|
||||
expect(callbackSpy).toHaveBeenCalled();
|
||||
@@ -83,14 +89,14 @@ describe("ServerDashboard Component: ", () => {
|
||||
|
||||
it("Invokes the shutdownHub event on button click", () => {
|
||||
let callbackSpy = mockAsync(),
|
||||
component = shallow(serverDashboardJsx(callbackSpy)),
|
||||
shutdownBtn = component.find(".shutdown-button");
|
||||
component = mount(serverDashboardJsx(callbackSpy)),
|
||||
shutdownBtn = component.find("#shutdown-button").first();
|
||||
shutdownBtn.simulate("click");
|
||||
expect(callbackSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Sorts according to username", () => {
|
||||
let component = mount(deepServerDashboardJsx(jest.fn())).find(
|
||||
let component = mount(serverDashboardJsx(jest.fn())).find(
|
||||
"ServerDashboard"
|
||||
),
|
||||
handler = component.find("SortHandler").first();
|
||||
@@ -103,7 +109,7 @@ describe("ServerDashboard Component: ", () => {
|
||||
});
|
||||
|
||||
it("Sorts according to admin", () => {
|
||||
let component = mount(deepServerDashboardJsx(jest.fn())).find(
|
||||
let component = mount(serverDashboardJsx(jest.fn())).find(
|
||||
"ServerDashboard"
|
||||
),
|
||||
handler = component.find("SortHandler").at(1);
|
||||
@@ -116,7 +122,7 @@ describe("ServerDashboard Component: ", () => {
|
||||
});
|
||||
|
||||
it("Sorts according to last activity", () => {
|
||||
let component = mount(deepServerDashboardJsx(jest.fn())).find(
|
||||
let component = mount(serverDashboardJsx(jest.fn())).find(
|
||||
"ServerDashboard"
|
||||
),
|
||||
handler = component.find("SortHandler").at(2);
|
||||
@@ -131,7 +137,7 @@ describe("ServerDashboard Component: ", () => {
|
||||
});
|
||||
|
||||
it("Sorts according to server status (running/not running)", () => {
|
||||
let component = mount(deepServerDashboardJsx(jest.fn())).find(
|
||||
let component = mount(serverDashboardJsx(jest.fn())).find(
|
||||
"ServerDashboard"
|
||||
),
|
||||
handler = component.find("SortHandler").at(3);
|
||||
@@ -146,7 +152,10 @@ describe("ServerDashboard Component: ", () => {
|
||||
});
|
||||
|
||||
it("Renders nothing if required data is not available", () => {
|
||||
let component = shallow(<ServerDashboard />);
|
||||
useSelector.mockImplementation((callback) => {
|
||||
return callback({});
|
||||
});
|
||||
let component = mount(serverDashboardJsx(jest.fn()));
|
||||
expect(component.html()).toBe("<div></div>");
|
||||
});
|
||||
});
|
||||
|
40
jsx/src/util/withAPI.js
Normal file
40
jsx/src/util/withAPI.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { withProps } from "recompose";
|
||||
import { jhapiRequest } from "./jhapiUtil";
|
||||
|
||||
const withAPI = withProps((props) => ({
|
||||
updateUsers: (cb) => jhapiRequest("/users", "GET"),
|
||||
shutdownHub: () => jhapiRequest("/shutdown", "POST"),
|
||||
startServer: (name) => jhapiRequest("/users/" + name + "/server", "POST"),
|
||||
stopServer: (name) => jhapiRequest("/users/" + name + "/server", "DELETE"),
|
||||
startAll: (names) =>
|
||||
names.map((e) => jhapiRequest("/users/" + e + "/server", "POST")),
|
||||
stopAll: (names) =>
|
||||
names.map((e) => jhapiRequest("/users/" + e + "/server", "DELETE")),
|
||||
addToGroup: (users, groupname) =>
|
||||
jhapiRequest("/groups/" + groupname + "/users", "POST", { users }),
|
||||
removeFromGroup: (users, groupname) =>
|
||||
jhapiRequest("/groups/" + groupname + "/users", "DELETE", { users }),
|
||||
createGroup: (groupName) => jhapiRequest("/groups/" + groupName, "POST"),
|
||||
deleteGroup: (name) => jhapiRequest("/groups/" + name, "DELETE"),
|
||||
addUsers: (usernames, admin) =>
|
||||
jhapiRequest("/users", "POST", { usernames, admin }),
|
||||
editUser: (username, updated_username, admin) =>
|
||||
jhapiRequest("/users/" + username, "PATCH", {
|
||||
name: updated_username,
|
||||
admin,
|
||||
}),
|
||||
deleteUser: (username) => jhapiRequest("/users/" + username, "DELETE"),
|
||||
failRegexEvent: () =>
|
||||
alert(
|
||||
"Cannot change username - either contains special characters or is too short."
|
||||
),
|
||||
noChangeEvent: () => {
|
||||
returns;
|
||||
},
|
||||
refreshGroupsData: () =>
|
||||
jhapiRequest("/groups", "GET").then((data) => data.json()),
|
||||
refreshUserData: () =>
|
||||
jhapiRequest("/users", "GET").then((data) => data.json()),
|
||||
}));
|
||||
|
||||
export default withAPI;
|
@@ -1 +0,0 @@
|
||||
38441
|
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user