Skip to content

Commit

Permalink
Merge pull request #26 from ucsb-cs156-s24/Jason-StaffIndexPage
Browse files Browse the repository at this point in the history
Jason Added Staff Index Page (Redirected from a Course's Staff Button)
  • Loading branch information
pconrad authored Jun 2, 2024
2 parents bdc7c9b + c598eeb commit d1e0c1e
Show file tree
Hide file tree
Showing 9 changed files with 623 additions and 19 deletions.
4 changes: 4 additions & 0 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import SchoolIndexPage from "main/pages/SchoolIndexPage";

import CoursesCreatePage from "main/pages/CoursesCreatePage";
import CoursesIndexPage from "main/pages/CoursesIndexPage";
import CoursesStaffPage from "main/pages/CoursesStaffPage";

import { hasRole, useCurrentUser } from "main/utils/currentUser";
import NotFoundPage from "main/pages/NotFoundPage";
import StaffCreatePage from "main/pages/StaffCreatePage";
import CoursesShowPage from "./main/pages/CoursesShowPage";

function App() {
Expand All @@ -43,6 +45,8 @@ function App() {
<Route path="/courses/create" element={<CoursesCreatePage />} />
<Route path="/courses" element={<CoursesIndexPage />} />
<Route path="/courses/edit/:id" element={<CoursesEditPage />} />
<Route path="/courses/:id/staff" element={<CoursesStaffPage />} />
<Route path="/courses/addStaff" element={<StaffCreatePage />} />
<Route path="/courses/:id" element={<CoursesShowPage />} />
</>
) : null;
Expand Down
32 changes: 25 additions & 7 deletions frontend/src/main/components/Staff/StaffTable.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import React from "react";
import OurTable from "main/components/OurTable"
import OurTable, { ButtonColumn } from "main/components/OurTable"
import { useBackendMutation } from "main/utils/useBackend";
import { cellToAxiosParamsDelete, onDeleteSuccess } from "main/components/Utils/StaffUtils"
import { hasRole } from "main/utils/currentUser";


export default function StaffTable({ staff }) {
export default function StaffTable({ staff, currentUser }) {
// Stryker disable all : hard to test for query caching

// Stryker disable next-line all : TODO try to make a good test for this
const deleteMutation = useBackendMutation(
cellToAxiosParamsDelete,
{ onSuccess: onDeleteSuccess },
["/courses/:id/staff"]
);
// Stryker restore all

// Stryker disable next-line all : TODO try to make a good test for this
const deleteCallback = async (cell) => { deleteMutation.mutate(cell); }

const columns = [
{
Expand All @@ -22,8 +34,14 @@ import OurTable from "main/components/OurTable"

];

return <OurTable
data={staff}
columns={columns}
testid={"StaffTable"} />;
if (hasRole(currentUser, "ROLE_ADMIN") || hasRole(currentUser, "ROLE_INSTRUCTOR")) {
columns.push(ButtonColumn("Delete", "danger", deleteCallback, "StaffTable"));
}

return (
<>
<div>Total Staff: {staff.length}</div>
<OurTable data={staff} columns={columns} testid={"StaffTable"} />
</>
);
};
46 changes: 46 additions & 0 deletions frontend/src/main/pages/CoursesStaffPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react'
import { useBackend } from 'main/utils/useBackend';
import BasicLayout from "main/layouts/BasicLayout/BasicLayout";
import StaffTable from 'main/components/Staff/StaffTable';
import { Button } from 'react-bootstrap';
import { useCurrentUser, hasRole} from 'main/utils/currentUser';
import { useParams } from "react-router-dom";

export default function CourseIndexPage() {

const { data: currentUser } = useCurrentUser();
let { id } = useParams();
const addStaffButton = () => {

return (
<Button
variant="primary"
href="/courses/addStaff"
style={{ float: "right" }}
>
Add Staff Member
</Button>
)

}

const { data: staff, error: _error, status: _status } =
useBackend(
// Stryker disable next-line all : don't test internal caching of React Query
[`/api/courses?id=${id}/staff`],
// Stryker disable next-line all : GET is the default
{ method: "GET", url: "/api/courses/getStaff", params: { courseId: parseInt(id) } },
[]
);

return (
<BasicLayout>
<div className="pt-2">
{(hasRole(currentUser, "ROLE_ADMIN") || hasRole(currentUser, "ROLE_INSTRUCTOR")) && addStaffButton()}
<h1>Staff</h1>
<StaffTable staff={staff} currentUser={currentUser} />

</div>
</BasicLayout>
)
}
13 changes: 13 additions & 0 deletions frontend/src/main/pages/StaffCreatePage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import BasicLayout from "main/layouts/BasicLayout/BasicLayout";

export default function StaffCreatePage() {

// Stryker disable all : placeholder for future implementation
return (
<BasicLayout>
<div className="pt-2">
<h1>Create page not yet implemented</h1>
</div>
</BasicLayout>
)
}
73 changes: 73 additions & 0 deletions frontend/src/stories/pages/CoursesStaffPage.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react';
import { apiCurrentUserFixtures } from "fixtures/currentUserFixtures";
import { systemInfoFixtures } from "fixtures/systemInfoFixtures";
import { staffFixture } from "fixtures/staffFixture";
import { rest } from "msw";

import CoursesStaffPage from "main/pages/CoursesStaffPage";

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

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

export const Empty = Template.bind({});
Empty.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/courses/all', (_req, res, ctx) => {
return res(ctx.json(staffFixture.threeStaff));
}),
rest.delete('/api/courses', (req, res, ctx) => {
window.alert("DELETE: " + JSON.stringify(req.url));
return res(ctx.status(200),ctx.json({}));
}),
]
}

export const ThreeItemsInstructorUser = Template.bind({});

ThreeItemsInstructorUser.parameters = {
msw: [
rest.get('/api/currentUser', (_req, res, ctx) => {
return res( ctx.json(apiCurrentUserFixtures.instructorUser));
}),
rest.get('/api/systemInfo', (_req, res, ctx) => {
return res(ctx.json(systemInfoFixtures.showingNeither));
}),
rest.get('/api/courses/all', (_req, res, ctx) => {
return res(ctx.json(staffFixture.threeStaff));
}),
rest.delete('/api/courses', (req, res, ctx) => {
window.alert("DELETE: " + JSON.stringify(req.url));
return res(ctx.status(200),ctx.json({}));
}),
],
}

export const ThreeItemsAdminUser = Template.bind({});

ThreeItemsAdminUser.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/courses/all', (_req, res, ctx) => {
return res(ctx.json(staffFixture.threeStaff));
}),
rest.delete('/api/courses', (req, res, ctx) => {
window.alert("DELETE: " + JSON.stringify(req.url));
return res(ctx.status(200),ctx.json({}));
}),
],
}
32 changes: 32 additions & 0 deletions frontend/src/stories/pages/StaffCreatePage.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { apiCurrentUserFixtures } from "fixtures/currentUserFixtures";
import { systemInfoFixtures } from "fixtures/systemInfoFixtures";
import { rest } from "msw";

import StaffCreatePage from 'main/pages/StaffCreatePage';

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

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

export const Default = Template.bind({});
Default.parameters = {
msw: [
rest.get('/api/currentUser', (_req, res, ctx) => {
return res(ctx.json(apiCurrentUserFixtures.userOnly));
}),
rest.get('/api/systemInfo', (_req, res, ctx) => {
return res(ctx.json(systemInfoFixtures.showingNeither));
}),
rest.post('/api/helprequest/post', (req, res, ctx) => {
window.alert("POST: " + JSON.stringify(req.url));
return res(ctx.status(200),ctx.json({}));
}),
]
}



76 changes: 64 additions & 12 deletions frontend/src/tests/components/Staff/StaffTable.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@

import StaffTable from "main/components/Staff/StaffTable"
import { currentUserFixtures } from "fixtures/currentUserFixtures";
// import { fireEvent, render, waitFor, screen } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import { staffFixture } from "fixtures/staffFixture";
import { QueryClient, QueryClientProvider } from "react-query";
import { MemoryRouter } from "react-router-dom";
import { fireEvent, render, waitFor, screen } from "@testing-library/react";


const mockedNavigate = jest.fn();
Expand Down Expand Up @@ -58,11 +58,6 @@ describe("StaffTable tests", () => {
expect(screen.getByTestId(`${testId}-cell-row-0-col-id`)).toHaveTextContent("1");
expect(screen.getByTestId(`${testId}-cell-row-1-col-id`)).toHaveTextContent("2");



const editButton = screen.queryByTestId(`${testId}-cell-row-0-col-Edit-button`);
expect(editButton).not.toBeInTheDocument();

const deleteButton = screen.queryByTestId(`${testId}-cell-row-0-col-Delete-button`);
expect(deleteButton).not.toBeInTheDocument();

Expand Down Expand Up @@ -125,15 +120,72 @@ describe("StaffTable tests", () => {
expect(screen.getByTestId(`${testId}-cell-row-0-col-id`)).toHaveTextContent("1");
expect(screen.getByTestId(`${testId}-cell-row-1-col-id`)).toHaveTextContent("2");

// const deleteButton = screen.getByTestId(`${testId}-cell-row-0-col-Delete-button`);
// expect(deleteButton).toBeInTheDocument();
// expect(deleteButton).toHaveClass("btn-danger");
const deleteButton = screen.queryByTestId(`${testId}-cell-row-0-col-Delete-button`);
expect(deleteButton).toBeInTheDocument();
expect(deleteButton).toHaveClass("btn-danger");

const totalStaffElement = screen.getByText("Total Staff: 3"); // Assuming there are 3 staff members in the fixture
expect(totalStaffElement).toBeInTheDocument();

});

test("Has the expected colum headers and content for instructorUser", () => {

const currentUser = currentUserFixtures.instructorUser;

render(
<QueryClientProvider client={queryClient}>
<MemoryRouter>
<StaffTable staff={staffFixture.threeStaff} currentUser={currentUser} />
</MemoryRouter>
</QueryClientProvider>

);


expectedHeaders.forEach((headerText) => {
const header = screen.getByText(headerText);
expect(header).toBeInTheDocument();
});


expectedFields.forEach((field) => {
const header = screen.getByTestId(`${testId}-cell-row-0-col-${field}`);
expect(header).toBeInTheDocument();
});

});
expect(screen.getByTestId(`${testId}-cell-row-0-col-id`)).toHaveTextContent("1");
expect(screen.getByTestId(`${testId}-cell-row-1-col-id`)).toHaveTextContent("2");

const deleteButton = screen.queryByTestId(`${testId}-cell-row-0-col-Delete-button`);
expect(deleteButton).toBeInTheDocument();
expect(deleteButton).toHaveClass("btn-danger");

const totalStaffElement = screen.getByText("Total Staff: 3"); // Assuming there are 3 staff members in the fixture
expect(totalStaffElement).toBeInTheDocument();

});

test("Delete button calls the callback", async () => {

const currentUser = currentUserFixtures.adminUser;

render(
<QueryClientProvider client={queryClient}>
<MemoryRouter>
<StaffTable staff={staffFixture.threeStaff} currentUser={currentUser} />
</MemoryRouter>
</QueryClientProvider>

);

await waitFor(() => { expect(screen.getByTestId(`StaffTable-cell-row-0-col-id`)).toHaveTextContent("1"); });

const deleteButton = screen.getByTestId(`StaffTable-cell-row-0-col-Delete-button`);
expect(deleteButton).toBeInTheDocument();

fireEvent.click(deleteButton);

const totalCoursesElement = screen.getByText("Total Staff: 3"); // Assuming there are 3 staff members in the fixture
expect(totalCoursesElement).toBeInTheDocument();
});
});
Loading

0 comments on commit d1e0c1e

Please sign in to comment.