Skip to content

Commit ba694aa

Browse files
committed
Add tests for press and team components
1 parent 608d177 commit ba694aa

8 files changed

Lines changed: 538 additions & 0 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { render, screen } from "@testing-library/react";
2+
import {
3+
facebookURL,
4+
githubURL,
5+
instagramURL,
6+
redditURL,
7+
} from "routes";
8+
import wrapper from "test/hookWrapper";
9+
10+
import SocialMediaLinks from "./SocialMediaLinks";
11+
12+
const socialLinks = [
13+
{ label: "GitHub", href: githubURL },
14+
{ label: "Instagram", href: instagramURL },
15+
{ label: "Reddit", href: redditURL },
16+
{ label: "BlueSky", href: "https://bsky.app/profile/couchers.bsky.social" },
17+
{ label: "TikTok", href: "https://www.tiktok.com/@couchersorg" },
18+
{ label: "Facebook", href: facebookURL },
19+
];
20+
21+
describe("SocialMediaLinks", () => {
22+
it("renders all 6 social links with aria-labels", () => {
23+
render(<SocialMediaLinks iconSize="1.5rem" />, { wrapper });
24+
socialLinks.forEach(({ label }) => {
25+
expect(screen.getByRole("link", { name: label })).toBeInTheDocument();
26+
});
27+
});
28+
29+
it("links point to correct URLs", () => {
30+
render(<SocialMediaLinks iconSize="1.5rem" />, { wrapper });
31+
socialLinks.forEach(({ label, href }) => {
32+
expect(screen.getByRole("link", { name: label })).toHaveAttribute(
33+
"href",
34+
href,
35+
);
36+
});
37+
});
38+
39+
it("links open in a new tab", () => {
40+
render(<SocialMediaLinks iconSize="1.5rem" />, { wrapper });
41+
socialLinks.forEach(({ label }) => {
42+
expect(screen.getByRole("link", { name: label })).toHaveAttribute(
43+
"target",
44+
"_blank",
45+
);
46+
});
47+
});
48+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { render, screen } from "@testing-library/react";
2+
import wrapper from "test/hookWrapper";
3+
import i18n from "test/i18n";
4+
5+
import About from "./About";
6+
7+
const { t } = i18n;
8+
9+
describe("About", () => {
10+
it("renders all 3 cards", () => {
11+
render(<About />, { wrapper });
12+
expect(
13+
screen.getByText(t("press:about_mission_heading")),
14+
).toBeInTheDocument();
15+
expect(
16+
screen.getByText(t("press:about_blog_heading")),
17+
).toBeInTheDocument();
18+
expect(
19+
screen.getByText(t("press:about_foundation_heading")),
20+
).toBeInTheDocument();
21+
});
22+
23+
it("card headings render as h3", () => {
24+
render(<About />, { wrapper });
25+
const headings = screen.getAllByRole("heading", { level: 3 });
26+
expect(headings).toHaveLength(3);
27+
});
28+
29+
it("each Read more link has a unique aria-label", () => {
30+
render(<About />, { wrapper });
31+
const headings = [
32+
t("press:about_mission_heading"),
33+
t("press:about_blog_heading"),
34+
t("press:about_foundation_heading"),
35+
];
36+
headings.forEach((heading) => {
37+
expect(
38+
screen.getByRole("link", {
39+
name: `${t("press:read_more")}: ${heading}`,
40+
}),
41+
).toBeInTheDocument();
42+
});
43+
});
44+
});
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { UseQueryResult } from "@tanstack/react-query";
2+
import { render, screen } from "@testing-library/react";
3+
import { RpcError } from "grpc-web";
4+
import { GetVolunteersRes, Volunteer } from "proto/public_pb";
5+
import wrapper from "test/hookWrapper";
6+
import i18n from "test/i18n";
7+
8+
import Facts from "./Facts";
9+
10+
const { t } = i18n;
11+
12+
const makeVolunteers = (
13+
current: Volunteer.AsObject[] = [],
14+
past: Volunteer.AsObject[] = [],
15+
isLoading = false,
16+
) =>
17+
({
18+
data: { currentVolunteersList: current, pastVolunteersList: past },
19+
isLoading,
20+
}) as UseQueryResult<GetVolunteersRes.AsObject, RpcError>;
21+
22+
const mockSignupInfo = {
23+
userCount: "80000",
24+
lastSignup: "2024-01-01T00:00:00Z",
25+
lastLocation: "Berlin",
26+
};
27+
28+
describe("Facts", () => {
29+
beforeEach(() => {
30+
global.fetch = jest.fn().mockResolvedValue({
31+
ok: true,
32+
json: () => Promise.resolve(mockSignupInfo),
33+
});
34+
});
35+
36+
afterEach(() => {
37+
jest.restoreAllMocks();
38+
});
39+
40+
it("shows skeletons while loading and hides stat text", () => {
41+
const { container } = render(
42+
<Facts volunteers={makeVolunteers([], [], true)} />,
43+
{ wrapper },
44+
);
45+
expect(container.querySelectorAll(".MuiSkeleton-root")).toHaveLength(4);
46+
expect(screen.queryByText(/members/)).not.toBeInTheDocument();
47+
expect(screen.queryByText(/countries/)).not.toBeInTheDocument();
48+
expect(screen.queryByText(/volunteers/)).not.toBeInTheDocument();
49+
});
50+
51+
it("shows user count from API", async () => {
52+
render(<Facts volunteers={makeVolunteers()} />, { wrapper });
53+
expect(
54+
await screen.findByText(t("landing:num_users2", { count: 80000 })),
55+
).toBeInTheDocument();
56+
});
57+
58+
it("falls back to 77000 when fetch fails", async () => {
59+
global.fetch = jest.fn().mockRejectedValue(new Error("Network error"));
60+
render(<Facts volunteers={makeVolunteers()} />, { wrapper });
61+
expect(
62+
await screen.findByText(t("landing:num_users2", { count: 77000 })),
63+
).toBeInTheDocument();
64+
});
65+
66+
it("falls back to 77000 when the response is not ok", async () => {
67+
global.fetch = jest.fn().mockResolvedValue({
68+
ok: false,
69+
json: () => Promise.resolve({}),
70+
});
71+
render(<Facts volunteers={makeVolunteers()} />, { wrapper });
72+
expect(
73+
await screen.findByText(t("landing:num_users2", { count: 77000 })),
74+
).toBeInTheDocument();
75+
});
76+
77+
it("falls back to 77000 when userCount is not a valid number", async () => {
78+
global.fetch = jest.fn().mockResolvedValue({
79+
ok: true,
80+
json: () => Promise.resolve({ ...mockSignupInfo, userCount: "bad" }),
81+
});
82+
render(<Facts volunteers={makeVolunteers()} />, { wrapper });
83+
expect(
84+
await screen.findByText(t("landing:num_users2", { count: 77000 })),
85+
).toBeInTheDocument();
86+
});
87+
88+
it("shows countries count", async () => {
89+
render(<Facts volunteers={makeVolunteers()} />, { wrapper });
90+
expect(
91+
await screen.findByText(t("landing:num_countries2", { count: 180 })),
92+
).toBeInTheDocument();
93+
});
94+
95+
it("shows last signup when lastSignup is present", async () => {
96+
render(<Facts volunteers={makeVolunteers()} />, { wrapper });
97+
expect(await screen.findByText(/Last signup/)).toBeInTheDocument();
98+
});
99+
100+
it("hides last signup stat when lastSignup is absent", async () => {
101+
global.fetch = jest.fn().mockResolvedValue({
102+
ok: true,
103+
json: () => Promise.resolve({ ...mockSignupInfo, lastSignup: "" }),
104+
});
105+
render(<Facts volunteers={makeVolunteers()} />, { wrapper });
106+
await screen.findByText(/members/);
107+
expect(screen.queryByText(/Last signup/)).not.toBeInTheDocument();
108+
});
109+
110+
it("shows combined current and past volunteer count", async () => {
111+
const current = Array(4).fill({}) as Volunteer.AsObject[];
112+
const past = Array(2).fill({}) as Volunteer.AsObject[];
113+
render(<Facts volunteers={makeVolunteers(current, past)} />, { wrapper });
114+
expect(
115+
await screen.findByText(t("press:num_volunteers2", { count: 6 })),
116+
).toBeInTheDocument();
117+
});
118+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { render, screen } from "@testing-library/react";
2+
import wrapper from "test/hookWrapper";
3+
import i18n from "test/i18n";
4+
5+
import Hero from "./Hero";
6+
7+
const { t } = i18n;
8+
9+
jest.mock("../dashboard/Hero/HeroImageAttribution", () => () => null);
10+
11+
describe("Hero", () => {
12+
it("renders the page title as h1", () => {
13+
render(<Hero />, { wrapper });
14+
expect(
15+
screen.getByRole("heading", {
16+
level: 1,
17+
name: t("press:hero_title"),
18+
}),
19+
).toBeInTheDocument();
20+
});
21+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { render, screen } from "@testing-library/react";
2+
import wrapper from "test/hookWrapper";
3+
import i18n from "test/i18n";
4+
5+
import MediaAssets from "./MediaAssets";
6+
7+
const { t } = i18n;
8+
9+
describe("MediaAssets", () => {
10+
it("mobile image has alt text", () => {
11+
render(<MediaAssets />, { wrapper });
12+
expect(
13+
screen.getByAltText(t("press:mobile_image_alt")),
14+
).toBeInTheDocument();
15+
});
16+
17+
it("download buttons have distinct aria-labels", () => {
18+
render(<MediaAssets />, { wrapper });
19+
expect(
20+
screen.getByRole("link", { name: t("press:download_logo_aria_label") }),
21+
).toBeInTheDocument();
22+
expect(
23+
screen.getByRole("link", {
24+
name: t("press:download_mobile_images_aria_label"),
25+
}),
26+
).toBeInTheDocument();
27+
});
28+
29+
it("download links point to correct files", () => {
30+
render(<MediaAssets />, { wrapper });
31+
expect(
32+
screen.getByRole("link", { name: t("press:download_logo_aria_label") }),
33+
).toHaveAttribute("href", "/img/press/downloads/couchers-logo-assets.zip");
34+
expect(
35+
screen.getByRole("link", {
36+
name: t("press:download_mobile_images_aria_label"),
37+
}),
38+
).toHaveAttribute(
39+
"href",
40+
"/img/press/downloads/couchers-mobile-images.zip",
41+
);
42+
});
43+
44+
it("download links have download attributes", () => {
45+
render(<MediaAssets />, { wrapper });
46+
expect(
47+
screen.getByRole("link", { name: t("press:download_logo_aria_label") }),
48+
).toHaveAttribute("download", "couchers-logo-assets.zip");
49+
expect(
50+
screen.getByRole("link", {
51+
name: t("press:download_mobile_images_aria_label"),
52+
}),
53+
).toHaveAttribute("download", "couchers-mobile-images.zip");
54+
});
55+
});
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { render, screen } from "@testing-library/react";
2+
import { useListVolunteers } from "features/communities/hooks";
3+
import wrapper from "test/hookWrapper";
4+
import i18n from "test/i18n";
5+
6+
import Press from "./Press";
7+
8+
const { t } = i18n;
9+
10+
jest.mock("../dashboard/Hero/HeroImageAttribution", () => () => null);
11+
jest.mock("features/communities/hooks");
12+
13+
const mockUseListVolunteers = useListVolunteers as jest.MockedFunction<
14+
typeof useListVolunteers
15+
>;
16+
17+
describe("Press", () => {
18+
beforeEach(() => {
19+
mockUseListVolunteers.mockReturnValue({
20+
data: { currentVolunteersList: [], pastVolunteersList: [] },
21+
isLoading: false,
22+
} as unknown as ReturnType<typeof useListVolunteers>);
23+
24+
global.fetch = jest.fn().mockResolvedValue({
25+
ok: true,
26+
json: () =>
27+
Promise.resolve({
28+
userCount: "80000",
29+
lastSignup: "2024-01-01T00:00:00Z",
30+
lastLocation: "Berlin",
31+
}),
32+
});
33+
});
34+
35+
afterEach(() => {
36+
jest.restoreAllMocks();
37+
});
38+
39+
it("renders all sections", async () => {
40+
render(<Press />, { wrapper });
41+
expect(
42+
screen.getByRole("heading", { level: 1, name: t("press:hero_title") }),
43+
).toBeInTheDocument();
44+
expect(
45+
screen.getByRole("heading", {
46+
level: 2,
47+
name: t("press:facts_subheading"),
48+
}),
49+
).toBeInTheDocument();
50+
expect(
51+
screen.getByRole("heading", {
52+
level: 2,
53+
name: t("press:about_subheading"),
54+
}),
55+
).toBeInTheDocument();
56+
expect(
57+
screen.getByRole("heading", {
58+
level: 2,
59+
name: t("press:download_subheading"),
60+
}),
61+
).toBeInTheDocument();
62+
expect(
63+
screen.getByRole("heading", {
64+
level: 2,
65+
name: t("press:team_subheading"),
66+
}),
67+
).toBeInTheDocument();
68+
expect(
69+
screen.getByRole("heading", {
70+
level: 2,
71+
name: t("press:social_media_subheading"),
72+
}),
73+
).toBeInTheDocument();
74+
expect(
75+
screen.getByRole("heading", {
76+
level: 2,
77+
name: t("press:press_coverage_subheading"),
78+
}),
79+
).toBeInTheDocument();
80+
});
81+
});

0 commit comments

Comments
 (0)