From f8f22fc9856cceec85b15f04795fad2e26eaefc7 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Fri, 2 Oct 2026 23:48:37 +0200 Subject: [PATCH] test(i18n): a language test unmounts its reader before switching the language back useTags reset the language in a finally while its hook was still mounted; the switch scheduled a render, and in a loaded gate run it landed after the jsdom environment was torn down ('window is not defined', two unhandled errors). auditColumns and comfortPin had the same shape. --- .../src/components/auditColumns.dom.test.tsx | 9 +++- .../comfortPin.langSwitch.dom.test.tsx | 5 +- frontend/src/lib/useTags.dom.test.tsx | 53 +++++++++---------- 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/frontend/src/components/auditColumns.dom.test.tsx b/frontend/src/components/auditColumns.dom.test.tsx index f418518f..dee35785 100644 --- a/frontend/src/components/auditColumns.dom.test.tsx +++ b/frontend/src/components/auditColumns.dom.test.tsx @@ -4,12 +4,17 @@ // "log_level = DEBUG", "Background sync paused") reads in the reader's language, rebuilt from the row's // data through the REAL catalog. A row whose summary is data (an e-mail, a URL) keeps it. import { afterEach, describe, expect, it } from "vitest"; -import { fireEvent, render } from "@testing-library/react"; +import { cleanup, fireEvent, render } from "@testing-library/react"; import i18n, { setLanguage } from "../i18n"; import type { AuditRow } from "../lib/api"; import { auditColumns } from "./auditColumns"; -afterEach(() => setLanguage("en")); +// Unmount BEFORE the language goes back: a mounted reader re-renders on the switch, and under a loaded +// run that render landed after the test environment was gone ("window is not defined", E31 S5). +afterEach(() => { + cleanup(); + setLanguage("en"); +}); const row = (over: Partial): AuditRow => ({ id: 1, diff --git a/frontend/src/components/comfortPin.langSwitch.dom.test.tsx b/frontend/src/components/comfortPin.langSwitch.dom.test.tsx index e0641de8..a3df99d4 100644 --- a/frontend/src/components/comfortPin.langSwitch.dom.test.tsx +++ b/frontend/src/components/comfortPin.langSwitch.dom.test.tsx @@ -6,13 +6,16 @@ // i18n (the key-echo mock does not re-render on a switch, which is the very thing under test). import { describe, it, expect, afterEach, vi } from "vitest"; import type { ReactNode } from "react"; -import { renderHook, act, waitFor } from "@testing-library/react"; +import { renderHook, act, waitFor, cleanup } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { setLanguage } from "../i18n"; import { api } from "../lib/api"; import { useComfortPin } from "./comfortPin"; +// Unmount BEFORE the language goes back: a mounted reader re-renders on the switch, and under a loaded +// run that render landed after the test environment was gone ("window is not defined", E31 S5). afterEach(() => { + cleanup(); vi.restoreAllMocks(); setLanguage("en"); }); diff --git a/frontend/src/lib/useTags.dom.test.tsx b/frontend/src/lib/useTags.dom.test.tsx index 80da15f7..04931ac9 100644 --- a/frontend/src/lib/useTags.dom.test.tsx +++ b/frontend/src/lib/useTags.dom.test.tsx @@ -3,14 +3,21 @@ // E30 (D1 A): the tag catalog as every reader sees it. A system (auto) tag is stored in English and // named in the reader's language; a user's own tag stays as they typed it, even when it happens to be // a vocabulary word. -import { describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import type { ReactNode } from "react"; -import { renderHook, waitFor } from "@testing-library/react"; +import { cleanup, renderHook, waitFor } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { setLanguage } from "../i18n"; import { api, type Tag } from "./api"; import { useTags } from "./useTags"; +// Unmount BEFORE the language goes back: a mounted reader re-renders on the switch, and under a loaded +// run that render landed after the test environment was gone ("window is not defined", E31 S5). +afterEach(() => { + cleanup(); + setLanguage("en"); +}); + const tag = (id: number, name: string, system: boolean): Tag => ({ id, name, @@ -36,19 +43,15 @@ describe("the tag catalog", () => { { ...tag(5, "Hungarian", true), category: "language" }, ]); setLanguage("hu"); - try { - const { result } = renderHook(() => useTags(), { wrapper }); - await waitFor(() => expect(result.current.data).toBeDefined()); - expect(result.current.data!.map((tg) => tg.name)).toEqual([ - "Oktatás", - "Utazás", - "Zene", - "Angol", - "Magyar", - ]); - } finally { - setLanguage("en"); - } + const { result } = renderHook(() => useTags(), { wrapper }); + await waitFor(() => expect(result.current.data).toBeDefined()); + expect(result.current.data!.map((tg) => tg.name)).toEqual([ + "Oktatás", + "Utazás", + "Zene", + "Angol", + "Magyar", + ]); }); it("names an auto-tag in the reader's language, a user's tag as typed", async () => { @@ -59,17 +62,13 @@ describe("the tag catalog", () => { tag(4, "Somethingnew", true), // an auto-tag the catalog does not name yet ]); setLanguage("hu"); - try { - const { result } = renderHook(() => useTags(), { wrapper }); - await waitFor(() => expect(result.current.data).toBeDefined()); - expect(result.current.data!.map((tg) => tg.name)).toEqual([ - "Egészség és fitnesz", - "Gaming", - "Magyar", - "Somethingnew", - ]); - } finally { - setLanguage("en"); - } + const { result } = renderHook(() => useTags(), { wrapper }); + await waitFor(() => expect(result.current.data).toBeDefined()); + expect(result.current.data!.map((tg) => tg.name)).toEqual([ + "Egészség és fitnesz", + "Gaming", + "Magyar", + "Somethingnew", + ]); }); });