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 { CardWebNotifications } from "content-src/components/TopSitesHoverCard/CardWebNotifications/CardWebNotifications";
const ORIGIN = "https://example.com";
const NOTIFICATIONS = {
a: {
id: "a",
title: "First",
body: "A",
origin: ORIGIN,
timestamp: 100,
},
b: {
id: "b",
title: "Second",
origin: ORIGIN,
timestamp: 300,
},
c: {
id: "c",
title: "Third",
origin: ORIGIN,
timestamp: 200,
},
d: {
id: "d",
title: "Fourth",
origin: ORIGIN,
timestamp: 400,
},
};
const BY_ORIGIN = { [ORIGIN]: ["a", "b", "c", "d"] };
function mockState({ notifications = {}, byOrigin = {} } = {}) {
return {
...INITIAL_STATE,
WebNotifications: {
...INITIAL_STATE.WebNotifications,
notifications,
byOrigin,
},
};
}
function renderCard(link, stateOpts) {
const store = createStore(combineReducers(reducers), mockState(stateOpts));
const dispatch = jest.spyOn(store, "dispatch");
const { container } = render(
);
return { container, dispatch };
}
function dispatchedActions(dispatch) {
return dispatch.mock.calls.map(([action]) => action);
}
describe("", () => {
const link = { url: `${ORIGIN}/path`, label: "Example" };
it("renders nothing when the site has no notifications", () => {
const { container } = renderCard(link, {});
expect(container.querySelectorAll(".top-sites-hover-card")).toHaveLength(0);
});
it("renders nothing for a non-http(s) link", () => {
const { container } = renderCard(
{ url: "about:blank" },
{ notifications: NOTIFICATIONS, byOrigin: BY_ORIGIN }
);
expect(container.querySelectorAll(".top-sites-hover-card")).toHaveLength(0);
});
it("lists every notification, most recent first", () => {
const { container } = renderCard(link, {
notifications: NOTIFICATIONS,
byOrigin: BY_ORIGIN,
});
const titles = Array.from(
container.querySelectorAll(".top-sites-hover-card-notification-title")
).map(node => node.textContent);
expect(titles).toEqual(["Fourth", "Second", "Third", "First"]);
});
it("renders every notification as an activatable button", () => {
const { container } = renderCard(link, {
notifications: {
a: {
id: "a",
title: "SW",
origin: ORIGIN,
timestamp: 2,
},
b: {
id: "b",
title: "Page",
origin: ORIGIN,
timestamp: 1,
},
},
byOrigin: { [ORIGIN]: ["a", "b"] },
});
const activators = container.querySelectorAll(
".top-sites-hover-card-notification-activate"
);
expect(activators).toHaveLength(2);
expect(activators[0].tagName).toBe("BUTTON");
expect(activators[1].tagName).toBe("BUTTON");
});
it("dispatches WEB_NOTIFICATIONS_CLICK when a notification is activated", () => {
const { container, dispatch } = renderCard(link, {
notifications: {
a: {
id: "a",
title: "Page",
origin: ORIGIN,
timestamp: 1,
},
},
byOrigin: { [ORIGIN]: ["a"] },
});
fireEvent.click(
container.querySelector(
"button.top-sites-hover-card-notification-activate"
)
);
expect(
dispatchedActions(dispatch).some(
action => action.type === at.WEB_NOTIFICATIONS_CLICK
)
).toBe(true);
});
it("dispatches WEB_NOTIFICATIONS_DISMISS from the per-row dismiss", () => {
const { container, dispatch } = renderCard(link, {
notifications: {
a: {
id: "a",
title: "A",
origin: ORIGIN,
timestamp: 1,
},
},
byOrigin: { [ORIGIN]: ["a"] },
});
fireEvent.click(
container.querySelector(
"button.top-sites-hover-card-notification-dismiss"
)
);
const action = dispatchedActions(dispatch).find(
a => a.type === at.WEB_NOTIFICATIONS_DISMISS
);
expect(action).toBeTruthy();
expect(action.data).toEqual({ origin: ORIGIN, id: "a" });
});
it("dispatches WEB_NOTIFICATIONS_DISMISS_ALL from mark-all-read", () => {
const { container, dispatch } = renderCard(link, {
notifications: NOTIFICATIONS,
byOrigin: BY_ORIGIN,
});
fireEvent.click(
container.querySelector("button.top-sites-hover-card-mark-read")
);
expect(
dispatchedActions(dispatch).some(
action => action.type === at.WEB_NOTIFICATIONS_DISMISS_ALL
)
).toBe(true);
});
describe("notification icons", () => {
function renderWithIcon(icon, site = ORIGIN) {
const id = "icon-1";
return renderCard(
{ url: `${site}/path`, label: "Example" },
{
notifications: { [id]: { id, title: "T", origin: site, icon } },
byOrigin: { [site]: [id] },
}
);
}
it("loads an icon through the image proxy, never from the origin", () => {
const { container } = renderWithIcon("https://example.com/icon.png");
const src = container
.querySelector("img.top-sites-hover-card-notification-icon")
.getAttribute("src");
expect(src).toContain("https://img-getpocket.cdn.mozilla.net/");
expect(src).not.toBe("https://example.com/icon.png");
});
it("renders no icon when the icon cannot be proxied", () => {
// eslint-disable-next-line sdl/no-insecure-url
const { container } = renderWithIcon("http://example.com/icon.png");
expect(
container.querySelectorAll("img.top-sites-hover-card-notification-icon")
).toHaveLength(0);
});
it("renders no icon for a suppressed origin", () => {
const { container } = renderWithIcon(
"https://apnews.com/icon.png",
"https://apnews.com"
);
expect(
container.querySelectorAll("img.top-sites-hover-card-notification-icon")
).toHaveLength(0);
});
it("drops the icon on a proxy error rather than retrying the origin", () => {
const { container } = renderWithIcon("https://example.com/icon.png");
fireEvent.error(
container.querySelector("img.top-sites-hover-card-notification-icon")
);
expect(
container.querySelectorAll("img.top-sites-hover-card-notification-icon")
).toHaveLength(0);
});
});
});