diff --git a/frontend/src/app/[locale]/settings/page.test.tsx b/frontend/src/app/[locale]/settings/page.test.tsx index 7cc6e680..82c54c89 100644 --- a/frontend/src/app/[locale]/settings/page.test.tsx +++ b/frontend/src/app/[locale]/settings/page.test.tsx @@ -2,6 +2,17 @@ import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import SettingsPage from "./page"; +const mockReplace = jest.fn(); + +jest.mock("next-intl", () => ({ + useLocale: () => "en", +})); + +jest.mock("next/navigation", () => ({ + useRouter: () => ({ replace: mockReplace }), + usePathname: () => "/en/settings", +})); + jest.mock("../../lib/session", () => ({ logoutUser: jest.fn(), })); @@ -32,10 +43,21 @@ jest.mock("../../stores/useWalletStore", () => ({ })); jest.mock("../../stores/useThemeStore", () => ({ - useThemeStore: jest.fn(() => ({ - theme: "system", - setTheme: jest.fn(), - })), + useThemeStore: jest.fn((selector) => + selector + ? selector({ + theme: "system", + hydrated: true, + initializeTheme: jest.fn(), + setTheme: jest.fn(), + }) + : { + theme: "system", + hydrated: true, + initializeTheme: jest.fn(), + setTheme: jest.fn(), + }, + ), })); jest.mock("../../hooks/useApi", () => ({ @@ -48,6 +70,10 @@ jest.mock("../../components/gamification/GamificationSettings", () => ({ })); describe("SettingsPage section navigation", () => { + beforeEach(() => { + mockReplace.mockClear(); + }); + it("exposes the default active section via aria-selected", () => { render(); @@ -79,4 +105,23 @@ describe("SettingsPage section navigation", () => { expect(panel).toHaveAttribute("id", panelId); expect(panel).toHaveAttribute("aria-labelledby", "settings-tab-profile"); }); + + it("renders supported locales (en, es, tl) in DisplaySection and navigates via router.replace on change", async () => { + const user = userEvent.setup(); + render(); + + const displayTab = screen.getByRole("tab", { name: "Display" }); + await user.click(displayTab); + + const languageSelect = screen.getByLabelText("Language") as HTMLSelectElement; + expect(languageSelect.value).toBe("en"); + + const optionValues = Array.from(languageSelect.options).map((o) => o.value); + expect(optionValues).toEqual(["en", "es", "tl"]); + + expect(screen.queryByText(/Only English is fully translated/i)).not.toBeInTheDocument(); + + await user.selectOptions(languageSelect, "es"); + expect(mockReplace).toHaveBeenCalledWith("/es/settings"); + }); }); diff --git a/frontend/src/app/[locale]/settings/page.tsx b/frontend/src/app/[locale]/settings/page.tsx index 57d6c47b..b03715d0 100644 --- a/frontend/src/app/[locale]/settings/page.tsx +++ b/frontend/src/app/[locale]/settings/page.tsx @@ -1,6 +1,8 @@ "use client"; -import { useEffect, useState, type KeyboardEvent } from "react"; +import { useEffect, useState, useTransition, type ChangeEvent, type KeyboardEvent } from "react"; +import { useLocale } from "next-intl"; +import { usePathname, useRouter } from "next/navigation"; import { User, Wallet, @@ -562,12 +564,22 @@ function DisplaySection() { const LANGUAGES = [ { code: "en", label: "English" }, { code: "es", label: "Español" }, - { code: "fr", label: "Français" }, - { code: "pt", label: "Português" }, - { code: "hi", label: "हिन्दी" }, + { code: "tl", label: "Tagalog" }, ]; - const [language, setLanguage] = useState("en"); + const [isPending, startTransition] = useTransition(); + const locale = useLocale(); + const router = useRouter(); + const pathname = usePathname(); + + function handleLanguageChange(event: ChangeEvent) { + const nextLocale = event.target.value; + startTransition(() => { + const segments = pathname.split("/"); + segments[1] = nextLocale; + router.replace(segments.join("/")); + }); + } const theme = useThemeStore((s) => s.theme); const hydrated = useThemeStore((s) => s.hydrated); @@ -616,12 +628,17 @@ function DisplaySection() {
-