From 8d7e2fb0ada6536a1e3cf7eccfdcda95df965736 Mon Sep 17 00:00:00 2001 From: Frank Niessink Date: Fri, 24 Jan 2025 16:53:35 +0100 Subject: [PATCH] Add accessibility tests to the frontend component. Closes #2934. --- components/frontend/package-lock.json | 180 ++++++++++++++++++ components/frontend/package.json | 2 + components/frontend/src/App.test.js | 45 +++-- components/frontend/src/AppUI.test.js | 54 +++--- components/frontend/src/PageContent.test.js | 26 ++- .../frontend/src/changelog/ChangeLog.js | 10 +- .../frontend/src/changelog/ChangeLog.test.js | 46 ++--- .../src/dashboard/CardDashboard.test.js | 10 +- .../frontend/src/dashboard/IssuesCard.test.js | 18 +- .../frontend/src/dashboard/LegendCard.js | 4 +- .../MetricsRequiringActionCard.test.js | 18 +- .../frontend/src/dashboard/PageHeader.test.js | 33 ++-- .../src/dashboard/StatusBarChart.test.js | 17 +- .../src/dashboard/StatusPieChart.test.js | 12 +- .../frontend/src/header_footer/Footer.test.js | 16 +- .../src/header_footer/Menubar.test.js | 34 ++-- .../src/header_footer/SettingsPanel.test.js | 6 +- .../src/header_footer/UIModeMenu.test.js | 11 +- .../settings_menu/SettingsMenu.js | 2 +- .../frontend/src/issue/IssueStatus.test.js | 125 +++++++----- .../frontend/src/issue/IssuesRows.test.js | 58 +++--- .../measurement/MeasurementSources.test.js | 43 +++-- .../src/measurement/MeasurementTarget.test.js | 166 +++++++--------- .../src/measurement/MeasurementValue.test.js | 69 ++++--- .../frontend/src/measurement/Overrun.js | 2 +- .../frontend/src/measurement/Overrun.test.js | 18 +- .../src/measurement/SourceStatus.test.js | 31 +-- .../frontend/src/measurement/StatusIcon.js | 58 ++++-- .../src/measurement/StatusIcon.test.js | 23 ++- .../frontend/src/measurement/TimeLeft.test.js | 37 ++-- .../src/measurement/TrendSparkline.test.js | 26 ++- .../MetricConfigurationParameters.test.js | 148 +++++++------- .../src/metric/MetricDebtParameters.test.js | 26 ++- .../frontend/src/metric/MetricDetails.test.js | 42 ++-- .../frontend/src/metric/MetricType.test.js | 15 +- components/frontend/src/metric/Target.test.js | 42 ++-- .../frontend/src/metric/TargetVisualiser.js | 2 +- .../src/metric/TargetVisualiser.test.js | 45 +++-- .../frontend/src/metric/TrendGraph.test.js | 6 +- .../NotificationDestinations.test.js | 24 ++- .../frontend/src/report/IssueTracker.js | 3 +- .../frontend/src/report/IssueTracker.test.js | 179 ++++++++--------- components/frontend/src/report/Report.test.js | 78 ++++---- .../src/report/ReportDashboard.test.js | 46 +++-- components/frontend/src/report/ReportTitle.js | 4 +- .../frontend/src/report/ReportTitle.test.js | 62 ++++-- .../src/report/ReportsOverview.test.js | 63 +++--- .../report/ReportsOverviewDashboard.test.js | 30 ++- .../src/report/ReportsOverviewTitle.js | 2 +- .../src/report/ReportsOverviewTitle.test.js | 21 +- components/frontend/src/setupTests.js | 4 + components/frontend/src/source/Source.test.js | 31 +-- .../frontend/src/source/SourceEntities.js | 11 +- .../src/source/SourceEntities.test.js | 45 +++-- .../frontend/src/source/SourceEntity.test.js | 39 ++-- .../src/source/SourceEntityAttribute.test.js | 64 ++++--- .../src/source/SourceEntityDetails.test.js | 32 ++-- .../src/source/SourceParameter.test.js | 82 +++++--- .../src/source/SourceParameters.test.js | 41 ++-- .../frontend/src/source/SourceType.test.js | 64 +++---- .../frontend/src/source/Sources.test.js | 46 +++-- .../frontend/src/subject/Subject.test.js | 9 +- .../frontend/src/subject/SubjectTable.test.js | 24 ++- .../src/subject/SubjectTableFooter.test.js | 15 +- .../src/subject/SubjectTableHeader.js | 8 +- .../src/subject/SubjectTableHeader.test.js | 29 +-- .../frontend/src/subject/SubjectTableRow.js | 6 +- .../src/subject/SubjectTableRow.test.js | 33 ++-- .../frontend/src/subject/SubjectTitle.test.js | 26 ++- .../frontend/src/subject/Subjects.test.js | 11 +- .../src/subject/SubjectsButtonRow.test.js | 17 +- components/frontend/src/testUtils.js | 7 + .../frontend/src/widgets/TableHeaderCell.js | 7 + docs/src/changelog.md | 4 + 74 files changed, 1661 insertions(+), 1032 deletions(-) create mode 100644 components/frontend/src/testUtils.js diff --git a/components/frontend/package-lock.json b/components/frontend/package-lock.json index e81eef4802..cfcf348770 100644 --- a/components/frontend/package-lock.json +++ b/components/frontend/package-lock.json @@ -43,6 +43,8 @@ "eslint-plugin-react": "^7.37.4", "eslint-plugin-simple-import-sort": "^12.1.1", "globals": "^15.14.0", + "jest": "^27.5.1", + "jest-axe": "^9.0.0", "prettier": "^3.4.2", "react-scripts": "5.0.1" }, @@ -3394,6 +3396,19 @@ "node": ">=0.10.0" } }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jest/source-map": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", @@ -4157,6 +4172,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -8087,6 +8109,16 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -11648,6 +11680,32 @@ } } }, + "node_modules/jest-axe": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jest-axe/-/jest-axe-9.0.0.tgz", + "integrity": "sha512-Xt7O0+wIpW31lv0SO1wQZUTyJE7DEmnDEZeTt9/S9L5WUywxrv8BrgvTuQEqujtfaQOcJ70p4wg7UUgK1E2F5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "axe-core": "4.9.1", + "chalk": "4.1.2", + "jest-matcher-utils": "29.2.2", + "lodash.merge": "4.6.2" + }, + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/jest-axe/node_modules/axe-core": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.9.1.tgz", + "integrity": "sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, "node_modules/jest-changed-files": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", @@ -12322,6 +12380,67 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-docblock": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", @@ -12670,6 +12789,67 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/jest-matcher-utils": { + "version": "29.2.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz", + "integrity": "sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.2.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.2.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", diff --git a/components/frontend/package.json b/components/frontend/package.json index 30f990ad6e..ae0ef0ebb3 100644 --- a/components/frontend/package.json +++ b/components/frontend/package.json @@ -67,6 +67,8 @@ "eslint-plugin-react": "^7.37.4", "eslint-plugin-simple-import-sort": "^12.1.1", "globals": "^15.14.0", + "jest": "^27.5.1", + "jest-axe": "^9.0.0", "prettier": "^3.4.2", "react-scripts": "5.0.1" }, diff --git a/components/frontend/src/App.test.js b/components/frontend/src/App.test.js index 9f9fa84365..64950461a7 100644 --- a/components/frontend/src/App.test.js +++ b/components/frontend/src/App.test.js @@ -5,6 +5,7 @@ import history from "history/browser" import * as auth from "./api/auth" import * as fetch_server_api from "./api/fetch_server_api" import App from "./App" +import { expectNoAccessibilityViolations } from "./testUtils" import * as toast from "./widgets/toast" function set_user_in_local_storage(session_expiration_datetime, email) { @@ -32,46 +33,53 @@ afterEach(() => { }) it("shows spinner", async () => { - render() + const { container } = render() expect(screen.getAllByLabelText(/Loading/).length).toBe(1) + await expectNoAccessibilityViolations(container) }) -it("sets the user from local storage", () => { +it("sets the user from local storage", async () => { set_user_in_local_storage("3000-02-23T22:00:50.945Z") - render() + const { container } = render() expect(screen.getAllByText(/admin/).length).toBe(1) expect(screen.getAllByAltText(/Avatar for admin/).length).toBe(1) + await expectNoAccessibilityViolations(container) }) -it("does not set invalid email addresses", () => { +it("does not set invalid email addresses", async () => { set_user_in_local_storage("3000-02-23T22:00:50.945Z", "admin at example.org") - render() + const { container } = render() expect(screen.getAllByText(/admin/).length).toBe(1) expect(screen.queryAllByAltText(/Avatar for admin/).length).toBe(0) + await expectNoAccessibilityViolations(container) }) -it("resets the user when the session is expired on mount", () => { +it("resets the user when the session is expired on mount", async () => { set_user_in_local_storage("2000-02-23T22:00:50.945Z") - render() + const { container } = render() expect(screen.queryAllByText(/admin/).length).toBe(0) + await expectNoAccessibilityViolations(container) }) it("resets the user when the user clicks logout", async () => { set_user_in_local_storage("3000-02-23T22:00:50.945Z") auth.logout = jest.fn().mockResolvedValue({ ok: true }) - render() + const { container } = render() await act(async () => { fireEvent.click(screen.getByText(/admin/)) + await expectNoAccessibilityViolations(container) }) await act(async () => { fireEvent.click(screen.getByText(/Logout/)) }) expect(screen.queryAllByText(/admin/).length).toBe(0) + await expectNoAccessibilityViolations(container) }) -async function selectDate() { +async function selectDate(container) { await act(async () => { fireEvent.click(screen.getByLabelText("Report date")) + await expectNoAccessibilityViolations(container) }) await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Previous month" })) @@ -82,37 +90,42 @@ async function selectDate() { } it("handles a date change", async () => { - render() - await selectDate() + const { container } = render() + await selectDate(container) const expectedDate = dayjs().subtract(1, "month").date(15).toDate().toDateString() expect(screen.getByLabelText("Report date").textContent).toMatch(expectedDate) + await expectNoAccessibilityViolations(container) }) it("handles a date change between two dates in the past", async () => { history.push("/?report_date=2022-03-13") - render() - await selectDate() + const { container } = render() + await selectDate(container) const expectedDate = dayjs().subtract(1, "month").date(15).toDate().toDateString() expect(screen.getByLabelText("Report date").textContent).toMatch(expectedDate) + await expectNoAccessibilityViolations(container) }) -it("reads the report date query parameter", () => { +it("reads the report date query parameter", async () => { history.push("/?report_date=2020-03-13") - render() + const { container } = render() const expectedDate = dayjs("2020-03-13").toDate().toDateString() expect(screen.getByLabelText("Report date").textContent).toMatch(expectedDate) + await expectNoAccessibilityViolations(container) }) it("handles a date reset", async () => { history.push("/?report_date=2020-03-13") - render() + const { container } = render() await act(async () => { fireEvent.click(screen.getByLabelText("Report date")) + await expectNoAccessibilityViolations(container) }) await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Today" })) }) expect(screen.getByLabelText("Report date").textContent).toMatch(/today/) + await expectNoAccessibilityViolations(container) }) it("handles the nr of measurements event source", async () => { diff --git a/components/frontend/src/AppUI.test.js b/components/frontend/src/AppUI.test.js index b2c406c58c..bc10774329 100644 --- a/components/frontend/src/AppUI.test.js +++ b/components/frontend/src/AppUI.test.js @@ -1,6 +1,7 @@ import { ThemeProvider } from "@mui/material/styles" import { act, fireEvent, render, screen } from "@testing-library/react" import history from "history/browser" +import { axe } from "jest-axe" import { dataModel, report } from "./__fixtures__/fixtures" import * as fetch_server_api from "./api/fetch_server_api" @@ -18,33 +19,35 @@ beforeEach(() => { afterEach(() => jest.restoreAllMocks()) -it("shows an error message when there are no reports", async () => { - await act(async () => - render( - - - , - ), - ) - expect(screen.getAllByText(/Sorry, no reports/).length).toBe(1) -}) - -it("handles sorting", async () => { - await act(async () => - render( +async function renderAppUI(reports) { + let result + await act(async () => { + result = render( , - ), - ) + ) + }) + return result +} + +it("shows an error message when there are no reports", async () => { + const { container } = await renderAppUI() + expect(screen.getAllByText(/Sorry, no reports/).length).toBe(1) + expect(await axe(container)).toHaveNoViolations() +}) + +it("handles sorting", async () => { + await renderAppUI([report]) fireEvent.click(screen.getAllByText("Comment")[0]) expect(history.location.search).toEqual("?sort_column_report_uuid=comment") fireEvent.click(screen.getAllByText("Status")[0]) @@ -60,19 +63,10 @@ it("handles sorting", async () => { expect(history.location.search).toEqual("") }) -async function renderAppUI() { - return await act(async () => - render( - - - , - ), - ) -} - it("resets all settings", async () => { history.push("?date_interval=2") - await act(async () => await renderAppUI()) + const { container } = await renderAppUI() + expect(await axe(container)).toHaveNoViolations() fireEvent.click(screen.getByText("Reset settings")) expect(history.location.search).toBe("") }) diff --git a/components/frontend/src/PageContent.test.js b/components/frontend/src/PageContent.test.js index f11cba0fa2..4a6b58105c 100644 --- a/components/frontend/src/PageContent.test.js +++ b/components/frontend/src/PageContent.test.js @@ -6,6 +6,7 @@ import { createTestableSettings } from "./__fixtures__/fixtures" import * as fetch_server_api from "./api/fetch_server_api" import { mockGetAnimations } from "./dashboard/MockAnimations" import { PageContent } from "./PageContent" +import { expectNoAccessibilityViolations } from "./testUtils" jest.mock("react-toastify") jest.mock("./api/fetch_server_api") @@ -25,8 +26,9 @@ afterEach(() => { async function renderPageContent({ loading = false, reports = [], report_date = null, report_uuid = "" } = {}) { const settings = createTestableSettings() - await act(async () => - render( + let result + await act(async () => { + result = render(
, - ), - ) + ) + }) + return result } it("shows the reports overview", async () => { - await renderPageContent({ report_date: new Date(2023, 10, 25) }) + const { container } = await renderPageContent({ report_date: new Date(2023, 10, 25) }) expect(screen.getAllByText(/Sorry, no reports/).length).toBe(1) + await expectNoAccessibilityViolations(container) }) it("shows that the report is missing", async () => { - await renderPageContent({ reports: [{}], report_uuid: "uuid" }) + const { container } = await renderPageContent({ reports: [{}], report_uuid: "uuid" }) expect(screen.getAllByText(/Sorry, this report doesn't exist/).length).toBe(1) + await expectNoAccessibilityViolations(container) }) it("shows that the report was missing", async () => { - await renderPageContent({ + const { container } = await renderPageContent({ report_date: new Date("2022-03-31"), reports: [{}], report_uuid: "uuid", }) expect(screen.getAllByText(/Sorry, this report didn't exist/).length).toBe(1) + await expectNoAccessibilityViolations(container) }) it("shows the loading spinner", async () => { - await renderPageContent({ loading: true }) + const { container } = await renderPageContent({ loading: true }) expect(screen.getAllByRole("progressbar").length).toBe(1) + await expectNoAccessibilityViolations(container) }) function expectMeasurementsCall(date, offset = 0) { @@ -79,8 +86,9 @@ function expectMeasurementsCall(date, offset = 0) { it("fetches measurements", async () => { const mockedDate = new Date("2022-04-27T16:00:05+0000") jest.setSystemTime(mockedDate) - await renderPageContent({ report_date: null }) + const { container } = await renderPageContent({ report_date: null }) expectMeasurementsCall(mockedDate) + await expectNoAccessibilityViolations(container) }) it("fetches measurements if nr dates > 1", async () => { diff --git a/components/frontend/src/changelog/ChangeLog.js b/components/frontend/src/changelog/ChangeLog.js index f259c0c865..4f129a2dba 100644 --- a/components/frontend/src/changelog/ChangeLog.js +++ b/components/frontend/src/changelog/ChangeLog.js @@ -1,5 +1,5 @@ import UpdateIcon from "@mui/icons-material/Update" -import { Button, List, ListItem, ListItemAvatar, ListItemText, Typography } from "@mui/material" +import { Button, List, ListItem, ListItemAvatar, ListItemText, Stack, Typography } from "@mui/material" import { string } from "prop-types" import React, { useEffect, useState } from "react" @@ -71,8 +71,12 @@ function ChangeLogWithoutMemo({ report_uuid, subject_uuid, metric_uuid, source_u return ( <> - {scope} - Most recent first + + {scope} + + Most recent first + + {changes.map((change) => ( { jest.resetAllMocks() }) +async function renderChangeLog({ props }) { + let result + await act(async () => { + result = render() + }) + return result +} + function expectNrEventsToBe(nr) { // Assert that the change log contains nr events const rows = document.querySelectorAll(".MuiListItem-root") @@ -19,65 +28,58 @@ function expectNrEventsToBe(nr) { it("renders no changes", async () => { changelog_api.get_changelog.mockImplementation(() => Promise.resolve({ changelog: [] })) - await act(async () => { - render() - }) + const { container } = await renderChangeLog({}) expectNrEventsToBe(0) + await expectNoAccessibilityViolations(container) }) it("renders one report change", async () => { changelog_api.get_changelog.mockImplementation(() => Promise.resolve({ changelog: [{ timestamp: "2020-01-01" }] })) - await act(async () => { - render() - }) + const { container } = await renderChangeLog({ report_uuid: "uuid" }) expectNrEventsToBe(1) + await expectNoAccessibilityViolations(container) }) it("renders one subject change", async () => { changelog_api.get_changelog.mockImplementation(() => Promise.resolve({ changelog: [{ timestamp: "2020-01-01" }] })) - await act(async () => { - render() - }) + const { container } = await renderChangeLog({ subject_uuid: "uuid" }) expectNrEventsToBe(1) + await expectNoAccessibilityViolations(container) }) it("renders one metric change", async () => { changelog_api.get_changelog.mockImplementation(() => Promise.resolve({ changelog: [{ timestamp: "2020-01-01" }] })) - await act(async () => { - render() - }) + const { container } = await renderChangeLog({ metric_uuid: "uuid" }) expectNrEventsToBe(1) + await expectNoAccessibilityViolations(container) }) it("renders one source change", async () => { changelog_api.get_changelog.mockImplementation(() => Promise.resolve({ changelog: [{ timestamp: "2020-01-01" }] })) - await act(async () => { - render() - }) + const { container } = await renderChangeLog({ source_uuid: "uuid" }) expectNrEventsToBe(1) + await expectNoAccessibilityViolations(container) }) it("loads more changes", async () => { changelog_api.get_changelog.mockImplementation(() => Promise.resolve({ changelog: [{ timestamp: "2020-01-01" }] })) - await act(async () => { - render() - }) + const { container } = await renderChangeLog({ source_uuid: "uuid" }) changelog_api.get_changelog.mockImplementation(() => Promise.resolve({ changelog: [{ timestamp: "2020-01-01" }, { timestamp: "2020-01-02" }] }), ) await act(async () => fireEvent.click(screen.getByText(/Load more changes/))) expectNrEventsToBe(2) + await expectNoAccessibilityViolations(container) }) it("shows error when loading more changes fails", async () => { changelog_api.get_changelog.mockImplementation(() => Promise.resolve({ changelog: [] })) - await act(async () => { - render() - }) + const { container } = await renderChangeLog({ source_uuid: "uuid" }) changelog_api.get_changelog.mockImplementation(() => Promise.reject(new Error("Couldn't retrieve changelog"))) await act(async () => fireEvent.click(screen.getByText(/Load more changes/))) - await waitFor(() => { + await waitFor(async () => { expect(toast.showMessage).toHaveBeenCalledTimes(1) + await expectNoAccessibilityViolations(container) }) expectNrEventsToBe(0) }) diff --git a/components/frontend/src/dashboard/CardDashboard.test.js b/components/frontend/src/dashboard/CardDashboard.test.js index dd566138e6..d579502c67 100644 --- a/components/frontend/src/dashboard/CardDashboard.test.js +++ b/components/frontend/src/dashboard/CardDashboard.test.js @@ -2,6 +2,7 @@ import { ThemeProvider } from "@mui/material/styles" import { fireEvent, render, screen } from "@testing-library/react" import { EDIT_REPORT_PERMISSION, Permissions } from "../context/Permissions" +import { expectNoAccessibilityViolations } from "../testUtils" import { theme } from "../theme" import { CardDashboard } from "./CardDashboard" import { MetricSummaryCard } from "./MetricSummaryCard" @@ -23,12 +24,13 @@ function renderCardDashboard({ cards = [], initialLayout = [], saveLayout = jest ) } -it("returns null without cards", () => { +it("returns null without cards", async () => { const { container } = renderCardDashboard() expect(container.children[0].children.length).toBe(0) + await expectNoAccessibilityViolations(container) }) -it("adds the card to the dashboard", () => { +it("adds the card to the dashboard", async () => { const { container } = renderCardDashboard({ cards: [ { ], }) expect(container.children.length).toBe(1) + await expectNoAccessibilityViolations(container) }) it("does not save the layout after click", async () => { const mockCallback = jest.fn() - renderCardDashboard({ + const { container } = renderCardDashboard({ cards: [ { }) fireEvent.click(screen.getByText("Card")) expect(mockCallback).not.toHaveBeenCalled() + await expectNoAccessibilityViolations(container) }) diff --git a/components/frontend/src/dashboard/IssuesCard.test.js b/components/frontend/src/dashboard/IssuesCard.test.js index 443f9ad53c..41a0bec246 100644 --- a/components/frontend/src/dashboard/IssuesCard.test.js +++ b/components/frontend/src/dashboard/IssuesCard.test.js @@ -1,5 +1,6 @@ import { render, screen } from "@testing-library/react" +import { expectNoAccessibilityViolations } from "../testUtils" import { IssuesCard } from "./IssuesCard" const report = { @@ -20,23 +21,26 @@ const report = { } function renderIssuesCard({ selected = false } = {}) { - render() + return render() } -it("shows the correct title", () => { - renderIssuesCard() +it("shows the correct title", async () => { + const { container } = renderIssuesCard() expect(screen.getByText(/Issues/)).toBeInTheDocument() + await expectNoAccessibilityViolations(container) }) -it("shows the title as selected when the card is selected", () => { - renderIssuesCard({ selected: true }) +it("shows the title as selected when the card is selected", async () => { + const { container } = renderIssuesCard({ selected: true }) expect(screen.getByText(/Issues/)).toHaveClass("selected") + await expectNoAccessibilityViolations(container) }) -it("shows the number of issues", () => { - renderIssuesCard() +it("shows the number of issues", async () => { + const { container } = renderIssuesCard() expect(screen.getByRole("row", { name: "Todo 0" })).toBeInTheDocument() expect(screen.getByRole("row", { name: "Doing 1" })).toBeInTheDocument() expect(screen.getByRole("row", { name: "Done 0" })).toBeInTheDocument() expect(screen.getByRole("row", { name: "Unknown 2" })).toBeInTheDocument() + await expectNoAccessibilityViolations(container) }) diff --git a/components/frontend/src/dashboard/LegendCard.js b/components/frontend/src/dashboard/LegendCard.js index d086fe0008..6371522964 100644 --- a/components/frontend/src/dashboard/LegendCard.js +++ b/components/frontend/src/dashboard/LegendCard.js @@ -7,10 +7,10 @@ import { DashboardCard } from "./DashboardCard" export function LegendCard() { const listItems = STATUSES.map((status) => ( - + diff --git a/components/frontend/src/dashboard/MetricsRequiringActionCard.test.js b/components/frontend/src/dashboard/MetricsRequiringActionCard.test.js index 99647c8b9c..ed7ac59fd0 100644 --- a/components/frontend/src/dashboard/MetricsRequiringActionCard.test.js +++ b/components/frontend/src/dashboard/MetricsRequiringActionCard.test.js @@ -1,5 +1,6 @@ import { render, screen } from "@testing-library/react" +import { expectNoAccessibilityViolations } from "../testUtils" import { MetricsRequiringActionCard } from "./MetricsRequiringActionCard" const report = { @@ -25,23 +26,26 @@ const report = { } function renderMetricsRequiringActionCard({ selected = false } = {}) { - render() + return render() } -it("shows the correct title", () => { - renderMetricsRequiringActionCard() +it("shows the correct title", async () => { + const { container } = renderMetricsRequiringActionCard() expect(screen.getByText(/Action required/)).toBeInTheDocument() + await expectNoAccessibilityViolations(container) }) -it("shows the title as selected when the card is selected", () => { - renderMetricsRequiringActionCard({ selected: true }) +it("shows the title as selected when the card is selected", async () => { + const { container } = renderMetricsRequiringActionCard({ selected: true }) expect(screen.getByText(/Action required/)).toHaveClass("selected") + await expectNoAccessibilityViolations(container) }) -it("shows the number of metrics", () => { - renderMetricsRequiringActionCard() +it("shows the number of metrics", async () => { + const { container } = renderMetricsRequiringActionCard() expect(screen.getByRole("row", { name: "Unknown 0" })).toBeInTheDocument() expect(screen.getByRole("row", { name: "Target not met 1" })).toBeInTheDocument() expect(screen.getByRole("row", { name: "Near target met 2" })).toBeInTheDocument() expect(screen.getByRole("row", { name: "Total 3" })).toBeInTheDocument() + await expectNoAccessibilityViolations(container) }) diff --git a/components/frontend/src/dashboard/PageHeader.test.js b/components/frontend/src/dashboard/PageHeader.test.js index f2eade23f9..9510736003 100644 --- a/components/frontend/src/dashboard/PageHeader.test.js +++ b/components/frontend/src/dashboard/PageHeader.test.js @@ -1,5 +1,6 @@ import { render, screen } from "@testing-library/react" +import { expectNoAccessibilityViolations } from "../testUtils" import { mockGetAnimations } from "./MockAnimations" import { PageHeader } from "./PageHeader" @@ -34,36 +35,42 @@ const report = { } function renderPageHeader({ lastUpdate = new Date(), report = null, reportDate = null } = {}) { - render() + return render() } -it("displays correct title for the reports overview", () => { - renderPageHeader({}) +it("displays correct title for the reports overview", async () => { + const { container } = renderPageHeader({}) expect(screen.getByText(/Reports overview/)).toBeInTheDocument() + await expectNoAccessibilityViolations(container) }) -it("displays correct title for a report", () => { - renderPageHeader({ report: report }) +it("displays correct title for a report", async () => { + const { container } = renderPageHeader({ report: report }) expect(screen.getByText(/Title/)).toBeInTheDocument() + await expectNoAccessibilityViolations(container) }) -it("displays dates in en-GB format", () => { - renderPageHeader({ lastUpdate: mockLastUpdate, report: report, reportDate: mockReportDate }) +it("displays dates in en-GB format", async () => { + const { container } = renderPageHeader({ lastUpdate: mockLastUpdate, report: report, reportDate: mockReportDate }) expect(screen.getByText(/Report date: 24-03-2024/)).toBeInTheDocument() expect(screen.getByText(/Generated: 26-03-2024, 12:34/)).toBeInTheDocument() + await expectNoAccessibilityViolations(container) }) -it("displays report URL", () => { - renderPageHeader({ report: report }) +it("displays report URL", async () => { + const { container } = renderPageHeader({ report: report }) expect(screen.getByTestId("reportUrl")).toBeInTheDocument() + await expectNoAccessibilityViolations(container) }) -it("displays version link", () => { - renderPageHeader({ lastUpdate: mockLastUpdate, report: report }) +it("displays version link", async () => { + const { container } = renderPageHeader({ lastUpdate: mockLastUpdate, report: report }) expect(screen.getByTestId("version")).toBeInTheDocument() + await expectNoAccessibilityViolations(container) }) -it("displays today as report date if no report date is provided", () => { - renderPageHeader({ lastUpdate: mockLastUpdate, report: report }) +it("displays today as report date if no report date is provided", async () => { + const { container } = renderPageHeader({ lastUpdate: mockLastUpdate, report: report }) expect(screen.getByText(`Report date: ${mockDateOfToday}`)).toBeInTheDocument() + await expectNoAccessibilityViolations(container) }) diff --git a/components/frontend/src/dashboard/StatusBarChart.test.js b/components/frontend/src/dashboard/StatusBarChart.test.js index f8d9873e0a..7d549a8912 100644 --- a/components/frontend/src/dashboard/StatusBarChart.test.js +++ b/components/frontend/src/dashboard/StatusBarChart.test.js @@ -2,6 +2,7 @@ import { ThemeProvider } from "@mui/material/styles" import { queryAllByRole, queryAllByText, render, screen } from "@testing-library/react" import userEvent from "@testing-library/user-event" +import { expectNoAccessibilityViolations } from "../testUtils" import { theme } from "../theme" import { MetricSummaryCard } from "./MetricSummaryCard" @@ -19,23 +20,26 @@ function renderBarChart(maxY, red) { const dateString = new Date("2023-01-02").toLocaleDateString() -it("shows the number of metrics per status when the total is zero", () => { - renderBarChart(0, 0) +it("shows the number of metrics per status when the total is zero", async () => { + const { container } = renderBarChart(0, 0) expect(screen.queryAllByLabelText(`Status on ${dateString}: no metrics`, { exact: false }).length).toBe(1) + await expectNoAccessibilityViolations(container) }) -it("shows the number of metrics per status when the total is not zero", () => { - renderBarChart(10, 0) +it("shows the number of metrics per status when the total is not zero", async () => { + const { container } = renderBarChart(10, 0) expect(screen.queryAllByLabelText(`Status on ${dateString}: no metrics`, { exact: false }).length).toBe(1) + await expectNoAccessibilityViolations(container) }) -it("shows the number of metrics per status", () => { - renderBarChart(2, 2) +it("shows the number of metrics per status", async () => { + const { container } = renderBarChart(2, 2) expect( screen.queryAllByLabelText(`Status on ${dateString}: 2 metrics, 2 target not met`, { exact: false, }).length, ).toBe(1) + await expectNoAccessibilityViolations(container) }) it("shows the tooltip", async () => { @@ -44,4 +48,5 @@ it("shows the tooltip", async () => { const targetNotMetLabel = "Target not met" await userEvent.hover(targetNotMetBar) expect(queryAllByText(container, targetNotMetLabel).length).toBe(1) + await expectNoAccessibilityViolations(container) }) diff --git a/components/frontend/src/dashboard/StatusPieChart.test.js b/components/frontend/src/dashboard/StatusPieChart.test.js index 26844f538e..13c47c832e 100644 --- a/components/frontend/src/dashboard/StatusPieChart.test.js +++ b/components/frontend/src/dashboard/StatusPieChart.test.js @@ -2,6 +2,7 @@ import { ThemeProvider } from "@mui/material/styles" import { queryAllByRole, queryAllByText, render, screen } from "@testing-library/react" import userEvent from "@testing-library/user-event" +import { expectNoAccessibilityViolations } from "../testUtils" import { theme } from "../theme" import { MetricSummaryCard } from "./MetricSummaryCard" @@ -15,19 +16,21 @@ function renderPieChart({ summary = { blue: 0, red: 0, green: 0, yellow: 0, whit const dateString = new Date("2023-01-01").toLocaleDateString() -it("shows there are no metrics", () => { - renderPieChart() +it("shows there are no metrics", async () => { + const { container } = renderPieChart() expect(screen.getAllByLabelText(`Status on ${dateString}: no metrics.`, { exact: false }).length).toBe(1) + await expectNoAccessibilityViolations(container) }) -it("shows the number of metrics per status", () => { - renderPieChart({ summary: { blue: 2, red: 1, green: 2, yellow: 3, white: 1, grey: 1 } }) +it("shows the number of metrics per status", async () => { + const { container } = renderPieChart({ summary: { blue: 2, red: 1, green: 2, yellow: 3, white: 1, grey: 1 } }) expect( screen.getAllByLabelText( `Status on ${dateString}: 10 metrics, 2 target met, 1 target not met, 3 near target, 1 with accepted technical debt, 2 informative, 1 with unknown status.`, { exact: false }, ).length, ).toBe(1) + await expectNoAccessibilityViolations(container) }) it("shows the tooltip", async () => { @@ -36,4 +39,5 @@ it("shows the tooltip", async () => { const unknownLabel = "Unknown" await userEvent.hover(unknownPie) expect(queryAllByText(container, unknownLabel).length).toBe(1) + await expectNoAccessibilityViolations(container) }) diff --git a/components/frontend/src/header_footer/Footer.test.js b/components/frontend/src/header_footer/Footer.test.js index 131d6acf63..b7666d6297 100644 --- a/components/frontend/src/header_footer/Footer.test.js +++ b/components/frontend/src/header_footer/Footer.test.js @@ -1,21 +1,25 @@ import { render, screen } from "@testing-library/react" +import { expectNoAccessibilityViolations } from "../testUtils" import { Footer } from "./Footer" -it("renders the report title when there is a report", () => { +it("renders the report title when there is a report", async () => { const lastUpdate = new Date() - render(