/* 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 https://mozilla.org/MPL/2.0/. */ import { render, fireEvent } from "@testing-library/react"; import { Provider } from "react-redux"; import { combineReducers, createStore } from "redux"; import { INITIAL_STATE, reducers } from "common/Reducers.sys.mjs"; import { actionTypes as at } from "common/Actions.mjs"; import { Stocks } from "content-src/components/Widgets/Stocks/Stocks"; const mockState = { ...INITIAL_STATE, Prefs: { ...INITIAL_STATE.Prefs, values: { ...INITIAL_STATE.Prefs.values, "widgets.system.enabled": true, "widgets.system.stocks.enabled": true, "widgets.stocks.enabled": true, "widgets.stocks.size": "medium", }, }, }; function WrapWithProvider({ children, state = INITIAL_STATE }) { const store = createStore(combineReducers(reducers), state); return {children}; } function renderStocks(dispatch = jest.fn(), props = {}) { const { container, unmount } = render( ); return { container, unmount, dispatch }; } describe("Stocks widget", () => { it("renders the widget at the resolved size", () => { const { container } = renderStocks(); const root = container.querySelector("article.stocks"); expect(root).toBeTruthy(); expect(root.className).toContain("medium-widget"); }); it("renders an always-visible localized title", () => { const { container } = renderStocks(); const title = container.querySelector( '[data-l10n-id="newtab-stocks-widget-title"]' ); expect(title).toBeTruthy(); expect(title.getAttribute("aria-hidden")).toBeNull(); }); it("offers medium and large sizes only", () => { const { container } = renderStocks(); const items = [ ...container.querySelectorAll( "#stocks-size-submenu panel-item[type='checkbox']" ), ]; const sizes = items.map(el => el.getAttribute("data-size")); expect(sizes).toEqual(["medium", "large"]); expect(items.every(el => !el.hasAttribute("disabled"))).toBe(true); }); it("hides the widget by setting its enabled pref to false", () => { const dispatch = jest.fn(); const { container } = renderStocks(dispatch); const hide = container.querySelector( '[data-l10n-id="newtab-widget-menu-hide"]' ); fireEvent.click(hide); const setPref = dispatch.mock.calls.find( ([action]) => action.type === at.SET_PREF && action.data?.name === "widgets.stocks.enabled" ); expect(setPref).toBeTruthy(); expect(setPref[0].data.value).toBe(false); }); it("records a telemetry event for the ticker-search stub", () => { const dispatch = jest.fn(); const { container } = renderStocks(dispatch); const search = container.querySelector( '[data-l10n-id="newtab-stocks-menu-search"]' ); fireEvent.click(search); const evt = dispatch.mock.calls.find( ([action]) => action.type === at.WIDGETS_USER_EVENT && action.data?.user_action === "search_tickers" ); expect(evt).toBeTruthy(); }); function renderStocksWithTickers(tickers, size = "medium") { const state = { ...mockState, Prefs: { ...mockState.Prefs, values: { ...mockState.Prefs.values, "widgets.stocks.size": size }, }, Stocks: { tickers, lastUpdated: 1 }, }; return render( ); } // Matches what Merino actually returns: last_price ends with " USD" and // todays_change_perc has no "%". The widget formats both for display. const SAMPLE = [ { ticker: "SPY", name: "SPDR S&P 500 ETF Trust", last_price: "$559.44 USD", todays_change_perc: "+0.2", }, { ticker: "ONEQ", name: "Fidelity Nasdaq Composite", last_price: "$70.10 USD", todays_change_perc: "-0.21", }, { ticker: "DIA", name: "SPDR Dow Jones ETF", last_price: "$430.00 USD", todays_change_perc: "0.00", }, { ticker: "IWM", name: "iShares Russell 2000 ETF", last_price: "$220.00 USD", todays_change_perc: "+1.0", }, ]; it("renders one card per ticker in the medium grid with formatted price and change", () => { const { container } = renderStocksWithTickers(SAMPLE, "medium"); expect( container.querySelectorAll(".stocks-grid .stock-ticker").length ).toBe(4); expect(container.querySelector(".stock-ticker-symbol").textContent).toBe( "SPY" ); expect(container.querySelector(".stock-ticker-price").textContent).toBe( "$559.44" ); expect(container.querySelector(".stock-ticker-change").textContent).toBe( "+0.2%" ); }); it("renders placeholder cards before data arrives", () => { const { container } = renderStocksWithTickers([], "medium"); expect(container.querySelector(".stocks-grid--loading")).toBeTruthy(); expect( container.querySelectorAll(".stocks-grid .stock-ticker").length ).toBe(4); expect(container.querySelectorAll(".stock-ticker-sr").length).toBe(0); }); it("renders the large size as a vertical list showing full names", () => { const { container } = renderStocksWithTickers(SAMPLE, "large"); expect(container.querySelector(".stocks-grid")).toBeNull(); expect( container.querySelectorAll(".stocks-list .stock-ticker--large").length ).toBe(4); expect(container.querySelector(".stock-ticker-name").textContent).toBe( "SPDR S&P 500 ETF Trust" ); expect(container.querySelector(".stock-ticker-price").textContent).toBe( "$559.44" ); expect(container.querySelector(".stock-ticker-change").textContent).toBe( "+0.2%" ); }); it("renders placeholder cards before data arrives at the large size", () => { const { container } = renderStocksWithTickers([], "large"); expect(container.querySelector(".stocks-list--loading")).toBeTruthy(); expect( container.querySelectorAll(".stocks-list .stock-ticker--large").length ).toBe(4); expect(container.querySelectorAll(".stock-ticker-sr").length).toBe(0); }); });