Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion jest.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ export default {
testEnvironment: "node",
setupFiles: ["./src/setupTests.js"],
transform: {
"^.+\\.jsx?$": "babel-jest",
"^.+\\.(m|c)?jsx?$": "babel-jest",
},
moduleNameMapper: {
"\\.(scss|css)$": "<rootDir>/src/components/__mocks__/styleMock.js",
"\\.svg$": "<rootDir>/src/components/__mocks__/svgMock.js",
"\\.(png|jpg|jpeg|ico)$": "<rootDir>/src/components/__mocks__/fileMock.js",
},
moduleFileExtensions: [
"js",
Expand Down
100 changes: 100 additions & 0 deletions src/components/Support/Support.data.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* @jest-environment jsdom
*/
// eslint-disable-next-line import/no-extraneous-dependencies
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
import { act, fireEvent, render, screen } from "@testing-library/react";
import SmallIcon from "../../assets/icon-square-small-slack.png";

// Data must be inline - jest.mock is hoisted before const declarations (TDZ)
jest.mock("./AdditionalSupporters.mjs", () => []);
jest.mock(
"./_supporters.json",
() => [
{
slug: "gold-org",
name: "Gold Org",
totalDonations: 2000000, // $20,000 - gold range ($10k-$50k)
monthlyDonations: 100000,
avatar: "https://example.com/avatar.png",
firstDonation: new Date(
Date.now() - 5 * 24 * 60 * 60 * 1000,
).toISOString(),
},
],
{ virtual: true },
);

// eslint-disable-next-line import/first
import Support from "./Support.jsx";

const AVATAR_URL = "https://example.com/avatar.png";

describe("Support with supporter data", () => {
let intersectionCallback;
let mockObserve;
let mockDisconnect;

beforeEach(() => {
mockObserve = jest.fn();
mockDisconnect = jest.fn();

Object.defineProperty(window, "IntersectionObserver", {
writable: true,
configurable: true,
value: class {
observe() {}

disconnect() {}
},
});
jest.spyOn(window, "IntersectionObserver").mockImplementation((cb) => {
intersectionCallback = cb;
return { observe: mockObserve, disconnect: mockDisconnect };
});
});

it("renders supporter links", () => {
render(<Support rank="gold" type="total" />);
// supporter link + become a sponsor link
expect(screen.getAllByRole("link").length).toBeGreaterThanOrEqual(2);
});

it("shows SmallIcon before intersection observer fires", () => {
render(<Support rank="gold" type="total" />);
const imgs = screen.getAllByRole("img");
expect(imgs[0].getAttribute("src")).toBe(SmallIcon);
});

it("shows avatar src after intersection observer fires", () => {
render(<Support rank="gold" type="total" />);
act(() => {
intersectionCallback([{ isIntersecting: true }]);
});
const imgs = screen.getAllByRole("img");
expect(imgs[0].getAttribute("src")).toBe(AVATAR_URL);
});

it("falls back to SmallIcon on image error", () => {
render(<Support rank="gold" type="total" />);
act(() => {
intersectionCallback([{ isIntersecting: true }]);
});
const imgs = screen.getAllByRole("img");
fireEvent.error(imgs[0]);
expect(imgs[0].getAttribute("src")).toBe(SmallIcon);
});

it("does not replace src if image already shows SmallIcon on error", () => {
render(<Support rank="gold" type="total" />);
// inView is false so src is already SmallIcon - error should not loop
const imgs = screen.getAllByRole("img");
fireEvent.error(imgs[0]);
expect(imgs[0].getAttribute("src")).toBe(SmallIcon);
});

it("matches snapshot with supporter data", () => {
const { container } = render(<Support rank="gold" type="total" />);
expect(container.firstChild).toMatchSnapshot();
});
});
Loading
Loading