From 02d99c055734edb3d705044de669f7856c0a8fe7 Mon Sep 17 00:00:00 2001 From: doug-s-nava <92806979+doug-s-nava@users.noreply.github.com> Date: Mon, 16 Dec 2024 12:18:12 -0500 Subject: [PATCH] [Issue #3078] clean up translation related code (#3089) * replaces use of "unstable_setRequestLocale" with "setRequestLocale" and passes in dynamic locales * includes a couple of quality of life improvements related to translation code * translation should now work if we provide Spanish content --- frontend/next.config.js | 2 +- .../src/app/[locale]/[...not-found]/page.tsx | 7 +++-- frontend/src/app/[locale]/layout.tsx | 4 +-- frontend/src/app/[locale]/not-found.tsx | 6 ++-- .../app/[locale]/opportunity/[id]/page.tsx | 11 ++++++-- frontend/src/app/[locale]/page.tsx | 13 +++++---- frontend/src/app/[locale]/process/page.tsx | 13 +++++---- frontend/src/app/[locale]/research/page.tsx | 13 +++++---- frontend/src/app/[locale]/search/layout.tsx | 8 ++++-- frontend/src/app/[locale]/search/page.tsx | 19 ++++++++----- .../[locale]/subscribe/confirmation/page.tsx | 15 ++++++---- frontend/src/app/[locale]/subscribe/page.tsx | 28 +++++++------------ .../[locale]/subscribe/unsubscribe/page.tsx | 15 ++++++---- frontend/src/components/Layout.tsx | 4 +-- frontend/src/i18n/config.ts | 4 +-- frontend/src/i18n/getMessagesWithFallbacks.ts | 18 +++--------- frontend/src/i18n/{server.ts => request.ts} | 17 ++++++++--- frontend/src/types/intl.ts | 2 ++ .../components/search/SearchFilters.test.tsx | 2 +- .../components/search/SearchResults.test.tsx | 2 +- frontend/tests/pages/page.test.tsx | 6 ++-- frontend/tests/pages/process/page.test.tsx | 6 ++-- frontend/tests/pages/research/page.test.tsx | 6 ++-- frontend/tests/pages/search/page.test.tsx | 27 +++++++++++++++--- frontend/tests/pages/subscribe/page.test.tsx | 28 +++++++++++++------ .../subscribe/subscribeEmailAction.test.tsx | 2 +- frontend/tests/react-utils.tsx | 3 ++ 27 files changed, 171 insertions(+), 110 deletions(-) rename frontend/src/i18n/{server.ts => request.ts} (51%) diff --git a/frontend/next.config.js b/frontend/next.config.js index 9a4247e1b..a2dc01e3e 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -1,6 +1,6 @@ // @ts-check -const withNextIntl = require("next-intl/plugin")("./src/i18n/server.ts"); +const withNextIntl = require("next-intl/plugin")(); const sassOptions = require("./scripts/sassOptions"); const nrExternals = require("@newrelic/next/load-externals"); diff --git a/frontend/src/app/[locale]/[...not-found]/page.tsx b/frontend/src/app/[locale]/[...not-found]/page.tsx index 7104ce112..a159ad189 100644 --- a/frontend/src/app/[locale]/[...not-found]/page.tsx +++ b/frontend/src/app/[locale]/[...not-found]/page.tsx @@ -1,10 +1,13 @@ import { Metadata } from "next"; +import { LocalizedPageProps } from "src/types/intl"; import { getTranslations } from "next-intl/server"; import { notFound } from "next/navigation"; -export async function generateMetadata() { - const t = await getTranslations({ locale: "en" }); +export async function generateMetadata({ + params: { locale }, +}: LocalizedPageProps) { + const t = await getTranslations({ locale }); const meta: Metadata = { title: t("ErrorPages.page_not_found.title"), description: t("Index.meta_description"), diff --git a/frontend/src/app/[locale]/layout.tsx b/frontend/src/app/[locale]/layout.tsx index 847137623..6c3c779fa 100644 --- a/frontend/src/app/[locale]/layout.tsx +++ b/frontend/src/app/[locale]/layout.tsx @@ -12,7 +12,7 @@ import Script from "next/script"; import "src/styles/styles.scss"; import { NextIntlClientProvider } from "next-intl"; -import { getMessages, unstable_setRequestLocale } from "next-intl/server"; +import { getMessages, setRequestLocale } from "next-intl/server"; import Layout from "src/components/Layout"; @@ -61,7 +61,7 @@ export default async function LocaleLayout({ children, params }: Props) { const { locale } = params; // Enable static rendering - unstable_setRequestLocale(locale); + setRequestLocale(locale); // Providing all messages to the client // side is the easiest way to get started diff --git a/frontend/src/app/[locale]/not-found.tsx b/frontend/src/app/[locale]/not-found.tsx index 2498b9845..7831c241e 100644 --- a/frontend/src/app/[locale]/not-found.tsx +++ b/frontend/src/app/[locale]/not-found.tsx @@ -1,14 +1,14 @@ import { Metadata } from "next"; import { useTranslations } from "next-intl"; -import { getTranslations, unstable_setRequestLocale } from "next-intl/server"; +import { getTranslations } from "next-intl/server"; import Link from "next/link"; import { GridContainer } from "@trussworks/react-uswds"; import BetaAlert from "src/components/BetaAlert"; export async function generateMetadata() { - const t = await getTranslations({ locale: "en" }); + const t = await getTranslations(); const meta: Metadata = { title: t("ErrorPages.page_not_found.title"), description: t("Index.meta_description"), @@ -16,8 +16,8 @@ export async function generateMetadata() { return meta; } +// note that NotFound pages do not take props so cannot be translated export default function NotFound() { - unstable_setRequestLocale("en"); const t = useTranslations("ErrorPages.page_not_found"); return ( diff --git a/frontend/src/app/[locale]/opportunity/[id]/page.tsx b/frontend/src/app/[locale]/opportunity/[id]/page.tsx index 64ebdd551..4ed492676 100644 --- a/frontend/src/app/[locale]/opportunity/[id]/page.tsx +++ b/frontend/src/app/[locale]/opportunity/[id]/page.tsx @@ -29,12 +29,17 @@ type OpportunityListingProps = { export const revalidate = 600; // invalidate ten minutes export const dynamic = "force-dynamic"; -export async function generateMetadata({ params }: { params: { id: string } }) { - const t = await getTranslations({ locale: "en" }); +export async function generateMetadata({ + params, +}: { + params: { id: string; locale: string }; +}) { + const { id, locale } = params; + const t = await getTranslations({ locale }); let title = `${t("OpportunityListing.page_title")}`; try { const { data: opportunityData } = await fetchOpportunity({ - subPath: params.id, + subPath: id, }); title = `${t("OpportunityListing.page_title")} - ${opportunityData.opportunity_title}`; } catch (error) { diff --git a/frontend/src/app/[locale]/page.tsx b/frontend/src/app/[locale]/page.tsx index cf9b22804..c722750a1 100644 --- a/frontend/src/app/[locale]/page.tsx +++ b/frontend/src/app/[locale]/page.tsx @@ -1,14 +1,17 @@ import { Metadata } from "next"; +import { LocalizedPageProps } from "src/types/intl"; -import { getTranslations, unstable_setRequestLocale } from "next-intl/server"; +import { getTranslations, setRequestLocale } from "next-intl/server"; import BetaAlert from "src/components/BetaAlert"; import IndexGoalContent from "src/components/content/IndexGoalContent"; import ProcessAndResearchContent from "src/components/content/ProcessAndResearchContent"; import Hero from "src/components/Hero"; -export async function generateMetadata() { - const t = await getTranslations({ locale: "en" }); +export async function generateMetadata({ + params: { locale }, +}: LocalizedPageProps) { + const t = await getTranslations({ locale }); const meta: Metadata = { title: t("Index.page_title"), description: t("Index.meta_description"), @@ -16,8 +19,8 @@ export async function generateMetadata() { return meta; } -export default function Home() { - unstable_setRequestLocale("en"); +export default function Home({ params: { locale } }: LocalizedPageProps) { + setRequestLocale(locale); return ( <> diff --git a/frontend/src/app/[locale]/process/page.tsx b/frontend/src/app/[locale]/process/page.tsx index 71dee5220..b37862d1c 100644 --- a/frontend/src/app/[locale]/process/page.tsx +++ b/frontend/src/app/[locale]/process/page.tsx @@ -3,14 +3,17 @@ import ProcessIntro from "src/app/[locale]/process/ProcessIntro"; import ProcessInvolved from "src/app/[locale]/process/ProcessInvolved"; import ProcessMilestones from "src/app/[locale]/process/ProcessMilestones"; import { PROCESS_CRUMBS } from "src/constants/breadcrumbs"; +import { LocalizedPageProps } from "src/types/intl"; -import { getTranslations, unstable_setRequestLocale } from "next-intl/server"; +import { getTranslations, setRequestLocale } from "next-intl/server"; import BetaAlert from "src/components/BetaAlert"; import Breadcrumbs from "src/components/Breadcrumbs"; -export async function generateMetadata() { - const t = await getTranslations({ locale: "en" }); +export async function generateMetadata({ + params: { locale }, +}: LocalizedPageProps) { + const t = await getTranslations({ locale }); const meta: Metadata = { title: t("Process.page_title"), description: t("Process.meta_description"), @@ -18,8 +21,8 @@ export async function generateMetadata() { return meta; } -export default function Process() { - unstable_setRequestLocale("en"); +export default function Process({ params: { locale } }: LocalizedPageProps) { + setRequestLocale(locale); return ( <> diff --git a/frontend/src/app/[locale]/research/page.tsx b/frontend/src/app/[locale]/research/page.tsx index e57c7bd89..92f24c4e9 100644 --- a/frontend/src/app/[locale]/research/page.tsx +++ b/frontend/src/app/[locale]/research/page.tsx @@ -5,14 +5,17 @@ import ResearchIntro from "src/app/[locale]/research/ResearchIntro"; import ResearchMethodology from "src/app/[locale]/research/ResearchMethodology"; import ResearchThemes from "src/app/[locale]/research/ResearchThemes"; import { RESEARCH_CRUMBS } from "src/constants/breadcrumbs"; +import { LocalizedPageProps } from "src/types/intl"; -import { getTranslations, unstable_setRequestLocale } from "next-intl/server"; +import { getTranslations, setRequestLocale } from "next-intl/server"; import BetaAlert from "src/components/BetaAlert"; import Breadcrumbs from "src/components/Breadcrumbs"; -export async function generateMetadata() { - const t = await getTranslations({ locale: "en" }); +export async function generateMetadata({ + params: { locale }, +}: LocalizedPageProps) { + const t = await getTranslations({ locale }); const meta: Metadata = { title: t("Research.page_title"), description: t("Research.meta_description"), @@ -20,8 +23,8 @@ export async function generateMetadata() { return meta; } -export default function Research() { - unstable_setRequestLocale("en"); +export default function Research({ params: { locale } }: LocalizedPageProps) { + setRequestLocale(locale); return ( <> diff --git a/frontend/src/app/[locale]/search/layout.tsx b/frontend/src/app/[locale]/search/layout.tsx index 0292e9413..74ff132a8 100644 --- a/frontend/src/app/[locale]/search/layout.tsx +++ b/frontend/src/app/[locale]/search/layout.tsx @@ -1,6 +1,6 @@ import { SEARCH_CRUMBS } from "src/constants/breadcrumbs"; -import { unstable_setRequestLocale } from "next-intl/server"; +import { setRequestLocale } from "next-intl/server"; import BetaAlert from "src/components/BetaAlert"; import Breadcrumbs from "src/components/Breadcrumbs"; @@ -8,10 +8,14 @@ import SearchCallToAction from "src/components/search/SearchCallToAction"; export default function SearchLayout({ children, + params: { locale }, }: { children: React.ReactNode; + params: { + locale: string; + }; }) { - unstable_setRequestLocale("en"); + setRequestLocale(locale); return ( <> diff --git a/frontend/src/app/[locale]/search/page.tsx b/frontend/src/app/[locale]/search/page.tsx index a671436db..d006a905b 100644 --- a/frontend/src/app/[locale]/search/page.tsx +++ b/frontend/src/app/[locale]/search/page.tsx @@ -1,12 +1,13 @@ import { Metadata } from "next"; import QueryProvider from "src/app/[locale]/search/QueryProvider"; import withFeatureFlag from "src/hoc/search/withFeatureFlag"; +import { LocalizedPageProps } from "src/types/intl"; import { SearchParamsTypes } from "src/types/search/searchRequestTypes"; import { Breakpoints } from "src/types/uiTypes"; import { convertSearchParamsToProperTypes } from "src/utils/search/convertSearchParamsToProperTypes"; import { useTranslations } from "next-intl"; -import { getTranslations, unstable_setRequestLocale } from "next-intl/server"; +import { getTranslations, setRequestLocale } from "next-intl/server"; import { redirect } from "next/navigation"; import ContentDisplayToggle from "src/components/ContentDisplayToggle"; @@ -15,19 +16,23 @@ import SearchBar from "src/components/search/SearchBar"; import SearchFilters from "src/components/search/SearchFilters"; import SearchResults from "src/components/search/SearchResults"; -export async function generateMetadata() { - const t = await getTranslations({ locale: "en" }); +export async function generateMetadata({ + params: { locale }, +}: LocalizedPageProps) { + const t = await getTranslations({ locale }); const meta: Metadata = { title: t("Search.title"), description: t("Index.meta_description"), }; return meta; } +type SearchPageProps = { + searchParams: SearchParamsTypes; + params: { locale: string }; +}; -type SearchPageProps = { searchParams: SearchParamsTypes }; - -function Search({ searchParams }: SearchPageProps) { - unstable_setRequestLocale("en"); +function Search({ searchParams, params: { locale } }: SearchPageProps) { + setRequestLocale(locale); const t = useTranslations("Search"); const convertedSearchParams = convertSearchParamsToProperTypes(searchParams); diff --git a/frontend/src/app/[locale]/subscribe/confirmation/page.tsx b/frontend/src/app/[locale]/subscribe/confirmation/page.tsx index 2f7178ebf..1b6c7b779 100644 --- a/frontend/src/app/[locale]/subscribe/confirmation/page.tsx +++ b/frontend/src/app/[locale]/subscribe/confirmation/page.tsx @@ -1,16 +1,19 @@ import { Metadata } from "next"; import { SUBSCRIBE_CONFIRMATION_CRUMBS } from "src/constants/breadcrumbs"; +import { LocalizedPageProps } from "src/types/intl"; import { useTranslations } from "next-intl"; -import { getTranslations, unstable_setRequestLocale } from "next-intl/server"; +import { getTranslations, setRequestLocale } from "next-intl/server"; import Link from "next/link"; import { Grid, GridContainer } from "@trussworks/react-uswds"; import BetaAlert from "src/components/BetaAlert"; import Breadcrumbs from "src/components/Breadcrumbs"; -export async function generateMetadata() { - const t = await getTranslations({ locale: "en" }); +export async function generateMetadata({ + params: { locale }, +}: LocalizedPageProps) { + const t = await getTranslations({ locale }); const meta: Metadata = { title: t("Subscribe.page_title"), description: t("Index.meta_description"), @@ -19,8 +22,10 @@ export async function generateMetadata() { return meta; } -export default function SubscriptionConfirmation() { - unstable_setRequestLocale("en"); +export default function SubscriptionConfirmation({ + params: { locale }, +}: LocalizedPageProps) { + setRequestLocale(locale); const t = useTranslations("Subscription_confirmation"); return ( diff --git a/frontend/src/app/[locale]/subscribe/page.tsx b/frontend/src/app/[locale]/subscribe/page.tsx index 688eed4fe..542a7fcb0 100644 --- a/frontend/src/app/[locale]/subscribe/page.tsx +++ b/frontend/src/app/[locale]/subscribe/page.tsx @@ -1,21 +1,19 @@ -import pick from "lodash/pick"; import { Metadata } from "next"; import { SUBSCRIBE_CRUMBS } from "src/constants/breadcrumbs"; +import { LocalizedPageProps } from "src/types/intl"; -import { - NextIntlClientProvider, - useMessages, - useTranslations, -} from "next-intl"; -import { getTranslations, unstable_setRequestLocale } from "next-intl/server"; +import { useTranslations } from "next-intl"; +import { getTranslations, setRequestLocale } from "next-intl/server"; import { Grid, GridContainer } from "@trussworks/react-uswds"; import BetaAlert from "src/components/BetaAlert"; import Breadcrumbs from "src/components/Breadcrumbs"; import SubscriptionForm from "src/components/subscribe/SubscriptionForm"; -export async function generateMetadata() { - const t = await getTranslations({ locale: "en" }); +export async function generateMetadata({ + params: { locale }, +}: LocalizedPageProps) { + const t = await getTranslations({ locale }); const meta: Metadata = { title: t("Subscribe.page_title"), description: t("Index.meta_description"), @@ -24,10 +22,9 @@ export async function generateMetadata() { return meta; } -export default function Subscribe() { - unstable_setRequestLocale("en"); +export default function Subscribe({ params: { locale } }: LocalizedPageProps) { + setRequestLocale(locale); const t = useTranslations("Subscribe"); - const messages = useMessages(); return ( <> @@ -54,12 +51,7 @@ export default function Subscribe() { })} - - - + diff --git a/frontend/src/app/[locale]/subscribe/unsubscribe/page.tsx b/frontend/src/app/[locale]/subscribe/unsubscribe/page.tsx index 9d14df08b..3fd60301e 100644 --- a/frontend/src/app/[locale]/subscribe/unsubscribe/page.tsx +++ b/frontend/src/app/[locale]/subscribe/unsubscribe/page.tsx @@ -1,16 +1,19 @@ import { Metadata } from "next"; import { UNSUBSCRIBE_CRUMBS } from "src/constants/breadcrumbs"; +import { LocalizedPageProps } from "src/types/intl"; import { useTranslations } from "next-intl"; -import { getTranslations, unstable_setRequestLocale } from "next-intl/server"; +import { getTranslations, setRequestLocale } from "next-intl/server"; import Link from "next/link"; import { Grid, GridContainer } from "@trussworks/react-uswds"; import BetaAlert from "src/components/BetaAlert"; import Breadcrumbs from "src/components/Breadcrumbs"; -export async function generateMetadata() { - const t = await getTranslations({ locale: "en" }); +export async function generateMetadata({ + params: { locale }, +}: LocalizedPageProps) { + const t = await getTranslations({ locale }); const meta: Metadata = { title: t("Subscribe.page_title"), description: t("Index.meta_description"), @@ -19,8 +22,10 @@ export async function generateMetadata() { return meta; } -export default function Unsubscribe() { - unstable_setRequestLocale("en"); +export default function Unsubscribe({ + params: { locale }, +}: LocalizedPageProps) { + setRequestLocale(locale); const t = useTranslations("Unsubscription_confirmation"); return ( diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 122dbb87b..0c510f49e 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -5,7 +5,7 @@ import { useMessages, useTranslations, } from "next-intl"; -import { unstable_setRequestLocale } from "next-intl/server"; +import { setRequestLocale } from "next-intl/server"; import Footer from "./Footer"; import GrantsIdentifier from "./GrantsIdentifier"; @@ -17,7 +17,7 @@ type Props = { }; export default function Layout({ children, locale }: Props) { - unstable_setRequestLocale(locale); + setRequestLocale(locale); const t = useTranslations(); const messages = useMessages(); diff --git a/frontend/src/i18n/config.ts b/frontend/src/i18n/config.ts index c7762c889..31eb5f7b2 100644 --- a/frontend/src/i18n/config.ts +++ b/frontend/src/i18n/config.ts @@ -11,9 +11,9 @@ type RequestConfig = Awaited< * List of languages supported by the application. Other tools (Storybook, tests) reference this. * These must be BCP47 language tags: https://en.wikipedia.org/wiki/IETF_language_tag#List_of_common_primary_language_subtags */ -export const locales = ["en", "es"] as const; +export const locales = ["en", "es"]; export type Locale = (typeof locales)[number]; -export const defaultLocale: Locale = "en"; +export const defaultLocale = "en"; /** * Specifying a time zone affects the rendering of dates and times. diff --git a/frontend/src/i18n/getMessagesWithFallbacks.ts b/frontend/src/i18n/getMessagesWithFallbacks.ts index a9c15c5cf..571f6351c 100644 --- a/frontend/src/i18n/getMessagesWithFallbacks.ts +++ b/frontend/src/i18n/getMessagesWithFallbacks.ts @@ -1,5 +1,5 @@ import { merge } from "lodash"; -import { defaultLocale, Locale, locales } from "src/i18n/config"; +import { defaultLocale, Locale } from "src/i18n/config"; interface LocaleFile { messages: Messages; @@ -15,21 +15,11 @@ async function importMessages(locale: Locale) { * from the current locale, the missing key will fallback to the default locale */ export async function getMessagesWithFallbacks( - requestedLocale: string = defaultLocale, + requestedLocale: Locale = defaultLocale, ) { - const isValidLocale = locales.includes(requestedLocale as Locale); // https://github.com/microsoft/TypeScript/issues/26255 - if (!isValidLocale) { - console.error( - "Unsupported locale was requested. Falling back to the default locale.", - { locale: requestedLocale, defaultLocale }, - ); - requestedLocale = defaultLocale; - } - - const targetLocale = requestedLocale as Locale; - let messages = await importMessages(targetLocale); + let messages = await importMessages(requestedLocale); - if (targetLocale !== defaultLocale) { + if (requestedLocale !== defaultLocale) { const fallbackMessages = await importMessages(defaultLocale); messages = merge({}, fallbackMessages, messages); } diff --git a/frontend/src/i18n/server.ts b/frontend/src/i18n/request.ts similarity index 51% rename from frontend/src/i18n/server.ts rename to frontend/src/i18n/request.ts index f50df8699..dc62138bb 100644 --- a/frontend/src/i18n/server.ts +++ b/frontend/src/i18n/request.ts @@ -1,7 +1,7 @@ -import { getRequestConfig } from "next-intl/server"; +import { defaultLocale, formats, locales, timeZone } from "src/i18n/config"; +import { getMessagesWithFallbacks } from "src/i18n/getMessagesWithFallbacks"; -import { formats, timeZone } from "./config"; -import { getMessagesWithFallbacks } from "./getMessagesWithFallbacks"; +import { getRequestConfig } from "next-intl/server"; /** * Make locale messages available to all server components. @@ -10,7 +10,16 @@ import { getMessagesWithFallbacks } from "./getMessagesWithFallbacks"; */ // @ts-expect-error TS2345: Argument of type error is expected behavior by next-intl maintainer: https://github.com/amannn/next-intl/issues/991#issuecomment-2050087509 -export default getRequestConfig(async ({ locale }) => { +export default getRequestConfig(async ({ requestLocale }) => { + let locale = (await requestLocale) || ""; + + const isValidLocale = locales.includes(locale); // https://github.com/microsoft/TypeScript/issues/26255 + if (!isValidLocale) { + console.error( + `Unsupported locale (${locale}) was requested. Falling back to the default locale.`, + ); + locale = defaultLocale; + } return { formats, messages: await getMessagesWithFallbacks(locale), diff --git a/frontend/src/types/intl.ts b/frontend/src/types/intl.ts index 2c512a7bd..ed17560f9 100644 --- a/frontend/src/types/intl.ts +++ b/frontend/src/types/intl.ts @@ -4,3 +4,5 @@ import type { getTranslations } from "next-intl/server"; export type TFn = | ReturnType> | Awaited>>; + +export type LocalizedPageProps = { params: { locale: string } }; diff --git a/frontend/tests/components/search/SearchFilters.test.tsx b/frontend/tests/components/search/SearchFilters.test.tsx index cc072ccc7..dcfbd0167 100644 --- a/frontend/tests/components/search/SearchFilters.test.tsx +++ b/frontend/tests/components/search/SearchFilters.test.tsx @@ -14,7 +14,7 @@ jest.mock("src/hooks/useSearchParamUpdater", () => ({ jest.mock("next-intl/server", () => ({ getTranslations: () => identity, - unstable_setRequestLocale: identity, + setRequestLocale: identity, })); jest.mock("next-intl", () => ({ diff --git a/frontend/tests/components/search/SearchResults.test.tsx b/frontend/tests/components/search/SearchResults.test.tsx index 420ee6227..675d6fec7 100644 --- a/frontend/tests/components/search/SearchResults.test.tsx +++ b/frontend/tests/components/search/SearchResults.test.tsx @@ -14,7 +14,7 @@ jest.mock("src/hooks/useSearchParamUpdater", () => ({ jest.mock("next-intl/server", () => ({ getTranslations: () => identity, - unstable_setRequestLocale: identity, + setRequestLocale: identity, })); jest.mock("next-intl", () => ({ diff --git a/frontend/tests/pages/page.test.tsx b/frontend/tests/pages/page.test.tsx index c4e183a1a..4a75d07e7 100644 --- a/frontend/tests/pages/page.test.tsx +++ b/frontend/tests/pages/page.test.tsx @@ -6,7 +6,7 @@ import { mockMessages, useTranslationsMock } from "src/utils/testing/intlMocks"; jest.mock("next-intl/server", () => ({ getTranslations: () => identity, - unstable_setRequestLocale: identity, + setRequestLocale: identity, })); jest.mock("next-intl", () => ({ @@ -16,7 +16,7 @@ jest.mock("next-intl", () => ({ describe("Home", () => { it("renders intro text", () => { - render(); + render(Home({ params: { locale: "en" } })); const content = screen.getByText("goal.paragraph_1"); @@ -24,7 +24,7 @@ describe("Home", () => { }); it("passes accessibility scan", async () => { - const { container } = render(); + const { container } = render(Home({ params: { locale: "en" } })); const results = await waitFor(() => axe(container)); expect(results).toHaveNoViolations(); diff --git a/frontend/tests/pages/process/page.test.tsx b/frontend/tests/pages/process/page.test.tsx index 6268ac3ff..b0e344e5c 100644 --- a/frontend/tests/pages/process/page.test.tsx +++ b/frontend/tests/pages/process/page.test.tsx @@ -6,7 +6,7 @@ import { mockMessages, useTranslationsMock } from "src/utils/testing/intlMocks"; jest.mock("next-intl/server", () => ({ getTranslations: () => identity, - unstable_setRequestLocale: identity, + setRequestLocale: identity, })); jest.mock("next-intl", () => ({ @@ -16,7 +16,7 @@ jest.mock("next-intl", () => ({ describe("Process", () => { it("renders intro text", () => { - render(); + render(Process({ params: { locale: "en" } })); const content = screen.getByText("intro.content"); @@ -24,7 +24,7 @@ describe("Process", () => { }); it("passes accessibility scan", async () => { - const { container } = render(); + const { container } = render(Process({ params: { locale: "en" } })); const results = await waitFor(() => axe(container)); expect(results).toHaveNoViolations(); diff --git a/frontend/tests/pages/research/page.test.tsx b/frontend/tests/pages/research/page.test.tsx index 2a0fb3298..4a8daf9c2 100644 --- a/frontend/tests/pages/research/page.test.tsx +++ b/frontend/tests/pages/research/page.test.tsx @@ -6,7 +6,7 @@ import { mockMessages, useTranslationsMock } from "src/utils/testing/intlMocks"; jest.mock("next-intl/server", () => ({ getTranslations: () => identity, - unstable_setRequestLocale: identity, + setRequestLocale: identity, })); jest.mock("next-intl", () => ({ @@ -16,7 +16,7 @@ jest.mock("next-intl", () => ({ describe("Research", () => { it("renders intro text", () => { - render(); + render(Research({ params: { locale: "en" } })); const content = screen.getByText("intro.content"); @@ -24,7 +24,7 @@ describe("Research", () => { }); it("passes accessibility scan", async () => { - const { container } = render(); + const { container } = render(Research({ params: { locale: "en" } })); const results = await waitFor(() => axe(container)); expect(results).toHaveNoViolations(); diff --git a/frontend/tests/pages/search/page.test.tsx b/frontend/tests/pages/search/page.test.tsx index b54e3e998..62cd9d00e 100644 --- a/frontend/tests/pages/search/page.test.tsx +++ b/frontend/tests/pages/search/page.test.tsx @@ -13,7 +13,7 @@ jest.mock("src/hoc/search/withFeatureFlag", () => jest.mock("next-intl/server", () => ({ getTranslations: () => identity, - unstable_setRequestLocale: identity, + setRequestLocale: identity, })); jest.mock("next-intl", () => ({ @@ -62,6 +62,10 @@ const fetchMock = jest.fn().mockResolvedValue({ status: 200, }); +// working around the complexities of exporting the component wrapped in a feature flag +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const TypedSearchPageComponent = Search as (props: any) => React.JSX.Element; + describe("Search Route", () => { let originalFetch: typeof global.fetch; beforeAll(() => { @@ -78,7 +82,12 @@ describe("Search Route", () => { status: "forecasted,posted", }; - render(); + render( + TypedSearchPageComponent({ + searchParams: mockSearchParams, + params: { locale: "en" }, + }), + ); // translation service is mocked, so the expected label here is the translation key rather than the label text const forecastedCheckbox = await screen.findByLabelText( @@ -110,7 +119,12 @@ describe("Search Route", () => { const mockSearchParams = { status: SEARCH_NO_STATUS_VALUE, }; - render(); + render( + TypedSearchPageComponent({ + searchParams: mockSearchParams, + params: { locale: "en" }, + }), + ); // None should be checked if the "no status checked" value is present. const statuses = ["forecasted", "posted", "closed", "archived"]; @@ -127,7 +141,12 @@ describe("Search Route", () => { const mockSearchParams = { status: undefined, }; - render(); + render( + TypedSearchPageComponent({ + searchParams: mockSearchParams, + params: { locale: "en" }, + }), + ); // These should be clicked if no status is present. const clicked = ["forecasted", "posted"]; diff --git a/frontend/tests/pages/subscribe/page.test.tsx b/frontend/tests/pages/subscribe/page.test.tsx index 551980338..c1c77e534 100644 --- a/frontend/tests/pages/subscribe/page.test.tsx +++ b/frontend/tests/pages/subscribe/page.test.tsx @@ -1,12 +1,13 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import { axe } from "jest-axe"; import { identity } from "lodash"; import Subscribe from "src/app/[locale]/subscribe/page"; -import { render, screen } from "tests/react-utils"; +import { useTranslationsMock } from "src/utils/testing/intlMocks"; jest.mock("react-dom", () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const originalModule = jest.requireActual("react-dom"); + const originalModule = + jest.requireActual("react-dom"); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return { ...originalModule, useFormStatus: jest.fn(() => ({ pending: false })), @@ -29,19 +30,28 @@ jest.mock("react-dom", () => { }; }); +jest.mock("next-intl", () => ({ + useTranslations: () => useTranslationsMock(), +})); + jest.mock("next-intl/server", () => ({ getTranslations: () => identity, - unstable_setRequestLocale: identity, + setRequestLocale: identity, })); describe("Subscribe", () => { it("renders intro text", () => { - render(); + render(Subscribe({ params: { locale: "en" } })); - const content = screen.getByText( - "Subscribe to get Simpler.Grants.gov project updates in your inbox!", - ); + const content = screen.getByText("intro"); expect(content).toBeInTheDocument(); }); + + it("passes accessibility scan", async () => { + const { container } = render(Subscribe({ params: { locale: "en" } })); + const results = await waitFor(() => axe(container)); + + expect(results).toHaveNoViolations(); + }); }); diff --git a/frontend/tests/pages/subscribe/subscribeEmailAction.test.tsx b/frontend/tests/pages/subscribe/subscribeEmailAction.test.tsx index a52310dde..a163821b6 100644 --- a/frontend/tests/pages/subscribe/subscribeEmailAction.test.tsx +++ b/frontend/tests/pages/subscribe/subscribeEmailAction.test.tsx @@ -12,7 +12,7 @@ afterEach(() => { jest.mock("next-intl/server", () => ({ getTranslations: () => identity, - unstable_setRequestLocale: identity, + setRequestLocale: identity, })); jest.mock("next-intl", () => ({ diff --git a/frontend/tests/react-utils.tsx b/frontend/tests/react-utils.tsx index c9cf51329..30998ea37 100644 --- a/frontend/tests/react-utils.tsx +++ b/frontend/tests/react-utils.tsx @@ -13,6 +13,9 @@ import { NextIntlClientProvider } from "next-intl"; /** * Wrapper component that provides global context to all tests. Notably, * it allows our tests to render content when using i18n translation methods. + * + * Note that this functionality does not work when testing page components, use original + * testing methods in that case. */ const GlobalProviders = ({ children }: { children: React.ReactNode }) => { return (