import { createI18n } from "@suaveplan/i18n/plugin"; import { afterEach, describe, expect, it } from "@suaveplan/testing/runner"; import { renderComponent } from "@suaveplan/testing-dom/render"; import { act } from "@testing-library/react"; import { TranslationProvider } from "../../provider/provider.js"; import { resetWarnOncePerProcess } from "../../utils/warn-once-per-process.js"; import { clearUseRelativeTimeCache, useRelativeTime, } from "./use-relative-time.js"; interface ProcessShape { env?: { NODE_ENV?: string }; } function withEnv(value: string, fn: () => T): T { const proc = (globalThis as unknown as { process?: ProcessShape }).process; const previous = proc?.env?.NODE_ENV; if (proc?.env !== undefined) proc.env.NODE_ENV = value; try { return fn(); } finally { if (proc?.env !== undefined) { if (previous === undefined) delete proc.env.NODE_ENV; else proc.env.NODE_ENV = previous; } } } function Probe(props: { readonly deltaMs: number; readonly options?: Parameters[1]; }) { const formatted = useRelativeTime(props.deltaMs, props.options); return {formatted}; } afterEach(() => { resetWarnOncePerProcess(); clearUseRelativeTimeCache(); }); describe("useRelativeTime", () => { it("renders a future minutes delta in English", { timeout: 5000 }, () => { const i18n = createI18n({ defaultLocale: "en-GB", messages: { "en-GB": {} }, }); const { getByTestId } = renderComponent( ); expect(getByTestId("probe").textContent).toMatch(/in 5 minutes/); }); it("renders a past hours delta with negative sign", { timeout: 5000, }, () => { const i18n = createI18n({ defaultLocale: "en-GB", messages: { "en-GB": {} }, }); const { getByTestId } = renderComponent( ); expect(getByTestId("probe").textContent).toMatch(/3 hours ago/); }); it("re-renders with French phrasing on locale change", { timeout: 5000, }, async () => { const i18n = createI18n({ defaultLocale: "en-GB", messages: { "en-GB": {}, "fr-FR": {} }, }); const { getByTestId } = renderComponent( ); const en = getByTestId("probe").textContent ?? ""; expect(en).toMatch(/in 5 minutes/); await act(async () => { await i18n.changeLanguage("fr-FR"); }); const fr = getByTestId("probe").textContent ?? ""; expect(fr).not.toBe(en); expect(fr).toMatch(/dans 5 minutes/); }); it("respects an explicit unit override", { timeout: 5000 }, () => { const i18n = createI18n({ defaultLocale: "en-GB", messages: { "en-GB": {} }, }); const { getByTestId } = renderComponent( ); expect(getByTestId("probe").textContent).toMatch(/in 60 minutes/); }); it("respects style: 'short'", { timeout: 5000 }, () => { const i18n = createI18n({ defaultLocale: "en-GB", messages: { "en-GB": {} }, }); const { getByTestId } = renderComponent( ); expect(getByTestId("probe").textContent).toMatch(/min\b/); }); it("auto-selects seconds, days, weeks, months, years when below thresholds", { timeout: 5000, }, () => { const i18n = createI18n({ defaultLocale: "en-GB", messages: { "en-GB": {} }, }); const cases: Array<[number, RegExp]> = [ [10_000, /10 seconds|seconds/], [3 * 86_400_000, /3 days/], [3 * 604_800_000, /3 weeks/], [3 * 2_629_800_000, /3 months/], [3 * 31_557_600_000, /3 years/], ]; for (const [delta, regex] of cases) { const { getByTestId, unmount } = renderComponent( ); expect(getByTestId("probe").textContent).toMatch(regex); unmount(); } }); it("formats via en-GB on the production no-Provider fallback", { timeout: 5000, }, () => { const original = console.warn; console.warn = (() => {}) as typeof console.warn; try { withEnv("production", () => { const { getByTestId } = renderComponent( ); expect(getByTestId("probe").textContent).toMatch( /in 5 minutes/ ); }); } finally { console.warn = original; } }); });