From c731fca7ab8d3fc338ec7c54058af5b72bc54285 Mon Sep 17 00:00:00 2001 From: Tyler Canepa Date: Tue, 28 May 2024 14:18:12 -0700 Subject: [PATCH 1/7] test for ShowPage and Storybook --- frontend/src/main/pages/CoursesShowPage.js | 44 +++++++++++++++++++ .../stories/pages/CourseShowPage.stories.js | 30 +++++++++++++ .../controllers/CoursesController.java | 2 +- 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 frontend/src/main/pages/CoursesShowPage.js create mode 100644 frontend/src/stories/pages/CourseShowPage.stories.js diff --git a/frontend/src/main/pages/CoursesShowPage.js b/frontend/src/main/pages/CoursesShowPage.js new file mode 100644 index 000000000..137bb8356 --- /dev/null +++ b/frontend/src/main/pages/CoursesShowPage.js @@ -0,0 +1,44 @@ +import React from 'react' +import { useBackend } from 'main/utils/useBackend'; +import BasicLayout from "main/layouts/BasicLayout/BasicLayout"; +import CoursesTable from 'main/components/Courses/CoursesTable'; +import { Button } from 'react-bootstrap'; +import { useCurrentUser, hasRole} from 'main/utils/currentUser'; + +export default function CourseShowPage() { + + const { id } = useParams(); + const { data: currentUser } = useCurrentUser(); + + const showButton = () => { + return ( + + ) + } + + const { data: courses, error: _error, status: _status } = + useBackend( + // Stryker disable next-line all : don't test internal caching of React Query + [`/api/courses?id=${id}`], + // Stryker disable next-line all : GET is the default + { method: "GET", url: "/api/courses/all", params: { id }}, + [] + ); + + return ( + +
+ {(hasRole(currentUser, "ROLE_ADMIN") || hasRole(currentUser, "ROLE_INSTRUCTOR")) && showButton()} +

Show Course

+ + +
+
+ ) +} diff --git a/frontend/src/stories/pages/CourseShowPage.stories.js b/frontend/src/stories/pages/CourseShowPage.stories.js new file mode 100644 index 000000000..c3d916052 --- /dev/null +++ b/frontend/src/stories/pages/CourseShowPage.stories.js @@ -0,0 +1,30 @@ +import React from 'react'; +import { apiCurrentUserFixtures } from "fixtures/currentUserFixtures"; +import { systemInfoFixtures } from "fixtures/systemInfoFixtures"; +import { coursesFixtures } from "fixtures/coursesFixtures"; +import { rest } from "msw"; + +import CourseShowPage from "main/pages/CourseShowPage"; + +export default { + title: 'pages/Course/CourseShowPage', + component: CourseShowPage +}; + +const Template = () => ; + +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/get', (_req, res, ctx) => { + return res(ctx.json(coursesFixtures.threeCourses)[0]); + }), + ], +} \ No newline at end of file diff --git a/src/main/java/edu/ucsb/cs156/organic/controllers/CoursesController.java b/src/main/java/edu/ucsb/cs156/organic/controllers/CoursesController.java index c0de4e063..dd62a4f96 100644 --- a/src/main/java/edu/ucsb/cs156/organic/controllers/CoursesController.java +++ b/src/main/java/edu/ucsb/cs156/organic/controllers/CoursesController.java @@ -235,4 +235,4 @@ public Course deleteCourse( return course; } -} +} \ No newline at end of file From f1c7927fe763d68e45978d4ae381040530d461b8 Mon Sep 17 00:00:00 2001 From: Tyler Canepa Date: Tue, 28 May 2024 18:56:49 -0700 Subject: [PATCH 2/7] added tests with full coverage and mutation --- frontend/src/App.js | 1 + .../main/components/Courses/CoursesTable.js | 1 + frontend/src/main/pages/CoursesShowPage.js | 28 ++-- .../stories/pages/CourseShowPage.stories.js | 6 +- .../src/tests/pages/CoursesShowPage.test.js | 121 ++++++++++++++++++ 5 files changed, 134 insertions(+), 23 deletions(-) create mode 100644 frontend/src/tests/pages/CoursesShowPage.test.js diff --git a/frontend/src/App.js b/frontend/src/App.js index 1cfcd516e..dee8f069e 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -16,6 +16,7 @@ import SchoolIndexPage from "main/pages/SchoolIndexPage"; import CoursesCreatePage from "main/pages/CoursesCreatePage"; import CourseIndexPage from "main/pages/CourseIndexPage"; + import { hasRole, useCurrentUser } from "main/utils/currentUser"; import NotFoundPage from "main/pages/NotFoundPage"; diff --git a/frontend/src/main/components/Courses/CoursesTable.js b/frontend/src/main/components/Courses/CoursesTable.js index a6f6ec368..3c413a36f 100644 --- a/frontend/src/main/components/Courses/CoursesTable.js +++ b/frontend/src/main/components/Courses/CoursesTable.js @@ -37,6 +37,7 @@ import React from "react"; { Header: 'id', accessor: 'id', + Cell: ({ value }) => {value}, }, { Header: 'Name', diff --git a/frontend/src/main/pages/CoursesShowPage.js b/frontend/src/main/pages/CoursesShowPage.js index 137bb8356..da88c5794 100644 --- a/frontend/src/main/pages/CoursesShowPage.js +++ b/frontend/src/main/pages/CoursesShowPage.js @@ -1,43 +1,31 @@ import React from 'react' +import { useParams } from "react-router-dom"; import { useBackend } from 'main/utils/useBackend'; import BasicLayout from "main/layouts/BasicLayout/BasicLayout"; import CoursesTable from 'main/components/Courses/CoursesTable'; -import { Button } from 'react-bootstrap'; -import { useCurrentUser, hasRole} from 'main/utils/currentUser'; +import { useCurrentUser} from 'main/utils/currentUser'; export default function CourseShowPage() { - const { id } = useParams(); + let { id } = useParams(); const { data: currentUser } = useCurrentUser(); - - const showButton = () => { - return ( - - ) - } const { data: courses, error: _error, status: _status } = useBackend( // Stryker disable next-line all : don't test internal caching of React Query [`/api/courses?id=${id}`], // Stryker disable next-line all : GET is the default - { method: "GET", url: "/api/courses/all", params: { id }}, + { method: "GET", url: "/api/courses/get", params: { id }}, + // Stryker disable next-line all : GET is the default [] ); + return (
- {(hasRole(currentUser, "ROLE_ADMIN") || hasRole(currentUser, "ROLE_INSTRUCTOR")) && showButton()} -

Show Course

- - +

Course {id} Info

+
) diff --git a/frontend/src/stories/pages/CourseShowPage.stories.js b/frontend/src/stories/pages/CourseShowPage.stories.js index c3d916052..9f3efbe4f 100644 --- a/frontend/src/stories/pages/CourseShowPage.stories.js +++ b/frontend/src/stories/pages/CourseShowPage.stories.js @@ -13,9 +13,9 @@ export default { const Template = () => ; -export const ThreeItemsAdminUser = Template.bind({}); +export const OneItemsAdminUser = Template.bind({}); -ThreeItemsAdminUser.parameters = { +OneItemsAdminUser.parameters = { msw: [ rest.get('/api/currentUser', (_req, res, ctx) => { return res( ctx.json(apiCurrentUserFixtures.adminUser)); @@ -24,7 +24,7 @@ ThreeItemsAdminUser.parameters = { return res(ctx.json(systemInfoFixtures.showingNeither)); }), rest.get('/api/courses/get', (_req, res, ctx) => { - return res(ctx.json(coursesFixtures.threeCourses)[0]); + return res(ctx.json(coursesFixtures.oneCourse)); }), ], } \ No newline at end of file diff --git a/frontend/src/tests/pages/CoursesShowPage.test.js b/frontend/src/tests/pages/CoursesShowPage.test.js new file mode 100644 index 000000000..6a5241201 --- /dev/null +++ b/frontend/src/tests/pages/CoursesShowPage.test.js @@ -0,0 +1,121 @@ +import { render, waitFor, screen, fireEvent } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "react-query"; +import { MemoryRouter } from "react-router-dom"; +import axios from "axios"; +import AxiosMockAdapter from "axios-mock-adapter"; +import CoursesShowPage from "main/pages/CoursesShowPage"; + +import AdminUsersPage from "main/pages/AdminUsersPage"; +import { apiCurrentUserFixtures } from "fixtures/currentUserFixtures"; +import { systemInfoFixtures } from "fixtures/systemInfoFixtures"; +import usersFixtures from "fixtures/usersFixtures"; + +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) + }; +}); + +// Mock the enableEndDateValidation function +jest.mock('main/components/Courses/dateValidation', () => ({ + enableEndDateValidation: jest.fn(), +})); + +const mockNavigate = jest.fn(); +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); + return { + __esModule: true, + ...originalModule, + useParams: () => ({ + id: 17 + }), + Navigate: (x) => { mockNavigate(x); return null; } + }; +}); + +describe("CoursesShowPage 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/courses/get", { params: { id: 17 } }).timeout(); + }); + + const queryClient = new QueryClient(); + test("renders header but table is not present", async () => { + + const restoreConsole = mockConsole(); + + render( + + + + + + ); + await screen.findByText("Course 17 Info"); + expect(screen.queryByTestId("CoursesForm-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/courses/get", { params: { id: 17 } }).reply(200, { + id: 17, + name: "CS 156", + school: "UCSB", + term: "f23", + startDate: "2023-09-29T00:00", + endDate: "2023-12-15T00:00", + githubOrg: "ucsb-cs156-f23" + }); + }); + + const queryClient = new QueryClient(); + test("renders without crashing", () => { + render( + + + + + + ); + }); + + test("renders with data", () => { + render( + + + + + + ); + expect(screen.getByText("CS 156")).toBeInTheDocument(); + expect(screen.getByText("UCSB")).toBeInTheDocument(); + expect(screen.getByText("f23")).toBeInTheDocument(); + }); + + }); +}); \ No newline at end of file From 881952795a3b3f71f3dab95221f93a4f85dbe092 Mon Sep 17 00:00:00 2001 From: Tyler Canepa Date: Tue, 28 May 2024 19:20:32 -0700 Subject: [PATCH 3/7] addedstorybook fixes --- frontend/package.json | 2 +- .../src/stories/pages/CourseShowPage.stories.js | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 4bc6ce9fc..9b2f54b8f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -57,7 +57,7 @@ "test": "react-scripts test", "eject": "react-scripts eject", "coverage": "react-scripts test --coverage --watchAll=false", - "storybook": "start-storybook --docs -p 6006 -s public", + "storybook": "NODE_OPTIONS=\"--openssl-legacy-provider\" start-storybook --docs -p 6006 -s public", "build-storybook": "build-storybook --docs -s public" }, "browserslist": { diff --git a/frontend/src/stories/pages/CourseShowPage.stories.js b/frontend/src/stories/pages/CourseShowPage.stories.js index 9f3efbe4f..fa2586012 100644 --- a/frontend/src/stories/pages/CourseShowPage.stories.js +++ b/frontend/src/stories/pages/CourseShowPage.stories.js @@ -4,18 +4,17 @@ import { systemInfoFixtures } from "fixtures/systemInfoFixtures"; import { coursesFixtures } from "fixtures/coursesFixtures"; import { rest } from "msw"; -import CourseShowPage from "main/pages/CourseShowPage"; +import CoursesShowPage from "main/pages/CoursesShowPage"; export default { - title: 'pages/Course/CourseShowPage', - component: CourseShowPage + title: 'pages/CourseShowPage', + component: CoursesShowPage }; -const Template = () => ; +const Template = () => ; -export const OneItemsAdminUser = Template.bind({}); - -OneItemsAdminUser.parameters = { +export const Default = Template.bind({}); +Default.parameters = { msw: [ rest.get('/api/currentUser', (_req, res, ctx) => { return res( ctx.json(apiCurrentUserFixtures.adminUser)); From 8ffbfb0bc818937e88a11090757e88fec9a9bac0 Mon Sep 17 00:00:00 2001 From: Tyler Canepa Date: Tue, 28 May 2024 19:50:00 -0700 Subject: [PATCH 4/7] fixed course table tests for stryker --- .../components/Courses/CoursesTable.test.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/frontend/src/tests/components/Courses/CoursesTable.test.js b/frontend/src/tests/components/Courses/CoursesTable.test.js index 6b15f5725..eb1070671 100644 --- a/frontend/src/tests/components/Courses/CoursesTable.test.js +++ b/frontend/src/tests/components/Courses/CoursesTable.test.js @@ -294,4 +294,22 @@ describe("UserTable tests", () => { expect(totalCoursesElement).toBeInTheDocument(); }); + test("ID is a hyperlink", async () => { + + const currentUser = currentUserFixtures.adminUser; + + render( + + + + + + + ); + + const idLink = screen.getByTestId(`CoursesTable-cell-row-0-col-id`).querySelector('a'); + expect(idLink).toBeInTheDocument(); + expect(idLink.getAttribute('href')).toBe('/courses/1'); + }); + }); \ No newline at end of file From a7b7e018ec793c80a3f7f462d12f639ecaaecf87 Mon Sep 17 00:00:00 2001 From: Tyler Canepa Date: Tue, 28 May 2024 20:50:30 -0700 Subject: [PATCH 5/7] fixed hyperlink testing for table --- frontend/src/main/components/Courses/CoursesTable.js | 2 +- frontend/src/tests/components/Courses/CoursesTable.test.js | 2 +- frontend/src/tests/pages/CoursesShowPage.test.js | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/src/main/components/Courses/CoursesTable.js b/frontend/src/main/components/Courses/CoursesTable.js index 3c413a36f..235e1dc52 100644 --- a/frontend/src/main/components/Courses/CoursesTable.js +++ b/frontend/src/main/components/Courses/CoursesTable.js @@ -37,7 +37,7 @@ import React from "react"; { Header: 'id', accessor: 'id', - Cell: ({ value }) => {value}, + Cell: ({ value }) => {value}, }, { Header: 'Name', diff --git a/frontend/src/tests/components/Courses/CoursesTable.test.js b/frontend/src/tests/components/Courses/CoursesTable.test.js index eb1070671..915542a35 100644 --- a/frontend/src/tests/components/Courses/CoursesTable.test.js +++ b/frontend/src/tests/components/Courses/CoursesTable.test.js @@ -307,7 +307,7 @@ describe("UserTable tests", () => { ); - const idLink = screen.getByTestId(`CoursesTable-cell-row-0-col-id`).querySelector('a'); + const idLink = screen.getByTestId(`linkToCoursesPage-1`); expect(idLink).toBeInTheDocument(); expect(idLink.getAttribute('href')).toBe('/courses/1'); }); diff --git a/frontend/src/tests/pages/CoursesShowPage.test.js b/frontend/src/tests/pages/CoursesShowPage.test.js index 6a5241201..fe89ce93b 100644 --- a/frontend/src/tests/pages/CoursesShowPage.test.js +++ b/frontend/src/tests/pages/CoursesShowPage.test.js @@ -1,14 +1,12 @@ -import { render, waitFor, screen, fireEvent } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "react-query"; import { MemoryRouter } from "react-router-dom"; import axios from "axios"; import AxiosMockAdapter from "axios-mock-adapter"; import CoursesShowPage from "main/pages/CoursesShowPage"; -import AdminUsersPage from "main/pages/AdminUsersPage"; import { apiCurrentUserFixtures } from "fixtures/currentUserFixtures"; import { systemInfoFixtures } from "fixtures/systemInfoFixtures"; -import usersFixtures from "fixtures/usersFixtures"; import mockConsole from "jest-mock-console"; From 4c3524ae67f50338a16c8607fdc9244e7479bc9f Mon Sep 17 00:00:00 2001 From: Tyler Canepa Date: Tue, 28 May 2024 20:57:03 -0700 Subject: [PATCH 6/7] fix storybook --- frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 9b2f54b8f..4bc6ce9fc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -57,7 +57,7 @@ "test": "react-scripts test", "eject": "react-scripts eject", "coverage": "react-scripts test --coverage --watchAll=false", - "storybook": "NODE_OPTIONS=\"--openssl-legacy-provider\" start-storybook --docs -p 6006 -s public", + "storybook": "start-storybook --docs -p 6006 -s public", "build-storybook": "build-storybook --docs -s public" }, "browserslist": { From 6268b35827e87e4b5216f393e384fcdc3e36c4ce Mon Sep 17 00:00:00 2001 From: hylee Date: Wed, 29 May 2024 18:52:13 -0700 Subject: [PATCH 7/7] added tooltips hover for school --- frontend/src/fixtures/schoolsFixtures.js | 12 ++---- .../src/main/components/School/SchoolForm.js | 38 +++++++++---------- .../src/main/components/School/SchoolTable.js | 4 -- .../components/School/SchoolForm.test.js | 7 ++-- .../components/School/SchoolTable.test.js | 12 +++--- 5 files changed, 32 insertions(+), 41 deletions(-) diff --git a/frontend/src/fixtures/schoolsFixtures.js b/frontend/src/fixtures/schoolsFixtures.js index ddee6e292..9755c89cc 100644 --- a/frontend/src/fixtures/schoolsFixtures.js +++ b/frontend/src/fixtures/schoolsFixtures.js @@ -3,30 +3,26 @@ const schoolsFixtures = { "abbrev": "ucsb", "name": "UC Santa Barbara", "termRegex": "[WSMF]\\d\\d", - "termDescription": "Enter quarter, e.g. F23, W24, S24, M24", - "termError": "Quarter must be entered in the correct format" + "termDescription": "Enter quarter, e.g. F23, W24, S24, M24" }, threeSchools: [ { "abbrev": "ucsb", "name": "UC Santa Barbara", "termRegex": "[WSMF]\\d\\d", - "termDescription": "Enter quarter, e.g. F23, W24, S24, M24", - "termError": "Quarter must be entered in the correct format" + "termDescription": "Enter quarter, e.g. F23, W24, S24, M24" }, { "abbrev": "umn", "name": "University of Minnesota", "termRegex": "[WSMF]\\d\\d", - "termDescription": "Enter quarter, e.g. F23, W24, S24, M24", - "termError": "Quarter must be entered in the correct format" + "termDescription": "Enter quarter, e.g. F23, W24, S24, M24" }, { "abbrev": "ucsd", "name": "UC San Diego", "termRegex": "[WSMF]\\d\\d", - "termDescription": "Enter quarter, e.g. F23, W24, S24, M24", - "termError": "Quarter must be entered in the correct format" + "termDescription": "Enter quarter, e.g. F23, W24, S24, M24" } ] }; diff --git a/frontend/src/main/components/School/SchoolForm.js b/frontend/src/main/components/School/SchoolForm.js index 336f5f2bb..56a388604 100644 --- a/frontend/src/main/components/School/SchoolForm.js +++ b/frontend/src/main/components/School/SchoolForm.js @@ -1,4 +1,4 @@ -import { Button, Form, Row, Col } from 'react-bootstrap'; +import { Button, Form, Row, Col, OverlayTrigger, Tooltip } from 'react-bootstrap'; import { useForm } from 'react-hook-form' import { useNavigate } from 'react-router-dom' @@ -26,6 +26,11 @@ function SchoolForm({ initialContents, submitAction, buttonLabel = "Create" }) { Abbreviation + Please enter an abbreviation for school name in lowercase. EX: UCSB} + delay='5' + > + {errors.abbrev?.type === 'required' && 'Abbreviation is required. '} {errors.abbrev?.type === 'pattern' && 'Abbreviation must be lowercase letters (_ and . allowed), e.g. ucsb'} @@ -57,6 +63,11 @@ function SchoolForm({ initialContents, submitAction, buttonLabel = "Create" }) { Term Regex + Please enter a regular expression for the format of terms (semesters or quarters) at the school. EX: [WSMF]\d\d for UCSB} + delay='5' + > + {errors.termRegex && 'Term Regex is required. '} @@ -75,6 +87,11 @@ function SchoolForm({ initialContents, submitAction, buttonLabel = "Create" }) { Term Description + Please enter a term description. EX: Quarter, Semester or Session} + delay='5' + > + {errors.termDescription && 'Term Description is required.'} @@ -89,24 +107,6 @@ function SchoolForm({ initialContents, submitAction, buttonLabel = "Create" }) { - - - - Term Error - - - {errors.termError && 'Term Error is required.'} - - - - -