/* 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 { Privacy } from "content-src/components/Widgets/Privacy/Privacy"; const mockState = { ...INITIAL_STATE, Prefs: { ...INITIAL_STATE.Prefs, values: { ...INITIAL_STATE.Prefs.values, "widgets.system.enabled": true, "widgets.system.privacy.enabled": true, "widgets.privacy.enabled": true, "widgets.privacy.size": "medium", }, }, }; function WrapWithProvider({ children, state = INITIAL_STATE }) { const store = createStore(combineReducers(reducers), state); return {children}; } function stateWithTrackers(trackersToday, sitesToday = 9) { return { ...mockState, PrivacyWidget: { ...INITIAL_STATE.PrivacyWidget, initialized: true, trackersToday, sitesToday, }, }; } function renderPrivacy(dispatch = jest.fn(), props = {}, state = mockState) { const { container, unmount } = render( ); return { container, unmount, dispatch }; } describe("Privacy widget", () => { it("renders the widget at the resolved size", () => { const { container } = renderPrivacy(); const root = container.querySelector("article.privacy"); expect(root).toBeTruthy(); expect(root.className).toContain("medium-widget"); }); it("dispatches an impression once when it scrolls into view", () => { const dispatch = jest.fn(); renderPrivacy(dispatch); // useIntersectionObserver invokes the callback on observe in the test env. const impressions = dispatch.mock.calls.filter( ([action]) => action.type === at.WIDGETS_IMPRESSION ); expect(impressions.length).toBeLessThanOrEqual(1); }); it("hides the widget by setting its enabled pref to false", () => { const dispatch = jest.fn(); const { container } = renderPrivacy(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.privacy.enabled" ); expect(setPref).toBeTruthy(); expect(setPref[0].data.value).toBe(false); }); it("shows no metric state until the feed has initialized", () => { // Default mockState has PrivacyWidget.initialized = false. const { container } = renderPrivacy(); const root = container.querySelector("article.privacy"); expect(root).toBeTruthy(); expect(root.className).not.toContain("is-empty"); expect(container.querySelector(".privacy-empty")).toBeFalsy(); expect(container.querySelector(".privacy-count")).toBeFalsy(); }); it("shows the empty state when no trackers are blocked today", () => { const { container } = renderPrivacy(jest.fn(), {}, stateWithTrackers(0)); expect(container.querySelector("article.privacy").className).toContain( "is-empty" ); expect(container.querySelector(".privacy-empty-message")).toBeTruthy(); expect(container.querySelector(".privacy-count")).toBeFalsy(); }); it("shows today's blocked-tracker count", () => { const { container } = renderPrivacy(jest.fn(), {}, stateWithTrackers(42)); expect(container.querySelector(".privacy-count-number").textContent).toBe( "42" ); expect(container.querySelector("article.privacy").className).not.toContain( "is-empty" ); }); it("ceilings the count at 100+", () => { const { container } = renderPrivacy(jest.fn(), {}, stateWithTrackers(250)); expect(container.querySelector(".privacy-count-number").textContent).toBe( "100+" ); }); it("caps at the widgets.privacy.maxCount pref when set", () => { const base = stateWithTrackers(75); const state = { ...base, Prefs: { ...base.Prefs, values: { ...base.Prefs.values, "widgets.privacy.maxCount": 50 }, }, }; const { container } = renderPrivacy(jest.fn(), {}, state); expect(container.querySelector(".privacy-count-number").textContent).toBe( "50+" ); }); it("lets trainhopConfig.widgets.privacyMaxCount override the pref", () => { const base = stateWithTrackers(75); const state = { ...base, Prefs: { ...base.Prefs, values: { ...base.Prefs.values, "widgets.privacy.maxCount": 200, trainhopConfig: { widgets: { privacyMaxCount: 50 } }, }, }, }; const { container } = renderPrivacy(jest.fn(), {}, state); // trainhop (50) wins over the pref (200): 75 > 50 caps to "50+". expect(container.querySelector(".privacy-count-number").textContent).toBe( "50+" ); }); it("shows the exact count just below the ceiling", () => { const { container } = renderPrivacy(jest.fn(), {}, stateWithTrackers(87)); expect(container.querySelector(".privacy-count-number").textContent).toBe( "87" ); }); it("passes the site count to the across-sites line", () => { const { container } = renderPrivacy( jest.fn(), {}, stateWithTrackers(42, 7) ); const sites = container.querySelector(".privacy-count-sites"); expect(sites).toBeTruthy(); expect(sites.getAttribute("data-l10n-args")).toBe( JSON.stringify({ count: 7 }) ); }); it("passes the numeric count (not the ceiling string) to the label plural", () => { const { container } = renderPrivacy(jest.fn(), {}, stateWithTrackers(250)); const label = container.querySelector(".privacy-count-label"); expect(label.getAttribute("data-l10n-args")).toBe( JSON.stringify({ count: 250 }) ); }); });