jsx: update and address eslint

add script to top-level package.json to run eslint in subdir
This commit is contained in:
Min RK
2025-03-26 12:02:04 +01:00
parent 742de1311e
commit cd79f17d90
10 changed files with 276 additions and 124 deletions

View File

@@ -1,7 +1,6 @@
import React, { act } from "react";
import "@testing-library/jest-dom";
import { render, screen, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Provider, useDispatch, useSelector } from "react-redux";
import { createStore } from "redux";
import { HashRouter } from "react-router";

View File

@@ -179,6 +179,7 @@ GroupEdit.propTypes = {
removeFromGroup: PropTypes.func,
deleteGroup: PropTypes.func,
updateGroups: PropTypes.func,
updateProp: PropTypes.func,
validateUser: PropTypes.func,
};

View File

@@ -3,6 +3,7 @@ import { useSelector, useDispatch } from "react-redux";
import { debounce } from "lodash";
import PropTypes from "prop-types";
import ErrorAlert from "../../util/error";
import { User, Server } from "../../util/jhapiUtil";
import {
Button,
@@ -209,6 +210,15 @@ const ServerDashboard = (props) => {
);
};
ServerButton.propTypes = {
server: Server,
user: User,
action: PropTypes.string,
name: PropTypes.string,
variant: PropTypes.string,
extraClass: PropTypes.string,
};
const StopServerButton = ({ server, user }) => {
if (!server.ready) {
return null;
@@ -222,6 +232,12 @@ const ServerDashboard = (props) => {
extraClass: "stop-button",
});
};
StopServerButton.propTypes = {
server: Server,
user: User,
};
const DeleteServerButton = ({ server, user }) => {
if (!server.name) {
// It's not possible to delete unnamed servers
@@ -240,6 +256,11 @@ const ServerDashboard = (props) => {
});
};
DeleteServerButton.propTypes = {
server: Server,
user: User,
};
const StartServerButton = ({ server, user }) => {
if (server.ready) {
return null;
@@ -254,6 +275,11 @@ const ServerDashboard = (props) => {
});
};
StartServerButton.propTypes = {
server: Server,
user: User,
};
const SpawnPageButton = ({ server, user }) => {
if (server.ready) {
return null;
@@ -271,6 +297,11 @@ const ServerDashboard = (props) => {
);
};
SpawnPageButton.propTypes = {
server: Server,
user: User,
};
const AccessServerButton = ({ server }) => {
if (!server.ready) {
return null;
@@ -283,6 +314,9 @@ const ServerDashboard = (props) => {
</a>
);
};
AccessServerButton.propTypes = {
server: Server,
};
const EditUserButton = ({ user }) => {
return (
@@ -303,10 +337,17 @@ const ServerDashboard = (props) => {
);
};
const ServerRowTable = ({ data }) => {
EditUserButton.propTypes = {
user: User,
};
const ServerRowTable = ({ data, exclude }) => {
const sortedData = Object.keys(data)
.sort()
.reduce(function (result, key) {
if (exclude && exclude.includes(key)) {
return result;
}
let value = data[key];
switch (key) {
case "last_activity":
@@ -346,88 +387,101 @@ const ServerDashboard = (props) => {
);
};
const serverRow = (user, server) => {
const { servers, ...userNoServers } = user;
ServerRowTable.propTypes = {
data: Server,
exclude: PropTypes.arrayOf(PropTypes.string),
};
const ServerRow = ({ user, server }) => {
const serverNameDash = server.name ? `-${server.name}` : "";
const userServerName = user.name + serverNameDash;
const open = collapseStates[userServerName] || false;
return [
<tr
key={`${userServerName}-row`}
data-testid={`user-row-${userServerName}`}
className="user-row"
>
<td data-testid="user-row-name">
<span>
<Button
onClick={() =>
setCollapseStates({
...collapseStates,
[userServerName]: !open,
})
}
aria-controls={`${userServerName}-collapse`}
aria-expanded={open}
data-testid={`${userServerName}-collapse-button`}
variant={open ? "secondary" : "primary"}
size="sm"
>
<span className="fa fa-caret-down"></span>
</Button>{" "}
</span>
<span data-testid={`user-name-div-${userServerName}`}>
{user.name}
</span>
</td>
<td data-testid="user-row-admin">{user.admin ? "admin" : ""}</td>
<td data-testid="user-row-server">
<p className="text-secondary">{server.name}</p>
</td>
<td data-testid="user-row-last-activity">
{server.last_activity ? timeSince(server.last_activity) : "Never"}
</td>
<td data-testid="user-row-server-activity" className="actions">
<StartServerButton server={server} user={user} />
<StopServerButton server={server} user={user} />
<DeleteServerButton server={server} user={user} />
<AccessServerButton server={server} />
<SpawnPageButton server={server} user={user} />
<EditUserButton user={user} />
</td>
</tr>,
<tr key={`${userServerName}-detail`}>
<td
colSpan={6}
style={{ padding: 0 }}
data-testid={`${userServerName}-td`}
return (
<Fragment key={`${userServerName}-row`}>
<tr
key={`${userServerName}-row`}
data-testid={`user-row-${userServerName}`}
className="user-row"
>
<Collapse in={open} data-testid={`${userServerName}-collapse`}>
<CardGroup
id={`${userServerName}-card-group`}
style={{ width: "100%", margin: "0 auto", float: "none" }}
>
<Card style={{ width: "100%", padding: 3, margin: "0 auto" }}>
<Card.Title>User</Card.Title>
<ServerRowTable data={userNoServers} />
</Card>
<Card style={{ width: "100%", padding: 3, margin: "0 auto" }}>
<Card.Title>Server</Card.Title>
<ServerRowTable data={server} />
</Card>
</CardGroup>
</Collapse>
</td>
</tr>,
];
<td data-testid="user-row-name">
<span>
<Button
onClick={() =>
setCollapseStates({
...collapseStates,
[userServerName]: !open,
})
}
aria-controls={`${userServerName}-collapse`}
aria-expanded={open}
data-testid={`${userServerName}-collapse-button`}
variant={open ? "secondary" : "primary"}
size="sm"
>
<span className="fa fa-caret-down"></span>
</Button>{" "}
</span>
<span data-testid={`user-name-div-${userServerName}`}>
{user.name}
</span>
</td>
<td data-testid="user-row-admin">{user.admin ? "admin" : ""}</td>
<td data-testid="user-row-server">
<p className="text-secondary">{server.name}</p>
</td>
<td data-testid="user-row-last-activity">
{server.last_activity ? timeSince(server.last_activity) : "Never"}
</td>
<td data-testid="user-row-server-activity" className="actions">
<StartServerButton server={server} user={user} />
<StopServerButton server={server} user={user} />
<DeleteServerButton server={server} user={user} />
<AccessServerButton server={server} />
<SpawnPageButton server={server} user={user} />
<EditUserButton user={user} />
</td>
</tr>
<tr key={`${userServerName}-detail`}>
<td
colSpan={6}
style={{ padding: 0 }}
data-testid={`${userServerName}-td`}
>
<Collapse in={open} data-testid={`${userServerName}-collapse`}>
<CardGroup
id={`${userServerName}-card-group`}
style={{ width: "100%", margin: "0 auto", float: "none" }}
>
<Card style={{ width: "100%", padding: 3, margin: "0 auto" }}>
<Card.Title>User</Card.Title>
<ServerRowTable data={user} exclude={["server", "servers"]} />
</Card>
<Card style={{ width: "100%", padding: 3, margin: "0 auto" }}>
<Card.Title>Server</Card.Title>
<ServerRowTable data={server} />
</Card>
</CardGroup>
</Collapse>
</td>
</tr>
</Fragment>
);
};
let servers = user_data.flatMap((user) => {
let userServers = Object.values({
ServerRow.propTypes = {
user: User,
server: Server,
};
const serverRows = user_data.flatMap((user) => {
const userServers = Object.values({
// eslint-disable-next-line react/prop-types
"": user.server || {},
// eslint-disable-next-line react/prop-types
...(user.servers || {}),
});
return userServers.map((server) => [user, server]);
return userServers.map((server) => ServerRow({ user, server }));
});
return (
@@ -583,7 +637,7 @@ const ServerDashboard = (props) => {
</Button>
</td>
</tr>
{servers.flatMap(([user, server]) => serverRow(user, server))}
{serverRows}
</tbody>
</table>
<PaginationFooter
@@ -607,7 +661,7 @@ const ServerDashboard = (props) => {
};
ServerDashboard.propTypes = {
user_data: PropTypes.array,
user_data: PropTypes.arrayOf(User),
updateUsers: PropTypes.func,
shutdownHub: PropTypes.func,
startServer: PropTypes.func,

View File

@@ -1,3 +1,5 @@
import PropTypes from "prop-types";
const jhdata = window.jhdata || {};
const base_url = jhdata.base_url || "/";
const xsrfToken = jhdata.xsrf_token;
@@ -17,3 +19,21 @@ export const jhapiRequest = (endpoint, method, data) => {
body: data ? JSON.stringify(data) : null,
});
};
// need to declare the subset of fields we use, at least
export const Server = PropTypes.shape({
name: PropTypes.string,
url: PropTypes.string,
active: PropTypes.boolean,
pending: PropTypes.string,
last_activity: PropTypes.string,
});
export const User = PropTypes.shape({
admin: PropTypes.boolean,
name: PropTypes.string,
last_activity: PropTypes.string,
url: PropTypes.string,
server: Server,
servers: PropTypes.objectOf(Server),
});