diff --git a/frontend-react/404.html b/frontend-react/404.html index 6e352fe8836..3250d693a92 100644 --- a/frontend-react/404.html +++ b/frontend-react/404.html @@ -5,10 +5,14 @@ - %VITE_TITLE% - - - + Page not found + + + + diff --git a/frontend-react/e2e/helpers/utils.ts b/frontend-react/e2e/helpers/utils.ts index eaa20ef8e30..75570e0cb17 100644 --- a/frontend-react/e2e/helpers/utils.ts +++ b/frontend-react/e2e/helpers/utils.ts @@ -154,3 +154,14 @@ export function removeDateTime(filename: string) { return filename; } + +export function isAbsoluteURL(url: string): boolean { + return /^(https?|ftp|file|mailto):/.test(url); +} + +export function isAssetURL(url: string): boolean { + // Regular expression to match common asset file extensions at the end of a URL + const assetExtensions = /\.(pdf|png|jpg|jpeg|gif|bmp|svg|webp|mp4|mp3|wav|ogg|avi|mov|mkv|zip|rar|tar|gz|iso)$/i; + + return assetExtensions.test(url); +} diff --git a/frontend-react/e2e/spec/all/authenticated/admin/last-mile-failures-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/admin/last-mile-failures-page.spec.ts index 60bc2032f68..c6b94cbb9cd 100644 --- a/frontend-react/e2e/spec/all/authenticated/admin/last-mile-failures-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/admin/last-mile-failures-page.spec.ts @@ -1,3 +1,4 @@ +import { pageNotFound } from "../../../../../src/content/error/ErrorMessages"; import { tableRows } from "../../../../helpers/utils"; import { LastMileFailuresPage } from "../../../../pages/authenticated/admin/last-mile-failures"; import { test as baseTest, expect } from "../../../../test"; @@ -95,7 +96,7 @@ test.describe("Last Mile Failure page", () => { test.use({ storageState: "e2e/.auth/receiver.json" }); test("returns Page Not Found", async ({ lastMileFailuresPage }) => { - await expect(lastMileFailuresPage.page).toHaveTitle(/Page Not Found/); + await expect(lastMileFailuresPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); @@ -103,7 +104,7 @@ test.describe("Last Mile Failure page", () => { test.use({ storageState: "e2e/.auth/sender.json" }); test("returns Page Not Found", async ({ lastMileFailuresPage }) => { - await expect(lastMileFailuresPage.page).toHaveTitle(/Page Not Found/); + await expect(lastMileFailuresPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); diff --git a/frontend-react/e2e/spec/all/authenticated/admin/message-id-search-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/admin/message-id-search-page.spec.ts index 6af81d230eb..f5f19cc8620 100644 --- a/frontend-react/e2e/spec/all/authenticated/admin/message-id-search-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/admin/message-id-search-page.spec.ts @@ -1,3 +1,4 @@ +import { pageNotFound } from "../../../../../src/content/error/ErrorMessages"; import { noData, tableRows } from "../../../../helpers/utils"; import { MOCK_GET_MESSAGES } from "../../../../mocks/messages"; import { MessageIDSearchPage } from "../../../../pages/authenticated/admin/message-id-search"; @@ -160,7 +161,7 @@ test.describe("Message ID Search Page", () => { messageIDSearchPage.mockError = true; await messageIDSearchPage.reload(); - await expect(messageIDSearchPage.page).toHaveTitle(/Page Not Found/); + await expect(messageIDSearchPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); @@ -171,7 +172,7 @@ test.describe("Message ID Search Page", () => { messageIDSearchPage.mockError = true; await messageIDSearchPage.reload(); - await expect(messageIDSearchPage.page).toHaveTitle(/Page Not Found/); + await expect(messageIDSearchPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); }); diff --git a/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-edit-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-edit-page.spec.ts index 4068bc091dd..ab33d105683 100644 --- a/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-edit-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-edit-page.spec.ts @@ -1,4 +1,5 @@ import { expect } from "@playwright/test"; +import { pageNotFound } from "../../../../../src/content/error/ErrorMessages"; import { tableDataCellValue } from "../../../../helpers/utils"; import { MOCK_GET_ORGANIZATION_IGNORE } from "../../../../mocks/organizations"; import { OrganizationEditPage } from "../../../../pages/authenticated/admin/organization-edit"; @@ -47,14 +48,14 @@ test.describe("Organization Edit Page", () => { test.describe("receiver user", () => { test.use({ storageState: "e2e/.auth/receiver.json" }); test("returns Page Not Found", async ({ organizationEditPage }) => { - await expect(organizationEditPage.page).toHaveTitle(/Page Not Found/); + await expect(organizationEditPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); test.describe("sender user", () => { test.use({ storageState: "e2e/.auth/sender.json" }); test("returns Page Not Found", async ({ organizationEditPage }) => { - await expect(organizationEditPage.page).toHaveTitle(/Page Not Found/); + await expect(organizationEditPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); @@ -84,18 +85,24 @@ test.describe("Organization Edit Page", () => { test("has expected 'Meta'", async ({ organizationEditPage }) => { const meta = organizationEditPage.page.getByTestId("gridContainer").getByTestId("grid").nth(2); await expect(meta).toHaveText(organizationEditPage.getOrgMeta(MOCK_GET_ORGANIZATION_IGNORE)); - }); + }); test("has expected 'Description'", async ({ organizationEditPage }) => { - await expect(organizationEditPage.page.getByTestId("description")).toHaveValue(MOCK_GET_ORGANIZATION_IGNORE.description); + await expect(organizationEditPage.page.getByTestId("description")).toHaveValue( + MOCK_GET_ORGANIZATION_IGNORE.description, + ); }); test("has expected 'Jurisdiction'", async ({ organizationEditPage }) => { - await expect(organizationEditPage.page.getByTestId("jurisdiction")).toHaveValue(MOCK_GET_ORGANIZATION_IGNORE.jurisdiction); + await expect(organizationEditPage.page.getByTestId("jurisdiction")).toHaveValue( + MOCK_GET_ORGANIZATION_IGNORE.jurisdiction, + ); }); test("has expected 'Filters'", async ({ organizationEditPage }) => { - await expect(organizationEditPage.page.getByTestId("filters")).toHaveValue(JSON.stringify(MOCK_GET_ORGANIZATION_IGNORE.filters, null, 2)); + await expect(organizationEditPage.page.getByTestId("filters")).toHaveValue( + JSON.stringify(MOCK_GET_ORGANIZATION_IGNORE.filters, null, 2), + ); }); }); @@ -129,9 +136,12 @@ test.describe("Organization Edit Page", () => { test.describe("'Organization Sender Settings' section", () => { test("can create a new organization sender", async ({ organizationEditPage }) => { await organizationEditPage.page - .locator('#orgsendersettings').getByRole('link', { name: 'New' }) + .locator("#orgsendersettings") + .getByRole("link", { name: "New" }) .click(); - await expect(organizationEditPage.page).toHaveURL(`/admin/orgnewsetting/org/ignore/settingtype/sender`); + await expect(organizationEditPage.page).toHaveURL( + `/admin/orgnewsetting/org/ignore/settingtype/sender`, + ); await expect(organizationEditPage.page.getByText(/Org name: ignore/)).toBeVisible(); await expect(organizationEditPage.page.getByText(/Setting Type: sender/)).toBeVisible(); @@ -141,9 +151,20 @@ test.describe("Organization Edit Page", () => { }); test("can edit an organization sender", async ({ organizationEditPage }) => { - const firstOrgSender = await organizationEditPage.page.locator("#orgsendersettings").nth(0).locator("td").nth(0).innerText(); - await organizationEditPage.page.locator('#orgsendersettings').getByRole('link', { name: 'Edit' }).nth(0).click(); - await expect(organizationEditPage.page).toHaveURL(`/admin/orgsendersettings/org/ignore/sender/${firstOrgSender}/action/edit`); + const firstOrgSender = await organizationEditPage.page + .locator("#orgsendersettings") + .nth(0) + .locator("td") + .nth(0) + .innerText(); + await organizationEditPage.page + .locator("#orgsendersettings") + .getByRole("link", { name: "Edit" }) + .nth(0) + .click(); + await expect(organizationEditPage.page).toHaveURL( + `/admin/orgsendersettings/org/ignore/sender/${firstOrgSender}/action/edit`, + ); await expect(organizationEditPage.page.getByText(`Org name: ignore`)).toBeVisible(); await expect(organizationEditPage.page.getByText(`Sender name: ${firstOrgSender}`)).toBeVisible(); @@ -162,15 +183,24 @@ test.describe("Organization Edit Page", () => { const orgSenderLocator = firstOrgSender.replace("-", "_"); - await expect(organizationEditPage.page.locator(`#id_Item__${orgSenderLocator}__has_been_saved`).getByTestId("alerttoast")).toHaveText(`Item '${firstOrgSender}' has been saved`); + await expect( + organizationEditPage.page + .locator(`#id_Item__${orgSenderLocator}__has_been_saved`) + .getByTestId("alerttoast"), + ).toHaveText(`Item '${firstOrgSender}' has been saved`); await expect(organizationEditPage.page).toHaveURL(organizationEditPage.url); }); test("can cancel when editing an organization sender", async ({ organizationEditPage }) => { const firstOrgSender = await tableDataCellValue(organizationEditPage.page, 0, 0); - await organizationEditPage.page. - locator('#orgsendersettings').getByRole('link', { name: 'Edit' }).nth(0).click(); - await expect(organizationEditPage.page).toHaveURL(`/admin/orgsendersettings/org/ignore/sender/${firstOrgSender}/action/edit`); + await organizationEditPage.page + .locator("#orgsendersettings") + .getByRole("link", { name: "Edit" }) + .nth(0) + .click(); + await expect(organizationEditPage.page).toHaveURL( + `/admin/orgsendersettings/org/ignore/sender/${firstOrgSender}/action/edit`, + ); await expect(organizationEditPage.page.getByText(`Org name: ignore`)).toBeVisible(); await expect(organizationEditPage.page.getByText(`Sender name: ${firstOrgSender}`)).toBeVisible(); @@ -182,9 +212,12 @@ test.describe("Organization Edit Page", () => { test.describe("'Organization Receiver Settings' section", () => { test("can create a new organization receiver", async ({ organizationEditPage }) => { await organizationEditPage.page - .locator('#orgreceiversettings').getByRole('link', { name: 'New' }) + .locator("#orgreceiversettings") + .getByRole("link", { name: "New" }) .click(); - await expect(organizationEditPage.page).toHaveURL(`/admin/orgnewsetting/org/ignore/settingtype/receiver`); + await expect(organizationEditPage.page).toHaveURL( + `/admin/orgnewsetting/org/ignore/settingtype/receiver`, + ); await expect(organizationEditPage.page.getByText(/Org name: ignore/)).toBeVisible(); await expect(organizationEditPage.page.getByText(/Setting Type: receiver/)).toBeVisible(); @@ -194,11 +227,24 @@ test.describe("Organization Edit Page", () => { }); test("can edit an organization receiver", async ({ organizationEditPage }) => { - const firstOrgReceiver = await organizationEditPage.page.locator("#orgreceiversettings").nth(0).locator("td").nth(0).innerText(); - await organizationEditPage.page.locator('#orgreceiversettings').getByRole('link', { name: 'Edit' }).nth(0).click(); - await expect(organizationEditPage.page).toHaveURL(`/admin/orgreceiversettings/org/ignore/receiver/${firstOrgReceiver}/action/edit`); + const firstOrgReceiver = await organizationEditPage.page + .locator("#orgreceiversettings") + .nth(0) + .locator("td") + .nth(0) + .innerText(); + await organizationEditPage.page + .locator("#orgreceiversettings") + .getByRole("link", { name: "Edit" }) + .nth(0) + .click(); + await expect(organizationEditPage.page).toHaveURL( + `/admin/orgreceiversettings/org/ignore/receiver/${firstOrgReceiver}/action/edit`, + ); await expect(organizationEditPage.page.getByText(`Org name: ignore`)).toBeVisible(); - await expect(organizationEditPage.page.getByText(`Receiver name: ${firstOrgReceiver}`)).toBeVisible(); + await expect( + organizationEditPage.page.getByText(`Receiver name: ${firstOrgReceiver}`), + ).toBeVisible(); await organizationEditPage.orgReceiverEdit.editJsonButton.click(); const modal = organizationEditPage.page.getByTestId("modalWindow").nth(0); @@ -208,23 +254,42 @@ test.describe("Organization Edit Page", () => { await expect(organizationEditPage.orgReceiverEdit.editJsonModal.save).toHaveAttribute("disabled"); await organizationEditPage.orgReceiverEdit.editJsonModal.checkSyntax.click(); - await expect(organizationEditPage.orgReceiverEdit.editJsonModal.save).not.toHaveAttribute("disabled"); + await expect(organizationEditPage.orgReceiverEdit.editJsonModal.save).not.toHaveAttribute( + "disabled", + ); await organizationEditPage.orgReceiverEdit.editJsonModal.save.click(); await expect(modal).toBeHidden(); const orgReceiverLocator = firstOrgReceiver.replace("-", "_"); - await expect(organizationEditPage.page.locator(`#id_Item__${orgReceiverLocator}__has_been_updated`).getByTestId("alerttoast")).toHaveText(`Item '${firstOrgReceiver}' has been updated`); + await expect( + organizationEditPage.page + .locator(`#id_Item__${orgReceiverLocator}__has_been_updated`) + .getByTestId("alerttoast"), + ).toHaveText(`Item '${firstOrgReceiver}' has been updated`); await expect(organizationEditPage.page).toHaveURL(organizationEditPage.url); }); test("can cancel when editing an organization receiver", async ({ organizationEditPage }) => { - const firstOrgReceiver = await organizationEditPage.page.locator("#orgreceiversettings").nth(0).locator("td").nth(0).innerText(); - await organizationEditPage.page.locator('#orgreceiversettings').getByRole('link', { name: 'Edit' }).nth(0).click(); - await expect(organizationEditPage.page).toHaveURL(`/admin/orgreceiversettings/org/ignore/receiver/${firstOrgReceiver}/action/edit`); + const firstOrgReceiver = await organizationEditPage.page + .locator("#orgreceiversettings") + .nth(0) + .locator("td") + .nth(0) + .innerText(); + await organizationEditPage.page + .locator("#orgreceiversettings") + .getByRole("link", { name: "Edit" }) + .nth(0) + .click(); + await expect(organizationEditPage.page).toHaveURL( + `/admin/orgreceiversettings/org/ignore/receiver/${firstOrgReceiver}/action/edit`, + ); await expect(organizationEditPage.page.getByText(`Org name: ignore`)).toBeVisible(); - await expect(organizationEditPage.page.getByText(`Receiver name: ${firstOrgReceiver}`)).toBeVisible(); + await expect( + organizationEditPage.page.getByText(`Receiver name: ${firstOrgReceiver}`), + ).toBeVisible(); await organizationEditPage.orgReceiverEdit.cancelButton.click(); await expect(organizationEditPage.page).toHaveURL(organizationEditPage.url); diff --git a/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-page.spec.ts index 70c8cf8079f..cb9ee546ffe 100644 --- a/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-page.spec.ts @@ -2,6 +2,7 @@ import { expect } from "@playwright/test"; import { readFileSync } from "node:fs"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; +import { pageNotFound } from "../../../../../src/content/error/ErrorMessages"; import { MOCK_GET_ORGANIZATION_SETTINGS_LIST } from "../../../../mocks/organizations"; import { OrganizationPage } from "../../../../pages/authenticated/admin/organization"; import { test as baseTest } from "../../../../test"; @@ -51,14 +52,14 @@ test.describe("Admin Organization Settings Page", () => { test.describe("receiver user", () => { test.use({ storageState: "e2e/.auth/receiver.json" }); test("returns Page Not Found", async ({ organizationPage }) => { - await expect(organizationPage.page).toHaveTitle(/Page Not Found/); + await expect(organizationPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); test.describe("sender user", () => { test.use({ storageState: "e2e/.auth/sender.json" }); test("returns Page Not Found", async ({ organizationPage }) => { - await expect(organizationPage.page).toHaveTitle(/Page Not Found/); + await expect(organizationPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); @@ -79,8 +80,8 @@ test.describe("Admin Organization Settings Page", () => { test.describe("when there is no error", () => { test("nav contains the 'Admin tools' dropdown with 'Organization Settings' option", async ({ - organizationPage, - }) => { + organizationPage, + }) => { const navItems = organizationPage.page.locator(".usa-nav li"); await expect(navItems).toContainText(["Admin tools"]); @@ -126,8 +127,8 @@ test.describe("Admin Organization Settings Page", () => { i === 0 ? MOCK_GET_ORGANIZATION_SETTINGS_LIST[0] : (MOCK_GET_ORGANIZATION_SETTINGS_LIST.find((i) => i.name === cols[0]) ?? { - name: "INVALID", - }); + name: "INVALID", + }); // if first row, we expect column headers. else, the data row matching id (name) // SetEdit is text of buttons in button column const expectedColContents = diff --git a/frontend-react/e2e/spec/all/authenticated/admin/receiver-status-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/admin/receiver-status-page.spec.ts index edba29a56c0..5f9049f8a09 100644 --- a/frontend-react/e2e/spec/all/authenticated/admin/receiver-status-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/admin/receiver-status-page.spec.ts @@ -1,6 +1,7 @@ -import {addDays, endOfDay, startOfDay, subDays} from "date-fns"; -import {AdminReceiverStatusPage} from "../../../../pages/authenticated/admin/receiver-status"; -import {test as baseTest, expect, logins} from "../../../../test"; +import { addDays, endOfDay, startOfDay, subDays } from "date-fns"; +import { pageNotFound } from "../../../../../src/content/error/ErrorMessages"; +import { AdminReceiverStatusPage } from "../../../../pages/authenticated/admin/receiver-status"; +import { test as baseTest, expect, logins } from "../../../../test"; export interface AdminReceiverStatusPageFixtures { adminReceiverStatusPage: AdminReceiverStatusPage; @@ -38,29 +39,29 @@ const test = baseTest.extend({ test.describe("Admin Receiver Status Page", () => { test.describe("not authenticated", () => { - test("redirects to login", async ({adminReceiverStatusPage}) => { + test("redirects to login", async ({ adminReceiverStatusPage }) => { await expect(adminReceiverStatusPage.page).toHaveURL("/login"); }); }); test.describe("authenticated receiver", () => { - test.use({storageState: logins.receiver.path}); - test("returns Page Not Found", async ({adminReceiverStatusPage}) => { - await expect(adminReceiverStatusPage.page).toHaveTitle(/Page Not Found/); + test.use({ storageState: logins.receiver.path }); + test("returns Page Not Found", async ({ adminReceiverStatusPage }) => { + await expect(adminReceiverStatusPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); test.describe("authenticated sender", () => { - test.use({storageState: logins.sender.path}); - test("returns Page Not Found", async ({adminReceiverStatusPage}) => { - await expect(adminReceiverStatusPage.page).toHaveTitle(/Page Not Found/); + test.use({ storageState: logins.sender.path }); + test("returns Page Not Found", async ({ adminReceiverStatusPage }) => { + await expect(adminReceiverStatusPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); test.describe("authenticated admin", () => { - test.use({storageState: logins.admin.path}); + test.use({ storageState: logins.admin.path }); - test("If there is an error, the error is shown on the page", async ({adminReceiverStatusPage}) => { + test("If there is an error, the error is shown on the page", async ({ adminReceiverStatusPage }) => { adminReceiverStatusPage.mockError = true; await adminReceiverStatusPage.reload(); @@ -68,72 +69,66 @@ test.describe("Admin Receiver Status Page", () => { }); test.describe("Header", () => { - test( - "has correct title + heading", - async ({adminReceiverStatusPage}) => { - await adminReceiverStatusPage.testHeader(); - }, - ); + test("has correct title + heading", async ({ adminReceiverStatusPage }) => { + await adminReceiverStatusPage.testHeader(); + }); }); test.describe("When there is no error", () => { test.describe("Displays correctly", () => { - test.describe( - "filters", - () => { - test("date range", async ({adminReceiverStatusPage}) => { - const {button, label, modalOverlay, valueDisplay} = - adminReceiverStatusPage.filterFormInputs.dateRange; - await expect(label).toBeVisible(); - await expect(button).toBeVisible(); - await expect(valueDisplay).toHaveText(adminReceiverStatusPage.expectedDateRangeLabelText); - await expect(modalOverlay).toBeHidden(); - }); + test.describe("filters", () => { + test("date range", async ({ adminReceiverStatusPage }) => { + const { button, label, modalOverlay, valueDisplay } = + adminReceiverStatusPage.filterFormInputs.dateRange; + await expect(label).toBeVisible(); + await expect(button).toBeVisible(); + await expect(valueDisplay).toHaveText(adminReceiverStatusPage.expectedDateRangeLabelText); + await expect(modalOverlay).toBeHidden(); + }); - test("receiver name", async ({adminReceiverStatusPage}) => { - const {input, expectedTooltipText, label, tooltip, expectedDefaultValue} = - adminReceiverStatusPage.filterFormInputs.receiverName; - await expect(label).toBeVisible(); - await expect(input).toBeVisible(); - await expect(input).toHaveValue(expectedDefaultValue); - - await expect(tooltip).toBeHidden(); - await input.hover(); - await expect(tooltip).toBeVisible(); - await expect(tooltip).toHaveText(expectedTooltipText); - }); + test("receiver name", async ({ adminReceiverStatusPage }) => { + const { input, expectedTooltipText, label, tooltip, expectedDefaultValue } = + adminReceiverStatusPage.filterFormInputs.receiverName; + await expect(label).toBeVisible(); + await expect(input).toBeVisible(); + await expect(input).toHaveValue(expectedDefaultValue); + + await expect(tooltip).toBeHidden(); + await input.hover(); + await expect(tooltip).toBeVisible(); + await expect(tooltip).toHaveText(expectedTooltipText); + }); - test("results message", async ({adminReceiverStatusPage}) => { - const {input, expectedTooltipText, label, tooltip, expectedDefaultValue} = - adminReceiverStatusPage.filterFormInputs.resultMessage; - await expect(label).toBeVisible(); - await expect(input).toBeVisible(); - await expect(input).toHaveValue(expectedDefaultValue); - - await expect(tooltip).toBeHidden(); - await input.hover(); - await expect(tooltip).toBeVisible(); - await expect(tooltip).toHaveText(expectedTooltipText); - }); + test("results message", async ({ adminReceiverStatusPage }) => { + const { input, expectedTooltipText, label, tooltip, expectedDefaultValue } = + adminReceiverStatusPage.filterFormInputs.resultMessage; + await expect(label).toBeVisible(); + await expect(input).toBeVisible(); + await expect(input).toHaveValue(expectedDefaultValue); + + await expect(tooltip).toBeHidden(); + await input.hover(); + await expect(tooltip).toBeVisible(); + await expect(tooltip).toHaveText(expectedTooltipText); + }); - test("success type", async ({adminReceiverStatusPage}) => { - const {input, expectedTooltipText, label, tooltip, expectedDefaultValue} = - adminReceiverStatusPage.filterFormInputs.successType; - await expect(label).toBeVisible(); - await expect(input).toBeVisible(); - await expect(input).toHaveValue(expectedDefaultValue); - - await expect(tooltip).toBeHidden(); - await input.hover(); - await expect(tooltip).toBeVisible(); - await expect(tooltip).toHaveText(expectedTooltipText); - }); - }, - ); + test("success type", async ({ adminReceiverStatusPage }) => { + const { input, expectedTooltipText, label, tooltip, expectedDefaultValue } = + adminReceiverStatusPage.filterFormInputs.successType; + await expect(label).toBeVisible(); + await expect(input).toBeVisible(); + await expect(input).toHaveValue(expectedDefaultValue); + + await expect(tooltip).toBeHidden(); + await input.hover(); + await expect(tooltip).toBeVisible(); + await expect(tooltip).toHaveText(expectedTooltipText); + }); + }); // Failures here indicate potential misalignment of playwright/browser timezone test.describe("receiver statuses", () => { - test("time periods", async ({adminReceiverStatusPage}) => { + test("time periods", async ({ adminReceiverStatusPage }) => { const result = await adminReceiverStatusPage.testReceiverStatusDisplay(); expect(result).toBe(true); }); @@ -142,64 +137,57 @@ test.describe("Admin Receiver Status Page", () => { test.describe("Footer", () => { test("has footer and explicit scroll to footer and scroll to top", async ({ - adminReceiverStatusPage, - }) => { + adminReceiverStatusPage, + }) => { await adminReceiverStatusPage.testFooter(); }); }); test.describe("Functions correctly", () => { test.describe("filters", () => { - test.describe( - "date range", - () => { - test("works through calendar", async ({adminReceiverStatusPage}) => { - const {valueDisplay} = adminReceiverStatusPage.filterFormInputs.dateRange; - const now = new Date(); - const targetFrom = startOfDay(subDays(now, 3)); - const targetTo = addDays(endOfDay(now), 1); - - const reqUrl = await adminReceiverStatusPage.updateFilters({ - dateRange: { - value: [targetFrom, targetTo], - }, - }); - expect(reqUrl).toBeDefined(); - - await expect(valueDisplay).toHaveText( - adminReceiverStatusPage.expectedDateRangeLabelText, - ); - expect(Object.fromEntries(reqUrl!.searchParams.entries())).toMatchObject({ - start_date: targetFrom.toISOString(), - end_date: targetTo.toISOString(), - }); + test.describe("date range", () => { + test("works through calendar", async ({ adminReceiverStatusPage }) => { + const { valueDisplay } = adminReceiverStatusPage.filterFormInputs.dateRange; + const now = new Date(); + const targetFrom = startOfDay(subDays(now, 3)); + const targetTo = addDays(endOfDay(now), 1); + + const reqUrl = await adminReceiverStatusPage.updateFilters({ + dateRange: { + value: [targetFrom, targetTo], + }, }); + expect(reqUrl).toBeDefined(); - test("works through textboxes", async ({adminReceiverStatusPage}) => { - const {valueDisplay} = adminReceiverStatusPage.filterFormInputs.dateRange; - await expect(adminReceiverStatusPage.receiverStatusRowsLocator).not.toHaveCount(0); - const now = new Date(); - const targetFrom = startOfDay(subDays(now, 3)); - const targetTo = addDays(endOfDay(now), 1); - - const reqUrl = await adminReceiverStatusPage.updateFilters({ - dateRange: { - value: [targetFrom, targetTo], - }, - }); - - expect(reqUrl).toBeDefined(); - - await expect(valueDisplay).toHaveText( - adminReceiverStatusPage.expectedDateRangeLabelText, - ); - expect(Object.fromEntries(reqUrl!.searchParams.entries())).toMatchObject({ - start_date: targetFrom.toISOString(), - end_date: targetTo.toISOString(), - }); + await expect(valueDisplay).toHaveText(adminReceiverStatusPage.expectedDateRangeLabelText); + expect(Object.fromEntries(reqUrl!.searchParams.entries())).toMatchObject({ + start_date: targetFrom.toISOString(), + end_date: targetTo.toISOString(), }); - }, - ); + }); + + test("works through textboxes", async ({ adminReceiverStatusPage }) => { + const { valueDisplay } = adminReceiverStatusPage.filterFormInputs.dateRange; + await expect(adminReceiverStatusPage.receiverStatusRowsLocator).not.toHaveCount(0); + const now = new Date(); + const targetFrom = startOfDay(subDays(now, 3)); + const targetTo = addDays(endOfDay(now), 1); + + const reqUrl = await adminReceiverStatusPage.updateFilters({ + dateRange: { + value: [targetFrom, targetTo], + }, + }); + + expect(reqUrl).toBeDefined(); + + await expect(valueDisplay).toHaveText(adminReceiverStatusPage.expectedDateRangeLabelText); + expect(Object.fromEntries(reqUrl!.searchParams.entries())).toMatchObject({ + start_date: targetFrom.toISOString(), + end_date: targetTo.toISOString(), + }); + }); + }); test("receiver name", async ({ adminReceiverStatusPage }) => { const { organizationName, receiverName, successRate } = @@ -229,12 +217,12 @@ test.describe("Admin Receiver Status Page", () => { await expect(receiversStatusRows).toHaveCount(adminReceiverStatusPage.timePeriodData.length); }); - test("result message", async ({adminReceiverStatusPage}) => { + test("result message", async ({ adminReceiverStatusPage }) => { const result = await adminReceiverStatusPage.testReceiverMessage(); expect(result).toBe(true); }); - test("success type", async ({adminReceiverStatusPage}) => { + test("success type", async ({ adminReceiverStatusPage }) => { const [failRow, , mixedRow] = adminReceiverStatusPage.timePeriodData; const failRowTitle = adminReceiverStatusPage.getExpectedReceiverStatusRowTitle( failRow.organizationName, @@ -272,7 +260,7 @@ test.describe("Admin Receiver Status Page", () => { test.describe("receiver statuses", () => { test.describe("date range length changes", () => { - test("increases", async ({adminReceiverStatusPage}) => { + test("increases", async ({ adminReceiverStatusPage }) => { const rows = adminReceiverStatusPage.receiverStatusRowsLocator; const days = rows.nthCustom(0).days; await expect(rows).not.toHaveCount(0); @@ -287,7 +275,7 @@ test.describe("Admin Receiver Status Page", () => { await expect(days).toHaveCount(4); }); - test("decreases", async ({adminReceiverStatusPage}) => { + test("decreases", async ({ adminReceiverStatusPage }) => { const rows = adminReceiverStatusPage.receiverStatusRowsLocator; const days = rows.nthCustom(0).days; await expect(rows).not.toHaveCount(0); @@ -303,17 +291,17 @@ test.describe("Admin Receiver Status Page", () => { }); }); - test("time period modals", async ({adminReceiverStatusPage}) => { + test("time period modals", async ({ adminReceiverStatusPage }) => { const result = await adminReceiverStatusPage.testReceiverTimePeriodModals(); expect(result).toBe(true); }); - test("receiver org links", async ({adminReceiverStatusPage}) => { + test("receiver org links", async ({ adminReceiverStatusPage }) => { const result = await adminReceiverStatusPage.testReceiverOrgLinks(); expect(result).toBe(true); }); - test("receiver links", async ({adminReceiverStatusPage}) => { + test("receiver links", async ({ adminReceiverStatusPage }) => { const result = await adminReceiverStatusPage.testReceiverLinks(); expect(result).toBe(true); }); diff --git a/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts b/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts index a53fdbc0b7a..34486c7505d 100644 --- a/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts +++ b/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts @@ -1,7 +1,9 @@ /* eslint-disable playwright/no-networkidle */ import axios, { AxiosError } from "axios"; import * as fs from "fs"; -import { test as baseTest, expect } from "../../test"; +import { pageNotFound } from "../../../src/content/error/ErrorMessages"; +import { isAbsoluteURL, isAssetURL } from "../../helpers/utils"; +import { test as baseTest, Browser, chromium, expect } from "../../test"; const test = baseTest.extend({}); @@ -16,7 +18,6 @@ const test = baseTest.extend({}); test.describe("Evaluate links on public facing pages", { tag: "@warning" }, () => { let urlPaths: string[] = []; - const normalizeUrl = (href: string, baseUrl: string) => new URL(href, baseUrl).toString(); // Using our sitemap.xml, we'll create a pathnames array // We cannot use our POM, we must @@ -26,7 +27,7 @@ test.describe("Evaluate links on public facing pages", { tag: "@warning" }, () = const response = await page.goto("/sitemap.xml"); const sitemapXml = await response!.text(); // Since we don't want to use any external XML parsing libraries, - // we can use page.evaluate, but that creates it's own execution context + // we can use page.evaluate, but that creates its own execution context // wherein we need to explicitly return something, which is why // we have the convoluted // elem.textContent ? new URL(elem.textContent).pathname : null, @@ -43,22 +44,22 @@ test.describe("Evaluate links on public facing pages", { tag: "@warning" }, () = }); test("Check if paths were fetched", () => { - expect(urlPaths.length).toBeGreaterThan(0); + expect(urlPaths.length).toBeGreaterThan(0); // Ensure that paths were fetched correctly }); test("Check all public-facing URLs and their links for a valid 200 response", async ({ page, frontendWarningsLogPath, isFrontendWarningsLog, + baseURL, }) => { let aggregateHref = []; // Set test timeout to be 1 minute instead of 30 seconds - test.setTimeout(60000); + test.setTimeout(120000); for (const path of urlPaths) { await page.goto(path, { waitUntil: "networkidle", }); - const baseUrl = new URL(page.url()).origin; const allATags = await page.getByRole("link", { includeHidden: true }).elementHandles(); @@ -66,11 +67,12 @@ test.describe("Evaluate links on public facing pages", { tag: "@warning" }, () = const href = await aTag.getAttribute("href"); // ONLY include http, https and relative path names if (href && /^(https?:|\/)/.test(href)) { - aggregateHref.push(normalizeUrl(href, baseUrl)); + aggregateHref.push(href); } } } - // Remove any link duplicates to save resources + + // Remove duplicate links aggregateHref = [...new Set(aggregateHref)]; const axiosInstance = axios.create({ @@ -79,36 +81,81 @@ test.describe("Evaluate links on public facing pages", { tag: "@warning" }, () = const warnings: { url: string; message: string }[] = []; - const validateLink = async (url: string) => { + const validateLink = async (browser: Browser, url: string) => { + // Our app does not properly handle 200 vs 400 HTTP codes for our pages + // so we cannot simply use Axios since it's an HTTP client only. + // This means we must actually navigate to the page(s) with Playwright + // to then decipher the rendered HTML DOM content to then determine + // if the page is valid or not. isAbsoluteURL determines if the page + // is an internal link or external one by determining if it's an + // absolute URL or a relative URL. + + if (isAbsoluteURL(url) || isAssetURL(url)) { + try { + const normalizedURL = new URL(url, baseURL).toString(); + const response = await axiosInstance.get(normalizedURL); + return { url, status: response.status }; + } catch (error) { + const e = error as AxiosError; + warnings.push({ url, message: e.message }); + return { url, status: e.response ? e.response.status : 400 }; + } + } else { + // For internal relative URLs, use Playwright to navigate and check the page content + const context = await browser.newContext(); + const page = await context.newPage(); + + try { + const absoluteUrl = new URL(url, baseURL).toString(); + await page.goto(absoluteUrl, { waitUntil: "load" }); + + const pageContent = await page.content(); + const hasPageNotFoundText = pageContent.includes(pageNotFound); + const isErrorWrapperVisible = await page.locator('[data-testid="error-page-wrapper"]').isVisible(); + + if (hasPageNotFoundText && isErrorWrapperVisible) { + warnings.push({ url, message: "Internal link: Page not found" }); + return { url, status: 404 }; + } + + return { url, status: 200 }; + } catch (error) { + warnings.push({ url, message: "Internal link: Page error" }); + return { url, status: 400 }; + } finally { + await page.close(); + await context.close(); + } + } + }; + + const browser = await chromium.launch(); + + const results = []; + for (const href of aggregateHref) { try { - const response = await axiosInstance.get(url); - return { url, status: response.status }; + const result = await validateLink(browser, href); + results.push(result); } catch (error) { - const e = error as AxiosError; - console.error(`Error accessing ${url}:`, e.message); - const warning = { url: url, message: e.message }; - warnings.push(warning); - - return { - url, - status: e.response ? e.response.status : "Request failed", - }; + console.error(`Issue validating link: ${href}`, error); + results.push({ url: href, status: 500 }); } - }; + } - const results = await Promise.all(aggregateHref.map((href) => validateLink(href))); + await browser.close(); if (isFrontendWarningsLog && warnings.length > 0) { fs.writeFileSync(frontendWarningsLogPath, `${JSON.stringify(warnings)}\n`); } results.forEach((result) => { - try { - expect(result.status).toBe(200); - } catch (error) { - const e = error as AxiosError; - console.warn(`Non-fatal: ${e.message}`); + if (result.status !== 200) { + console.warn(`Warning: ${result.url} returned status ${result.status}`); } }); + + // Required expect statement + if somehow the warnings and number of links + // are the same, that's a huge problem. + expect(warnings.length).toBeLessThan(aggregateHref.length); }); }); diff --git a/frontend-react/package.json b/frontend-react/package.json index 74e016d89d4..47d201b9a26 100644 --- a/frontend-react/package.json +++ b/frontend-react/package.json @@ -25,6 +25,7 @@ "history": "^5.3.0", "html-to-text": "^9.0.5", "lodash": "^4.17.21", + "p-limit": "^6.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-helmet-async": "^2.0.5", @@ -72,7 +73,7 @@ "test:debug": "cross-env DEBUG_PRINT_LIMIT=100000 vitest --run --no-file-parallelism", "test:ci": "cross-env VITE_BACKEND_URL=http://localhost vitest --coverage", "test:ui": "cross-env vitest --ui", - "test:e2e": "playwright test", + "test:e2e": "playwright test --trace on", "test:e2e-smoke": "MOCK_DISABLED=true playwright test --project chromium --grep @smoke", "test:e2e-ui": "playwright test --ui", "test:e2e-ui:smoke": "MOCK_DISABLED=true playwright test --project chromium --grep @smoke --ui", diff --git a/frontend-react/playwright.config.ts b/frontend-react/playwright.config.ts index 428b8dbb7ef..2a57ce85c36 100644 --- a/frontend-react/playwright.config.ts +++ b/frontend-react/playwright.config.ts @@ -25,6 +25,7 @@ export default defineConfig({ // Tests sharded in CI runner and reported as blobs that are later turned into html report reporter: isCi ? [["blob", { outputDir: "e2e-data/report" }]] : [["html", { outputFolder: "e2e-data/report" }]], outputDir: "e2e-data/results", + timeout: 1000 * 180, use: { // keep playwright and browser timezones aligned. set preferably UTC by env var timezoneId: Intl.DateTimeFormat().resolvedOptions().timeZone, diff --git a/frontend-react/src/AppRouter.tsx b/frontend-react/src/AppRouter.tsx index 2a65608fb01..b6d41cd6487 100644 --- a/frontend-react/src/AppRouter.tsx +++ b/frontend-react/src/AppRouter.tsx @@ -9,172 +9,85 @@ import { PERMISSIONS } from "./utils/UsefulTypes"; /* Content Pages */ const Home = lazy(lazyRouteMarkdown(() => import("./content/home/index.mdx"))); -const About = lazy( - lazyRouteMarkdown(() => import("./content/about/index.mdx")), -); -const OurNetwork = lazy( - lazyRouteMarkdown(() => import("./content/about/our-network.mdx")), -); -const Roadmap = lazy( - lazyRouteMarkdown(() => import("./content/about/roadmap.mdx")), -); +const About = lazy(lazyRouteMarkdown(() => import("./content/about/index.mdx"))); +const OurNetwork = lazy(lazyRouteMarkdown(() => import("./content/about/our-network.mdx"))); +const Roadmap = lazy(lazyRouteMarkdown(() => import("./content/about/roadmap.mdx"))); const News = lazy(lazyRouteMarkdown(() => import("./content/about/news.mdx"))); -const Security = lazy( - lazyRouteMarkdown(() => import("./content/about/security.mdx")), -); -const ReleaseNotes = lazy( - lazyRouteMarkdown(() => import("./content/about/release-notes.mdx")), -); -const CaseStudies = lazy( - lazyRouteMarkdown(() => import("./content/about/case-studies.mdx")), -); +const Security = lazy(lazyRouteMarkdown(() => import("./content/about/security.mdx"))); +const ReleaseNotes = lazy(lazyRouteMarkdown(() => import("./content/about/release-notes.mdx"))); +const CaseStudies = lazy(lazyRouteMarkdown(() => import("./content/about/case-studies.mdx"))); const ReferHealthcareOrganizations = lazy( - lazyRouteMarkdown( - () => - import( - "./content/managing-your-connection/refer-healthcare-organizations.mdx" - ), - ), + lazyRouteMarkdown(() => import("./content/managing-your-connection/refer-healthcare-organizations.mdx")), ); -const GettingStartedSendingData = lazy( - lazyRouteMarkdown( - () => import("./content/getting-started/sending-data.mdx"), - ), -); +const GettingStartedSendingData = lazy(lazyRouteMarkdown(() => import("./content/getting-started/sending-data.mdx"))); const GettingStartedReceivingData = lazy( - lazyRouteMarkdown( - () => import("./content/getting-started/receiving-data.mdx"), - ), + lazyRouteMarkdown(() => import("./content/getting-started/receiving-data.mdx")), ); const ReportStreamApiIndex = lazy( - lazyRouteMarkdown( - () => - import( - "./content/developer-resources/reportstream-api/ReportStreamApi.mdx" - ), - ), -); -const DeveloperResourcesIndex = lazy( - lazyRouteMarkdown( - () => import("./content/developer-resources/index-page.mdx"), - ), + lazyRouteMarkdown(() => import("./content/developer-resources/reportstream-api/ReportStreamApi.mdx")), ); +const DeveloperResourcesIndex = lazy(lazyRouteMarkdown(() => import("./content/developer-resources/index-page.mdx"))); const ReportStreamApiGettingStarted = lazy( lazyRouteMarkdown( - () => - import( - "./content/developer-resources/reportstream-api/getting-started/GettingStarted.mdx" - ), + () => import("./content/developer-resources/reportstream-api/getting-started/GettingStarted.mdx"), ), ); const ReportStreamApiDocumentation = lazy( - lazyRouteMarkdown( - () => - import( - "./content/developer-resources/reportstream-api/documentation/Documentation.mdx" - ), - ), + lazyRouteMarkdown(() => import("./content/developer-resources/reportstream-api/documentation/Documentation.mdx")), ); const ReportStreamApiDocumentationResponses = lazy( lazyRouteMarkdown( - () => - import( - "./content/developer-resources/reportstream-api/documentation/ResponsesFromReportStream.mdx" - ), + () => import("./content/developer-resources/reportstream-api/documentation/ResponsesFromReportStream.mdx"), ), ); const ManagingYourConnectionIndex = lazy( - lazyRouteMarkdown( - () => import("./content/managing-your-connection/index.mdx"), - ), -); -const SupportIndex = lazy( - lazyRouteMarkdown(() => import("./content/support/index.mdx")), + lazyRouteMarkdown(() => import("./content/managing-your-connection/index.mdx")), ); +const SupportIndex = lazy(lazyRouteMarkdown(() => import("./content/support/index.mdx"))); const ReportStreamApiDocumentationPayloads = lazy( lazyRouteMarkdown( - () => - import( - "./content/developer-resources/reportstream-api/documentation/SamplePayloadsAndOutput.mdx" - ), + () => import("./content/developer-resources/reportstream-api/documentation/SamplePayloadsAndOutput.mdx"), ), ); /* Public Pages */ const TermsOfService = lazy(() => import("./pages/TermsOfService")); -const LoginCallback = lazy( - () => import("./shared/LoginCallback/LoginCallback"), -); -const LogoutCallback = lazy( - () => import("./shared/LogoutCallback/LogoutCallback"), -); +const LoginCallback = lazy(() => import("./shared/LoginCallback/LoginCallback")); +const LogoutCallback = lazy(() => import("./shared/LogoutCallback/LogoutCallback")); const Login = lazy(() => import("./pages/Login")); -const ErrorNoPage = lazy( - () => import("./pages/error/legacy-content/ErrorNoPage"), -); +const ErrorNoPage = lazy(() => import("./pages/error/legacy-content/ErrorNoPage")); /* Auth Pages */ const FeatureFlagsPage = lazy(() => import("./pages/misc/FeatureFlags")); -const SubmissionDetailsPage = lazy( - () => import("./pages/submissions/SubmissionDetails"), -); +const SubmissionDetailsPage = lazy(() => import("./pages/submissions/SubmissionDetails")); const SubmissionsPage = lazy(() => import("./pages/submissions/Submissions")); const AdminMainPage = lazy(() => import("./pages/admin/AdminMain")); const AdminOrgNewPage = lazy(() => import("./pages/admin/AdminOrgNew")); const AdminOrgEditPage = lazy(() => import("./pages/admin/AdminOrgEdit")); -const EditSenderSettingsPage = lazy( - () => import("./components/Admin/EditSenderSettings"), -); +const EditSenderSettingsPage = lazy(() => import("./components/Admin/EditSenderSettings")); const AdminLMFPage = lazy(() => import("./pages/admin/AdminLastMileFailures")); -const AdminMessageTrackerPage = lazy( - () => import("./pages/admin/AdminMessageTracker"), -); +const AdminMessageTrackerPage = lazy(() => import("./pages/admin/AdminMessageTracker")); const AdminReceiverDashPage = lazy( - () => - import( - "./pages/admin/receiver-dashboard/AdminReceiverDashboardPage/AdminReceiverDashboardPage" - ), -); -const DeliveryDetailPage = lazy( - () => import("./pages/deliveries/details/DeliveryDetail"), -); -const ValueSetsDetailPage = lazy( - () => import("./pages/admin/value-set-editor/ValueSetsDetail"), -); -const ValueSetsIndexPage = lazy( - () => import("./pages/admin/value-set-editor/ValueSetsIndex"), + () => import("./pages/admin/receiver-dashboard/AdminReceiverDashboardPage/AdminReceiverDashboardPage"), ); +const DeliveryDetailPage = lazy(() => import("./pages/deliveries/details/DeliveryDetail")); +const ValueSetsDetailPage = lazy(() => import("./pages/admin/value-set-editor/ValueSetsDetail")); +const ValueSetsIndexPage = lazy(() => import("./pages/admin/value-set-editor/ValueSetsIndex")); const DeliveriesPage = lazy(() => import("./pages/deliveries/Deliveries")); -const EditReceiverSettingsPage = lazy( - () => import("./components/Admin/EditReceiverSettings"), -); +const EditReceiverSettingsPage = lazy(() => import("./components/Admin/EditReceiverSettings")); const AdminRevHistoryPage = lazy(() => import("./pages/admin/AdminRevHistory")); -const MessageDetailsPage = lazy( - () => import("./components/MessageTracker/MessageDetails"), -); -const ManagePublicKeyPage = lazy( - () => import("./components/ManagePublicKey/ManagePublicKey"), -); -const DataDashboardPage = lazy( - () => import("./pages/data-dashboard/DataDashboard"), -); -const ReportDetailsPage = lazy( - () => import("./components/DataDashboard/ReportDetails/ReportDetails"), -); +const MessageDetailsPage = lazy(() => import("./components/MessageTracker/MessageDetails")); +const ManagePublicKeyPage = lazy(() => import("./components/ManagePublicKey/ManagePublicKey")); +const DataDashboardPage = lazy(() => import("./pages/data-dashboard/DataDashboard")); +const ReportDetailsPage = lazy(() => import("./components/DataDashboard/ReportDetails/ReportDetails")); const FacilitiesProvidersPage = lazy( - () => - import( - "./components/DataDashboard/FacilitiesProviders/FacilitiesProviders" - ), + () => import("./components/DataDashboard/FacilitiesProviders/FacilitiesProviders"), ); const FacilityProviderSubmitterDetailsPage = lazy( - () => - import( - "./components/DataDashboard/FacilityProviderSubmitterDetails/FacilityProviderSubmitterDetails" - ), + () => import("./components/DataDashboard/FacilityProviderSubmitterDetails/FacilityProviderSubmitterDetails"), ); const NewSettingPage = lazy(() => import("./components/Admin/NewSetting")); @@ -343,9 +256,7 @@ export const appRoutes: RouteObject[] = [ children: [ { path: "", - element: ( - - ), + element: , index: true, handle: { isContentPage: true, @@ -353,18 +264,14 @@ export const appRoutes: RouteObject[] = [ }, { path: "responses-from-reportstream", - element: ( - - ), + element: , handle: { isContentPage: true, }, }, { path: "sample-payloads-and-output", - element: ( - - ), + element: , handle: { isContentPage: true, }, @@ -491,27 +398,15 @@ export const appRoutes: RouteObject[] = [ }, { path: "facility/:senderId", - element: ( - - ), + element: , }, { path: "provider/:senderId", - element: ( - - ), + element: , }, { path: "submitter/:senderId", - element: ( - - ), + element: , }, ], }, diff --git a/frontend-react/src/content/error/ErrorMessages.ts b/frontend-react/src/content/error/ErrorMessages.ts index b6ebe5ea076..4e0e5586e05 100644 --- a/frontend-react/src/content/error/ErrorMessages.ts +++ b/frontend-react/src/content/error/ErrorMessages.ts @@ -11,8 +11,7 @@ export interface ParagraphWithTitle { export type ErrorDisplayMessage = ParagraphWithTitle | string; /** Default message for an error */ -export const GENERIC_ERROR_STRING = - "Our apologies, there was an error loading this content."; +export const GENERIC_ERROR_STRING = "Our apologies, there was an error loading this content."; /** Default content for an error page */ export const GENERIC_ERROR_PAGE_CONFIG: ErrorDisplayMessage = { header: "An error has occurred", @@ -21,3 +20,5 @@ export const GENERIC_ERROR_PAGE_CONFIG: ErrorDisplayMessage = { have been automatically notified and will be looking into this with the utmost urgency.`, }; + +export const pageNotFound = "Page not found"; diff --git a/frontend-react/src/content/home/index.mdx b/frontend-react/src/content/home/index.mdx index 5d196b9e14c..c17cecde963 100644 --- a/frontend-react/src/content/home/index.mdx +++ b/frontend-react/src/content/home/index.mdx @@ -25,7 +25,7 @@ import site from "../site.json";
-
+

How we help you

diff --git a/frontend-react/src/pages/error/legacy-content/ErrorNoPage.tsx b/frontend-react/src/pages/error/legacy-content/ErrorNoPage.tsx index a076f75d3f4..aceb33d7c51 100644 --- a/frontend-react/src/pages/error/legacy-content/ErrorNoPage.tsx +++ b/frontend-react/src/pages/error/legacy-content/ErrorNoPage.tsx @@ -2,6 +2,7 @@ import { Button } from "@trussworks/react-uswds"; import { Helmet } from "react-helmet-async"; import { useNavigate } from "react-router-dom"; +import { pageNotFound } from "../../../content/error/ErrorMessages"; import site from "../../../content/site.json"; export const ErrorNoPage = () => { @@ -9,42 +10,31 @@ export const ErrorNoPage = () => { return ( <> - Page Not Found | {import.meta.env.VITE_TITLE} + + {pageNotFound} | {import.meta.env.VITE_TITLE} + -
+
-

Page not found

+

{pageNotFound}

- We’re sorry, we can’t find the page you're - looking for. It might have been removed, changed - names, or is otherwise unavailable. + We’re sorry, we can’t find the page you're looking for. It might have been removed, + changed names, or is otherwise unavailable.

- If you typed the URL directly, check your - spelling and capitalization. Our URLs look like - this:{" "} - - reportstream.cdc.gov/example-one - - . + If you typed the URL directly, check your spelling and capitalization. Our URLs look + like this: reportstream.cdc.gov/example-one.

- Visit our homepage or contact us at{" "} - {site.orgs.RS.email} and we’ll point you in the + Visit our homepage or contact us at {site.orgs.RS.email} and we’ll point you in the right direction.{" "}

  • -
  • @@ -52,11 +42,7 @@ export const ErrorNoPage = () => { diff --git a/frontend-react/yarn.lock b/frontend-react/yarn.lock index 262a13d07be..c22b4708f09 100644 --- a/frontend-react/yarn.lock +++ b/frontend-react/yarn.lock @@ -11429,6 +11429,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:^6.1.0": + version: 6.1.0 + resolution: "p-limit@npm:6.1.0" + dependencies: + yocto-queue: ^1.1.1 + checksum: 0c98d8fc1006b70fc7423232a47e8d026dc69279b06fe7ff8b4c0cc8023de2b6bb8991b609d93c3dec691a7a362ab0f0157df521d931a01fec192a5e404b9ee5 + languageName: node + linkType: hard + "p-locate@npm:^3.0.0": version: 3.0.0 resolution: "p-locate@npm:3.0.0" @@ -12268,6 +12277,7 @@ __metadata: msw-storybook-addon: beta npm-run-all: ^4.1.5 otpauth: ^9.3.2 + p-limit: ^6.1.0 patch-package: ^8.0.0 postcss: ^8.4.45 prettier: ^3.3.3 @@ -15612,6 +15622,13 @@ __metadata: languageName: node linkType: hard +"yocto-queue@npm:^1.1.1": + version: 1.1.1 + resolution: "yocto-queue@npm:1.1.1" + checksum: f2e05b767ed3141e6372a80af9caa4715d60969227f38b1a4370d60bffe153c9c5b33a862905609afc9b375ec57cd40999810d20e5e10229a204e8bde7ef255c + languageName: node + linkType: hard + "zwitch@npm:^2.0.0": version: 2.0.2 resolution: "zwitch@npm:2.0.2"