Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added school edit pages, utils, tests, stories #36

Merged
merged 4 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import CoursesEditPage from "main/pages/CoursesEditPage";
import AdminUsersPage from "main/pages/AdminUsersPage";
import AdminJobsPage from "main/pages/AdminJobsPage";
import SchoolIndexPage from "main/pages/SchoolIndexPage";
import SchoolEditPage from "main/pages/SchoolEditPage";

import CoursesCreatePage from "main/pages/CoursesCreatePage";
import CourseIndexPage from "main/pages/CourseIndexPage";
Expand All @@ -25,6 +26,7 @@ function App() {
const adminRoutes = hasRole(currentUser, "ROLE_ADMIN") ? (
<>
<Route path="/admin/schools" element={<SchoolIndexPage />} />
<Route path="/admin/schools/edit/:abbrev" element={<SchoolEditPage />} />
<Route path="/admin/users" element={<AdminUsersPage />} />
<Route path="/admin/jobs" element={<AdminJobsPage />} />
</>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/main/components/School/SchoolTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import React from "react";
const navigate = useNavigate();

const editCallback = (cell) => {
navigate(`/schools/edit/${cell.row.values.abbrev}`);
navigate(`/admin/schools/edit/${cell.row.values.abbrev}`);
};

// Stryker disable all : hard to test for query caching
Expand Down
70 changes: 70 additions & 0 deletions frontend/src/main/pages/SchoolEditPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import BasicLayout from "main/layouts/BasicLayout/BasicLayout";
import { useParams } from "react-router-dom";
import SchoolForm from "main/components/School/SchoolForm";
import { Navigate } from 'react-router-dom'
import { useBackend, useBackendMutation } from "main/utils/useBackend";
import { toast } from "react-toastify";

export default function SchoolEditPage({storybook=false}) {
let { abbrev } = useParams();

const { data: school, _error, _status } =
useBackend(
// Stryker disable next-line all : don't test internal caching of React Query
[`/api/schools?abbrev=${abbrev}`],
{ // Stryker disable next-line all : GET is the default, so changing this to "" doesn't introduce a bug
method: "GET",
url: `/api/schools`,
params: {
abbrev
}
}
);


const objectToAxiosPutParams = (school) => ({
url: "/api/schools/update",
method: "PUT",
params: {
abbrev: school.abbrev,
},
data: {
name: school.name,
termRegex: school.termRegex,
termDescription: school.termDescription,
termError: school.termError
},
});

const onSuccess = (school) => {
toast(`School Updated - abbrev: ${school.abbrev} name: ${school.name}`);
}

const mutation = useBackendMutation(
objectToAxiosPutParams,
{ onSuccess },
// Stryker disable next-line all : hard to set up test for caching
[`/api/schools/update?abbrev=${abbrev}`]
);

const { isSuccess } = mutation

const onSubmit = async (data) => {
mutation.mutate(data);
}

if (isSuccess && !storybook) {
return <Navigate to="/admin/schools" />
}

return (
<BasicLayout>
<div className="pt-2">
<h1>Edit School</h1>
{
school && <SchoolForm initialContents={school} submitAction={onSubmit} buttonLabel="Update" />
}
</div>
</BasicLayout>
)
}
2 changes: 1 addition & 1 deletion frontend/src/main/pages/SchoolIndexPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function SchoolIndexPage() {
return (
<Button
variant="primary"
href="/schools/create"
href="/admin/schools/create"
style={{ float: "right" }}
>
Create School
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/main/utils/schoolsUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { toast } from "react-toastify";

export function onDeleteSuccess(message) {
console.log(message);
toast(message);
}

export function cellToAxiosParamsDelete(cell) {
return {
url: "/api/schools",
method: "DELETE",
params: {
abbrev: cell.row.values.abbrev
}
}
}

37 changes: 37 additions & 0 deletions frontend/src/stories/pages/SchoolEditPage.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import { apiCurrentUserFixtures } from "fixtures/currentUserFixtures";
import { systemInfoFixtures } from "fixtures/systemInfoFixtures";
import { schoolsFixtures } from 'fixtures/schoolsFixtures';
import { rest } from "msw";

import SchoolEditPage from 'main/pages/SchoolEditPage';

export default {
title: 'pages/SchoolEditPage',
component: SchoolEditPage
};

const Template = () => <SchoolEditPage storybook={true}/>;

export const Default = Template.bind({});
Default.parameters = {
msw: [
rest.get('/api/currentUser', (_req, res, ctx) => {
return res( ctx.json(apiCurrentUserFixtures.adminUser));
}),
rest.get('/api/systemInfo', (_req, res, ctx) => {
return res(ctx.json(systemInfoFixtures.showingNeither));
}),
rest.get('/api/schools', (_req, res, ctx) => {
return res(ctx.json(schoolsFixtures.threeSchools[0]));
}),
rest.put('/api/schools', async (req, res, ctx) => {
var reqBody = await req.text();
window.alert("PUT: " + req.url + " and body: " + reqBody);
return res(ctx.status(200),ctx.json({}));
}),
],
}



2 changes: 1 addition & 1 deletion frontend/src/tests/components/School/SchoolTable.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ describe("UserTable tests", () => {

fireEvent.click(editButton);

await waitFor(() => expect(mockedNavigate).toHaveBeenCalledWith('/schools/edit/ucsb'));
await waitFor(() => expect(mockedNavigate).toHaveBeenCalledWith('/admin/schools/edit/ucsb'));

});

Expand Down
177 changes: 177 additions & 0 deletions frontend/src/tests/pages/School/SchoolEditPage.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { fireEvent, render, waitFor, screen } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "react-query";
import { MemoryRouter } from "react-router-dom";
import SchoolEditPage from "main/pages/SchoolEditPage";

import { apiCurrentUserFixtures } from "fixtures/currentUserFixtures";
import { systemInfoFixtures } from "fixtures/systemInfoFixtures";
import axios from "axios";
import AxiosMockAdapter from "axios-mock-adapter";
import mockConsole from "jest-mock-console";

const mockToast = jest.fn();
jest.mock('react-toastify', () => {
const originalModule = jest.requireActual('react-toastify');
return {
__esModule: true,
...originalModule,
toast: (x) => mockToast(x)
};
});

const mockNavigate = jest.fn();
jest.mock('react-router-dom', () => {
const originalModule = jest.requireActual('react-router-dom');
return {
__esModule: true,
...originalModule,
useParams: () => ({
abbrev: "ucsb"
}),
Navigate: (x) => { mockNavigate(x); return null; }
};
});


describe("SchoolEditPage tests", () => {

describe("when the backend doesn't return data", () => {

const axiosMock = new AxiosMockAdapter(axios);

beforeEach(() => {
axiosMock.reset();
axiosMock.resetHistory();
axiosMock.onGet("/api/currentUser").reply(200, apiCurrentUserFixtures.userOnly);
axiosMock.onGet("/api/systemInfo").reply(200, systemInfoFixtures.showingNeither);
axiosMock.onGet("/api/schools", { params: { abbrev: "ucsb" } }).timeout();
});

const queryClient = new QueryClient();
test("renders header but table is not present", async () => {

const restoreConsole = mockConsole();

render(
<QueryClientProvider client={queryClient}>
<MemoryRouter>
<SchoolEditPage />
</MemoryRouter>
</QueryClientProvider>
);
await screen.findByText("Edit School");
expect(screen.queryByTestId("SchoolsForm-name")).not.toBeInTheDocument();
restoreConsole();
});
});

describe("tests where backend is working normally", () => {

const axiosMock = new AxiosMockAdapter(axios);

beforeEach(() => {
axiosMock.reset();
axiosMock.resetHistory();
axiosMock.onGet("/api/currentUser").reply(200, apiCurrentUserFixtures.userOnly);
axiosMock.onGet("/api/systemInfo").reply(200, systemInfoFixtures.showingNeither);
axiosMock.onGet("/api/schools", { params: { abbrev: "ucsb" } }).reply(200, {
abbrev: "ucsb",
name: "University of California, Santa Barbara",
termRegex: "regexTest",
termDescription: "descriptionTest",
termError: "errorTest",
});
axiosMock.onPut('/api/schools/update').reply(200, {
abbrev: "ucsb",
name: "University of California, Sha Bi",
termRegex: "regexTest2",
termDescription: "descText2",
termError: "errTest2",
});
});

const queryClient = new QueryClient();
test("renders without crashing", () => {
render(
<QueryClientProvider client={queryClient}>
<MemoryRouter>
<SchoolEditPage />
</MemoryRouter>
</QueryClientProvider>
);
});

test("Is populated with the data provided", async () => {

render(
<QueryClientProvider client={queryClient}>
<MemoryRouter>
<SchoolEditPage />
</MemoryRouter>
</QueryClientProvider>
);

await screen.findByTestId("SchoolForm-abbrev");

const nameField = screen.getByTestId("SchoolForm-name");
const abbrevField = screen.getByTestId("SchoolForm-abbrev");
const termRegexField = screen.getByTestId("SchoolForm-termRegex");
const termDescriptionField = screen.getByTestId("SchoolForm-termDescription");
const termErrorField = screen.getByTestId("SchoolForm-termError");
const submitButton = screen.getByTestId("SchoolForm-submit");

expect(nameField).toHaveValue("University of California, Santa Barbara");
expect(abbrevField).toHaveValue("ucsb");
expect(termRegexField).toHaveValue("regexTest");
expect(termDescriptionField).toHaveValue("descriptionTest");
expect(termErrorField).toHaveValue("errorTest");
expect(submitButton).toBeInTheDocument();
});

test("Changes when you click Update", async () => {

render(
<QueryClientProvider client={queryClient}>
<MemoryRouter>
<SchoolEditPage />
</MemoryRouter>
</QueryClientProvider>
);

await screen.findByTestId("SchoolForm-abbrev");

const nameField = screen.getByTestId("SchoolForm-name");
const abbrevField = screen.getByTestId("SchoolForm-abbrev");
const termRegexField = screen.getByTestId("SchoolForm-termRegex");
const termDescriptionField = screen.getByTestId("SchoolForm-termDescription");
const termErrorField = screen.getByTestId("SchoolForm-termError");
const submitButton = screen.getByTestId("SchoolForm-submit");

expect(nameField).toHaveValue("University of California, Santa Barbara");
expect(abbrevField).toHaveValue("ucsb");
expect(termRegexField).toHaveValue("regexTest");
expect(termDescriptionField).toHaveValue("descriptionTest");
expect(termErrorField).toHaveValue("errorTest");
expect(submitButton).toBeInTheDocument();

fireEvent.change(nameField, { target: { value: "University of California, Sha Bi" } })
fireEvent.change(termRegexField, { target: { value: "regexTest2" } })
fireEvent.change(termDescriptionField, { target: { value: "descTest2" } })
fireEvent.change(termErrorField, { target: { value: "errTest2" } })

fireEvent.click(submitButton);

await waitFor(() => expect(mockToast).toBeCalled());
expect(mockToast).toBeCalledWith("School Updated - abbrev: ucsb name: University of California, Sha Bi");
expect(mockNavigate).toBeCalledWith({ "to": "/admin/schools" });

expect(axiosMock.history.put.length).toBe(1);
expect(axiosMock.history.put[0].params).toEqual({ abbrev: "ucsb"});
expect(axiosMock.history.put[0].data).toEqual(JSON.stringify({ name: "University of California, Sha Bi", termRegex: "regexTest2", termDescription: "descTest2", termError: "errTest2"}));

});


});
});

Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe("SchoolIndexPage tests", () => {
expect(screen.getByText(/Create School/)).toBeInTheDocument();
});
const button = screen.getByText(/Create School/);
expect(button).toHaveAttribute("href", "/schools/create"); //fixme could have bug because plural
expect(button).toHaveAttribute("href", "/admin/schools/create");
expect(button).toHaveAttribute("style", "float: right;");
});

Expand Down
Loading
Loading