Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import { renderHook, waitFor } from "@testing-library/react";
import { useLocalizedTeamNames } from "content-src/components/Widgets/SportsWidget/useLocalizedTeamNames.jsx";
const FLUENT_LABELS = {
"newtab-sports-widget-team-name-label-bih": "Bosnia and Herzegovina",
"newtab-sports-widget-team-name-label-civ": "Ivory Coast",
"newtab-sports-widget-team-name-label-cod": "DR Congo",
"newtab-sports-widget-team-name-label-eng": "England",
"newtab-sports-widget-team-name-label-sco": "Scotland",
};
function mockDocumentL10n() {
document.l10n = {
formatMessages: jest.fn(async ids =>
ids.map(({ id }) => ({
value: null,
attributes: [{ name: "label", value: FLUENT_LABELS[id] }],
}))
),
};
}
describe("useLocalizedTeamNames", () => {
beforeEach(mockDocumentL10n);
afterEach(() => {
delete document.l10n;
});
it("returns null on the first render before names resolve", () => {
// Include an override key (ENG) so the resolver suspends on the
// formatMessages await — otherwise the no-override path resolves
// synchronously inside renderHook's act() and the initial null is
// overwritten before the test can observe it.
const teams = [{ key: "ENG", name: "England" }];
const { result, unmount } = renderHook(() => useLocalizedTeamNames(teams));
expect(result.current).toBeNull();
// Unmount before the pending formatMessages promise resolves so the
// hook's cleanup sets `cancelled = true` and skips the setResolved
// setState that would otherwise fire outside act().
unmount();
});
it("resolves Intl.DisplayNames names for non-override FIFA keys", async () => {
const teams = [
{ key: "CAN", name: "Canada" },
{ key: "AUS", name: "Australia" },
{ key: "ALG", name: "Algeria" },
];
const { result } = renderHook(() => useLocalizedTeamNames(teams));
await waitFor(() => expect(result.current).not.toBeNull());
// en-US Intl.DisplayNames for the corresponding ISO codes.
expect(result.current.CAN).toBe("Canada");
expect(result.current.AUS).toBe("Australia");
expect(result.current.ALG).toBe("Algeria");
});
it("resolves Fluent overrides only for FIFA codes in FLUENT_OVERRIDE_KEYS", async () => {
const teams = [
{ key: "ENG", name: "England (source)" },
{ key: "CAN", name: "Canada" },
];
const { result } = renderHook(() => useLocalizedTeamNames(teams));
await waitFor(() => expect(result.current).not.toBeNull());
expect(result.current.ENG).toBe("England");
expect(document.l10n.formatMessages).toHaveBeenCalledWith([
{ id: "newtab-sports-widget-team-name-label-eng" },
]);
});
it("requests the correct Fluent ID for each of the 5 exceptions to Intl.DisplayNames (BIH, CIV, COD, ENG, SCO)", async () => {
const teams = ["BIH", "CIV", "COD", "ENG", "SCO"].map(key => ({
key,
name: `${key} source`,
}));
const { result } = renderHook(() => useLocalizedTeamNames(teams));
await waitFor(() => expect(result.current).not.toBeNull());
expect(document.l10n.formatMessages).toHaveBeenCalledWith([
{ id: "newtab-sports-widget-team-name-label-bih" },
{ id: "newtab-sports-widget-team-name-label-civ" },
{ id: "newtab-sports-widget-team-name-label-cod" },
{ id: "newtab-sports-widget-team-name-label-eng" },
{ id: "newtab-sports-widget-team-name-label-sco" },
]);
expect(result.current).toEqual({
BIH: "Bosnia and Herzegovina",
CIV: "Ivory Coast",
COD: "DR Congo",
ENG: "England",
SCO: "Scotland",
});
});
it("falls back to team.name when key is in neither map", async () => {
const teams = [
{ key: "ZZZ", name: "Atlantis" },
{ key: "CAN", name: "Canada" },
];
const { result } = renderHook(() => useLocalizedTeamNames(teams));
await waitFor(() => expect(result.current).not.toBeNull());
expect(result.current.ZZZ).toBe("Atlantis");
});
it("falls back to team.name when the Fluent message has no label attribute", async () => {
document.l10n.formatMessages = jest.fn(async ids =>
ids.map(() => ({ value: null, attributes: [] }))
);
const teams = [{ key: "ENG", name: "England (source)" }];
const { result } = renderHook(() => useLocalizedTeamNames(teams));
await waitFor(() => expect(result.current).not.toBeNull());
expect(result.current.ENG).toBe("England (source)");
});
it("does not call document.l10n when no teams need Fluent overrides", async () => {
const teams = [
{ key: "CAN", name: "Canada" },
{ key: "AUS", name: "Australia" },
];
const { result } = renderHook(() => useLocalizedTeamNames(teams));
await waitFor(() => expect(result.current).not.toBeNull());
expect(document.l10n.formatMessages).not.toHaveBeenCalled();
});
it("returns null when teams reference changes, then re-resolves for the new list", async () => {
const initialTeams = [
{ key: "CAN", name: "Canada" },
{ key: "ENG", name: "England" },
];
const { result, rerender } = renderHook(
({ teams }) => useLocalizedTeamNames(teams),
{ initialProps: { teams: initialTeams } }
);
await waitFor(() => expect(result.current).not.toBeNull());
expect(result.current.CAN).toBe("Canada");
const newTeams = [
{ key: "FRA", name: "France" },
{ key: "SCO", name: "Scotland" },
];
rerender({ teams: newTeams });
// Hook returns null until the new resolution lands, preventing stale
// reads when callers index into localizedNames[team.key] during sort
// or filter.
expect(result.current).toBeNull();
await waitFor(() => expect(result.current).not.toBeNull());
expect(result.current.FRA).toBe("France");
expect(result.current.SCO).toBe("Scotland");
});
});