/* 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 { fireEvent, render } from "@testing-library/react"; import { Provider } from "react-redux"; import { actionTypes as at } from "common/Actions.mjs"; import { SportsMatchRow, UpcomingMatchPlaceholder, } from "content-src/components/Widgets/SportsWidget/SportsMatchRow"; const baseMatch = { home_team: { key: "ENG", name: "England" }, away_team: { key: "USA", name: "United States" }, date: "2026-05-08T14:00:00+00:00", status_type: null, home_score: 1, away_score: 0, home_extra: null, away_extra: null, home_penalty: null, away_penalty: null, query: "England vs United States", }; // SportsMatchRow uses `useDispatch` and `useSelector`, which need a // Provider context. We give it a fake store whose `dispatch` is a jest // mock so we can assert on what got dispatched when a user clicks a row. // The fake state surfaces the widgets.sportsWidget.size pref because the // row reads it for telemetry. function renderWithDispatch(ui, { widgetSize = "large" } = {}) { const dispatch = jest.fn(); const store = { getState: () => ({ Prefs: { values: { "widgets.sportsWidget.size": widgetSize } }, }), subscribe: () => () => {}, dispatch, }; const result = render({ui}); return { ...result, dispatch }; } describe(" upcoming variant", () => { it("renders home and away team codes", () => { const { container } = renderWithDispatch( ); const codes = container.querySelectorAll(".sports-match-code"); expect(codes[0].textContent).toBe("ENG"); expect(codes[1].textContent).toBe("USA"); }); it("displays team.region for the code, falling back to team.key", () => { const { container } = renderWithDispatch( ); const codes = container.querySelectorAll(".sports-match-code"); // Home uses region; away has no region so it falls back to key. expect(codes[0].textContent).toBe("COD"); expect(codes[1].textContent).toBe("USA"); }); it("renders match time element", () => { const { container } = renderWithDispatch( ); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-time']" ) ).toBeInTheDocument(); }); it("renders match date when no special status", () => { const { container } = renderWithDispatch( ); expect( container.querySelector("[data-l10n-id='newtab-sports-widget-key-date']") ).toBeInTheDocument(); }); it("renders status label instead of date for a special status", () => { const { container } = renderWithDispatch( ); expect( container.querySelector("[data-l10n-id='newtab-sports-widget-postponed']") ).toBeInTheDocument(); expect( container.querySelector("[data-l10n-id='newtab-sports-widget-key-date']") ).not.toBeInTheDocument(); }); it("accepts the American spelling 'canceled' from the API and renders the Cancelled badge", () => { const { container } = renderWithDispatch( ); expect( container.querySelector("[data-l10n-id='newtab-sports-widget-cancelled']") ).toBeInTheDocument(); }); }); describe(" now variant", () => { it("renders home and away scores without double-counting extra time", () => { const { container } = renderWithDispatch( ); expect(container.querySelector(".sports-score-home").textContent).toBe("2"); expect(container.querySelector(".sports-score-away").textContent).toBe("0"); }); it.each([ [ "halftime (period 1, status Break)", { period: "1", status: "Break" }, "newtab-sports-widget-match-halftime", ], [ "extra time (period ExtraTime)", { period: "ExtraTime", status: "In Progress" }, "newtab-sports-widget-match-extra-time", ], ])( "renders the localised live status label for %s", (_label, matchOverrides, expectedL10nId) => { const { container } = renderWithDispatch( ); expect( container.querySelector(`[data-l10n-id='${expectedL10nId}']`) ).toBeInTheDocument(); } ); it.each([ ["regulation in progress", { period: "1", status: "In Progress" }], ["all fields null", { period: null, status: null }], ])("renders no live status footer for %s", (_label, matchOverrides) => { const { container } = renderWithDispatch( ); expect( container.querySelector(".sports-match-live-footer") ).not.toBeInTheDocument(); }); it("does not treat a Suspended match as a penalty shootout (regression guard for the 'pen' substring trap)", () => { const { container } = renderWithDispatch( ); expect( container.querySelector(".sports-match-live-footer") ).not.toBeInTheDocument(); }); it.each([ ["halftime", { period: "1", status: "Break" }], ["extra time", { period: "ExtraTime", status: "In Progress" }], ])( "does not render the live status footer in the medium widget when %s", (_label, matchOverrides) => { const { container } = renderWithDispatch( ); expect( container.querySelector(".sports-match-live-footer") ).not.toBeInTheDocument(); } ); it("renders the Penalties footer for a penalty shootout with null scores", () => { const { container } = renderWithDispatch( ); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-penalties']" ) ).toBeInTheDocument(); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-extra-time']" ) ).not.toBeInTheDocument(); expect(container.querySelectorAll(".sports-score-penalty")).toHaveLength(0); }); it.each([["pen"], ["PEN"], ["penaltyshootout"]])( "recognises the short-form period value %s as a shootout", period => { const { container } = renderWithDispatch( ); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-penalties']" ) ).toBeInTheDocument(); } ); it("treats populated penalty scores as a shootout even when period flips back to a regulation value", () => { const { container } = renderWithDispatch( ); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-penalties']" ) ).toBeInTheDocument(); const penalties = container.querySelectorAll(".sports-score-penalty"); expect(penalties[0].textContent).toBe("(4)"); expect(penalties[1].textContent).toBe("(3)"); }); it.each([["extratime"], ["ET"], ["et "], ["Extra Time"]])( "matches the Extra time footer case-insensitively on period value %s", period => { const { container } = renderWithDispatch( ); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-extra-time']" ) ).toBeInTheDocument(); } ); it("prefers the shootout footer over halftime when both signals are present", () => { const { container } = renderWithDispatch( ); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-penalties']" ) ).toBeInTheDocument(); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-halftime']" ) ).not.toBeInTheDocument(); }); it("prefers the shootout footer over extra time when penalty scores are present alongside an ExtraTime period", () => { const { container } = renderWithDispatch( ); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-penalties']" ) ).toBeInTheDocument(); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-extra-time']" ) ).not.toBeInTheDocument(); }); it("prefers the extra time footer over halftime when both signals are present", () => { const { container } = renderWithDispatch( ); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-extra-time']" ) ).toBeInTheDocument(); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-halftime']" ) ).not.toBeInTheDocument(); }); it("treats away_penalty alone as a shootout signal", () => { const { container } = renderWithDispatch( ); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-penalties']" ) ).toBeInTheDocument(); }); it.each([["large"], ["medium"]])( "renders penalty subscripts in the Now-variant score pill in the %s widget when penalty scores are present", size => { const { container } = renderWithDispatch( ); const penalties = container.querySelectorAll(".sports-score-penalty"); expect(penalties[0].textContent).toBe("(5)"); expect(penalties[1].textContent).toBe("(4)"); } ); }); describe(" results variant", () => { it("renders home and away scores without double-counting extra time", () => { const { container } = renderWithDispatch( ); expect(container.querySelector(".sports-score-home").textContent).toBe("2"); expect(container.querySelector(".sports-score-away").textContent).toBe("0"); }); it("renders the Full time FTL footer when status_type is Final", () => { const { container } = renderWithDispatch( ); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-full-time']" ) ).toBeInTheDocument(); }); it("defaults to the Full time FTL footer when status_type is an unmapped finished-state value", () => { const { container } = renderWithDispatch( ); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-full-time']" ) ).toBeInTheDocument(); const footer = container.querySelector(".sports-match-result-footer"); expect(footer.textContent).not.toContain("Final - Shoot Out"); }); it("defaults to the Full time FTL footer when a live-state match leaks into the Results bucket", () => { const { container } = renderWithDispatch( ); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-full-time']" ) ).toBeInTheDocument(); const footer = container.querySelector(".sports-match-result-footer"); expect(footer.textContent).not.toContain("live"); }); it("defaults to the Full time FTL footer when status_type is null", () => { const { container } = renderWithDispatch( ); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-full-time']" ) ).toBeInTheDocument(); }); it("renders penalty scores and footer text when penalties are present", () => { const { container } = renderWithDispatch( ); const penalties = container.querySelectorAll(".sports-score-penalty"); expect(penalties[0].textContent).toBe("(4)"); expect(penalties[1].textContent).toBe("(3)"); // Full time default still renders alongside the penalties footer. expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-full-time']" ) ).toBeInTheDocument(); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-penalties']" ) ).toBeInTheDocument(); }); it("shows 'Penalties' instead of 'Full time' for medium-size rows", () => { const { container } = renderWithDispatch( ); // Penalty scores still render inside the score pill. expect(container.querySelectorAll(".sports-score-penalty")).toHaveLength(2); // Medium replaces the status text with "Penalties" (no "Full time"). expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-penalties']" ) ).toBeInTheDocument(); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-full-time']" ) ).not.toBeInTheDocument(); }); it("does not render the penalties footer label for list-size rows", () => { const { container } = renderWithDispatch( ); // Penalty scores still render inside the score pill. expect(container.querySelectorAll(".sports-score-penalty")).toHaveLength(2); // List view keeps only the status text, with no penalties label. expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-full-time']" ) ).toBeInTheDocument(); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-penalties']" ) ).not.toBeInTheDocument(); }); it("does not render penalty elements when no penalties", () => { const { container } = renderWithDispatch( ); expect(container.querySelectorAll(".sports-score-penalty")).toHaveLength(0); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-match-penalties']" ) ).not.toBeInTheDocument(); }); }); describe(" aria-label l10n", () => { function getAnchorL10n(container) { const anchor = container.querySelector("a.sports-match-row"); return { id: anchor.getAttribute("data-l10n-id"), args: JSON.parse(anchor.getAttribute("data-l10n-args")), }; } describe("results variant", () => { it("uses the results l10n id for a normal full-time result", () => { const { container } = renderWithDispatch( ); const { id, args } = getAnchorL10n(container); expect(id).toBe("newtab-sports-widget-match-aria-label-results"); expect(args).toEqual({ homeTeam: "England", awayTeam: "United States", homeScore: 3, awayScore: 2, }); }); it("uses the results-penalties l10n id and includes penalty scores when the match went to a shootout", () => { const { container } = renderWithDispatch( ); const { id, args } = getAnchorL10n(container); expect(id).toBe( "newtab-sports-widget-match-aria-label-results-penalties" ); expect(args.homePenalty).toBe(5); expect(args.awayPenalty).toBe(4); }); it("does not double-count extra-time goals in the announced scores", () => { const { container } = renderWithDispatch( ); const { args } = getAnchorL10n(container); expect(args.homeScore).toBe(2); expect(args.awayScore).toBe(1); }); it("treats home_penalty === 0 as a real shootout value, not a missing one", () => { // Guards against a regression where a falsy check on home_penalty // would route a 0-goal shootout to the non-penalties ID. const { container } = renderWithDispatch( ); const { id, args } = getAnchorL10n(container); expect(id).toBe( "newtab-sports-widget-match-aria-label-results-penalties" ); expect(args.homePenalty).toBe(0); expect(args.awayPenalty).toBe(3); }); }); describe("now variant", () => { it("uses the now l10n id with the current score", () => { const { container } = renderWithDispatch( ); const { id, args } = getAnchorL10n(container); expect(id).toBe("newtab-sports-widget-match-aria-label-now"); expect(args).toEqual( expect.objectContaining({ homeTeam: "England", awayTeam: "United States", homeScore: 1, awayScore: 0, }) ); }); }); describe("upcoming variant", () => { it("uses the upcoming l10n id and passes the date timestamp for a scheduled match", () => { const { container } = renderWithDispatch( ); const { id, args } = getAnchorL10n(container); expect(id).toBe("newtab-sports-widget-match-aria-label-upcoming"); expect(args.date).toBe(new Date(baseMatch.date).getTime()); }); it("falls back to the upcoming (scheduled) l10n id when status_type is missing", () => { // Defensive: API can omit status_type; the default scheduled string // must still be picked. const { container } = renderWithDispatch( ); expect(getAnchorL10n(container).id).toBe( "newtab-sports-widget-match-aria-label-upcoming" ); }); it.each([ ["delayed", "newtab-sports-widget-match-aria-label-upcoming-delayed"], ["postponed", "newtab-sports-widget-match-aria-label-upcoming-postponed"], ["suspended", "newtab-sports-widget-match-aria-label-upcoming-suspended"], ["cancelled", "newtab-sports-widget-match-aria-label-upcoming-cancelled"], ["canceled", "newtab-sports-widget-match-aria-label-upcoming-cancelled"], ])( "picks the matching per-status l10n id when status_type is %s", (statusType, expectedId) => { const { container } = renderWithDispatch( ); expect(getAnchorL10n(container).id).toBe(expectedId); } ); }); }); describe(" click handling", () => { it.each(["upcoming", "results", "now"])( "dispatches a newtab telemetry event and the open-search action on click (variant=%s)", variant => { const { container, dispatch } = renderWithDispatch( , { widgetSize: "medium" } ); fireEvent.click(container.querySelector("a.sports-match-row")); expect(dispatch).toHaveBeenCalledTimes(2); const [[userEvent], [openSearch]] = dispatch.mock.calls; expect(userEvent).toMatchObject({ type: at.WIDGETS_USER_EVENT, data: { widget_name: "sports", widget_source: "widget", user_action: "open_match_search", action_value: variant, widget_size: "medium", }, }); expect(openSearch).toMatchObject({ type: at.WIDGETS_SPORTS_OPEN_MATCH_SEARCH, data: { query: baseMatch.query, eventInfo: expect.objectContaining({ button: 0 }), }, }); } ); it("propagates modifier key state in the dispatched eventInfo", () => { const { container, dispatch } = renderWithDispatch( ); fireEvent.click(container.querySelector("a.sports-match-row"), { metaKey: true, shiftKey: true, }); // calls[0] is the USER_EVENT; calls[1] is the OPEN_MATCH_SEARCH action // whose eventInfo carries the modifier state. const [, [openSearch]] = dispatch.mock.calls; expect(openSearch.data.eventInfo).toMatchObject({ metaKey: true, shiftKey: true, button: 0, }); }); it("treats Enter and Space key presses as activation", () => { // Anchors without href don't fire click on Enter/Space natively, so the // component wires keyboard activation up manually. Each activation // produces two dispatches (USER_EVENT + OPEN_MATCH_SEARCH). const { container, dispatch } = renderWithDispatch( ); const anchor = container.querySelector("a.sports-match-row"); fireEvent.keyDown(anchor, { key: "Enter" }); fireEvent.keyDown(anchor, { key: " " }); expect(dispatch).toHaveBeenCalledTimes(4); expect(dispatch.mock.calls[0][0].type).toBe(at.WIDGETS_USER_EVENT); expect(dispatch.mock.calls[1][0].type).toBe( at.WIDGETS_SPORTS_OPEN_MATCH_SEARCH ); expect(dispatch.mock.calls[2][0].type).toBe(at.WIDGETS_USER_EVENT); expect(dispatch.mock.calls[3][0].type).toBe( at.WIDGETS_SPORTS_OPEN_MATCH_SEARCH ); }); it("does not dispatch when the match has no query", () => { const { container, dispatch } = renderWithDispatch( ); fireEvent.click(container.querySelector("a.sports-match-row")); expect(dispatch).not.toHaveBeenCalled(); }); it("calls handleInteraction on click to mark the widget as interacted-with", () => { const handleInteraction = jest.fn(); const { container } = renderWithDispatch( ); fireEvent.click(container.querySelector("a.sports-match-row")); expect(handleInteraction).toHaveBeenCalledTimes(1); }); it("does not call handleInteraction when the match has no query", () => { const handleInteraction = jest.fn(); const { container } = renderWithDispatch( ); fireEvent.click(container.querySelector("a.sports-match-row")); expect(handleInteraction).not.toHaveBeenCalled(); }); it("applies link semantics only when the match has a query", () => { // With a query: the row claims a link role and is in the tab order. const { container: withQuery } = renderWithDispatch( ); const clickableAnchor = withQuery.querySelector("a.sports-match-row"); expect(clickableAnchor).toHaveAttribute("tabindex", "0"); expect(clickableAnchor).toHaveAttribute("role", "link"); expect(clickableAnchor.className).toContain("clickable"); // Without a query: no role, no tabindex, no `clickable` class — the // anchor is an inert wrapper so AT doesn't announce a phantom link. const { container: noQuery } = renderWithDispatch( ); const inertAnchor = noQuery.querySelector("a.sports-match-row"); expect(inertAnchor).not.toHaveAttribute("tabindex"); expect(inertAnchor).not.toHaveAttribute("role"); expect(inertAnchor.className).not.toContain("clickable"); }); }); describe(" followed teams", () => { function getFlagWrappers(container) { return container.querySelectorAll(".sports-match-flag-wrapper"); } function getCodes(container) { return container.querySelectorAll(".sports-match-code"); } it("does not mark either side as followed when followedTeams is undefined", () => { const { container } = renderWithDispatch( ); const wrappers = getFlagWrappers(container); expect(wrappers[0].classList.contains("is-followed")).toBe(false); expect(wrappers[1].classList.contains("is-followed")).toBe(false); expect(container.querySelectorAll(".sports-match-flag-check")).toHaveLength( 0 ); const codes = getCodes(container); expect(codes[0].querySelector("strong")).toBeNull(); expect(codes[1].querySelector("strong")).toBeNull(); }); it("marks only the home side when only the home team is followed", () => { const { container } = renderWithDispatch( ); const wrappers = getFlagWrappers(container); expect(wrappers[0].classList.contains("is-followed")).toBe(true); expect(wrappers[1].classList.contains("is-followed")).toBe(false); const checks = container.querySelectorAll(".sports-match-flag-check"); expect(checks).toHaveLength(1); expect(wrappers[0].querySelector(".sports-match-flag-check")).toBeTruthy(); const codes = getCodes(container); expect(codes[0].querySelector("strong").textContent).toBe("ENG"); expect(codes[1].querySelector("strong")).toBeNull(); }); it("marks only the away side when only the away team is followed", () => { const { container } = renderWithDispatch( ); const wrappers = getFlagWrappers(container); expect(wrappers[0].classList.contains("is-followed")).toBe(false); expect(wrappers[1].classList.contains("is-followed")).toBe(true); expect(wrappers[1].querySelector(".sports-match-flag-check")).toBeTruthy(); const codes = getCodes(container); expect(codes[0].querySelector("strong")).toBeNull(); expect(codes[1].querySelector("strong").textContent).toBe("USA"); }); it("marks both sides when both teams are followed", () => { const { container } = renderWithDispatch( ); const wrappers = getFlagWrappers(container); expect(wrappers[0].classList.contains("is-followed")).toBe(true); expect(wrappers[1].classList.contains("is-followed")).toBe(true); expect(container.querySelectorAll(".sports-match-flag-check")).toHaveLength( 2 ); }); it("hides the check badge from assistive technology", () => { const { container } = renderWithDispatch( ); expect( container .querySelector(".sports-match-flag-check") .getAttribute("aria-hidden") ).toBe("true"); }); }); describe(" localized team names", () => { it("renders the localized name in the flag's title and alt when localizedName is provided", () => { const { container } = renderWithDispatch( ); const flags = container.querySelectorAll(".sports-match-flag"); expect(flags[0].getAttribute("title")).toBe("Deutschland"); expect(flags[0].getAttribute("alt")).toBe("Deutschland"); expect(flags[1].getAttribute("title")).toBe("Brasilien"); expect(flags[1].getAttribute("alt")).toBe("Brasilien"); }); it("falls back to team.name when localizedNames is undefined or missing a key", () => { const { container: undefinedContainer } = renderWithDispatch( ); const undefinedFlags = undefinedContainer.querySelectorAll(".sports-match-flag"); expect(undefinedFlags[0].getAttribute("title")).toBe( baseMatch.home_team.name ); expect(undefinedFlags[0].getAttribute("alt")).toBe( baseMatch.home_team.name ); const { container: partialContainer } = renderWithDispatch( ); const partialFlags = partialContainer.querySelectorAll(".sports-match-flag"); expect(partialFlags[0].getAttribute("title")).toBe("Deutschland"); expect(partialFlags[1].getAttribute("title")).toBe( baseMatch.away_team.name ); expect(partialFlags[1].getAttribute("alt")).toBe(baseMatch.away_team.name); }); it("uses localized team names in the row aria-label args when localizedNames is provided", () => { const { container } = renderWithDispatch( ); const anchor = container.querySelector(".sports-match-row"); const argsJson = anchor.getAttribute("data-l10n-args"); expect(argsJson).toBeTruthy(); const args = JSON.parse(argsJson); expect(args.homeTeam).toBe("Deutschland"); expect(args.awayTeam).toBe("Brasilien"); }); }); describe(" flag accessibility", () => { it("sets alt and title on the home team flag image to the team name", () => { const { container } = renderWithDispatch( ); const flags = container.querySelectorAll(".sports-match-flag"); expect(flags[0].getAttribute("alt")).toBe("England"); expect(flags[0].getAttribute("title")).toBe("England"); }); it("sets alt and title on the away team flag image to the team name", () => { const { container } = renderWithDispatch( ); const flags = container.querySelectorAll(".sports-match-flag"); expect(flags[1].getAttribute("alt")).toBe("United States"); expect(flags[1].getAttribute("title")).toBe("United States"); }); it("does not set title on the parent .sports-match-team div", () => { const { container } = renderWithDispatch( ); const teamDivs = container.querySelectorAll(".sports-match-team"); expect(teamDivs[0].getAttribute("title")).toBeNull(); expect(teamDivs[1].getAttribute("title")).toBeNull(); }); }); describe(" undecided (TBD) teams", () => { // A knockout fixture whose teams aren't decided yet: the backend sends // home_team/away_team as null but still provides a query and stage. const tbdMatch = { ...baseMatch, home_team: null, away_team: null, status_type: "scheduled", query: "Quarter-finals World Cup 2026", }; it("renders a placeholder rectangle and '--' for each undecided team", () => { const { container } = renderWithDispatch( ); // Placeholder spans stand in for the flag images, which are absent. expect(container.querySelectorAll(".sports-match-flag-tbd")).toHaveLength( 2 ); expect(container.querySelectorAll("img.sports-match-flag")).toHaveLength(0); const codes = container.querySelectorAll(".sports-match-code"); expect(codes[0].textContent).toBe("--"); expect(codes[1].textContent).toBe("--"); }); it("renders a real team on the decided side and a placeholder on the other", () => { const { container } = renderWithDispatch( ); const flags = container.querySelectorAll("img.sports-match-flag"); expect(flags).toHaveLength(1); expect(flags[0].getAttribute("alt")).toBe("England"); expect(container.querySelectorAll(".sports-match-flag-tbd")).toHaveLength( 1 ); const codes = container.querySelectorAll(".sports-match-code"); expect(codes[0].textContent).toBe("ENG"); expect(codes[1].textContent).toBe("--"); }); it("substitutes the tbdTeamName prop for an undecided team in the aria-label", () => { const { container } = renderWithDispatch( ); const args = JSON.parse( container .querySelector("a.sports-match-row") .getAttribute("data-l10n-args") ); expect(args.homeTeam).toBe("England"); expect(args.awayTeam).toBe("To be determined"); }); }); describe("", () => { it("renders two placeholder tiles with '--' and a vs separator", () => { const { container } = render(); expect(container.querySelectorAll(".sports-match-flag-tbd")).toHaveLength( 2 ); expect(container.querySelectorAll("img.sports-match-flag")).toHaveLength(0); const codes = container.querySelectorAll(".sports-match-code"); expect(codes[0].textContent).toBe("--"); expect(codes[1].textContent).toBe("--"); expect( container.querySelector("[data-l10n-id='newtab-sports-widget-match-vs']") ).toBeInTheDocument(); }); it("shows the 'no upcoming matches' note at the large size", () => { const { container } = render(); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-no-upcoming-matches']" ) ).toBeInTheDocument(); }); it("hides the note at non-large sizes", () => { const { container } = render(); expect( container.querySelector( "[data-l10n-id='newtab-sports-widget-no-upcoming-matches']" ) ).not.toBeInTheDocument(); }); });