Skip to content

Commit 1971104

Browse files
committed
fix: polyfill AbortSignal.timeout in transparency tests for jsdom compatibility
1 parent 0e63318 commit 1971104

1 file changed

Lines changed: 36 additions & 20 deletions

File tree

frontend/__tests__/transparency.test.tsx

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,37 @@ import StatCard from "@/components/StatCard";
1313
import SLOStatusPanel from "@/components/SLOStatusPanel";
1414
import type { SLOData } from "@/lib/transparencyHooks";
1515

16-
// ── Mock fetch globally ───────────────────────────────────────────────────
17-
const mockFetch = jest.fn();
18-
global.fetch = mockFetch;
16+
// ── jsdom polyfills ────────────────────────────────────────────────────────
17+
// jsdom does not implement AbortSignal.timeout(), which HealthBanner uses
18+
// for the readiness fetch call. Polyfill it so the mock fetch is reached.
19+
const ORIGINAL_ABORT_TIMEOUT = AbortSignal.timeout;
20+
beforeAll(() => {
21+
AbortSignal.timeout = (_ms: number) => {
22+
const controller = new AbortController();
23+
return controller.signal;
24+
};
25+
});
26+
afterAll(() => {
27+
AbortSignal.timeout = ORIGINAL_ABORT_TIMEOUT;
28+
});
29+
30+
// ── Mock fetch globally (follow adminAuth.test.ts pattern) ────────────────
31+
32+
const ORIGINAL_FETCH = global.fetch;
1933

2034
beforeEach(() => {
21-
jest.clearAllMocks();
35+
global.fetch = jest.fn();
36+
});
37+
38+
afterAll(() => {
39+
global.fetch = ORIGINAL_FETCH;
2240
});
2341

2442
// ── HealthBanner Tests ────────────────────────────────────────────────────
2543

2644
describe("HealthBanner", () => {
27-
it('shows "All Systems Operational" when readyz returns 200 and all checks OK', async () => {
28-
mockFetch.mockResolvedValueOnce({
45+
it('shows "All Systems Operational" when readyz returns 200 with all checks OK', async () => {
46+
(global.fetch as jest.Mock).mockResolvedValue({
2947
ok: true,
3048
json: async () => ({
3149
status: "ready",
@@ -39,14 +57,13 @@ describe("HealthBanner", () => {
3957

4058
render(<HealthBanner />);
4159

42-
// Wait for the fetch to resolve and state to update
4360
await screen.findByText(/All Systems Operational/i);
4461
expect(screen.getByRole("status")).toBeInTheDocument();
4562
expect(screen.getByText(/🟢/)).toBeInTheDocument();
4663
});
4764

48-
it('shows "Degraded Performance" when some subsystems are degraded', async () => {
49-
mockFetch.mockResolvedValueOnce({
65+
it('shows "Service Disruption" when subsystems are unreachable', async () => {
66+
(global.fetch as jest.Mock).mockResolvedValue({
5067
ok: true,
5168
json: async () => ({
5269
status: "not ready",
@@ -66,22 +83,24 @@ describe("HealthBanner", () => {
6683
});
6784

6885
it('shows "Service Disruption" when fetch fails', async () => {
69-
mockFetch.mockRejectedValueOnce(new Error("Network error"));
86+
(global.fetch as jest.Mock).mockRejectedValue(new Error("Network error"));
7087

7188
render(<HealthBanner />);
7289

7390
await screen.findByText(/Service Disruption/i);
7491
expect(screen.getByText(/🔴/)).toBeInTheDocument();
7592
});
7693

77-
it("shows loading skeleton initially", () => {
78-
// Don't resolve the fetch so it stays in loading state
79-
mockFetch.mockImplementationOnce(() => new Promise(() => {}));
94+
it("shows loading skeleton initially before fetch resolves", async () => {
95+
// Return a promise that never resolves so the component stays in loading state
96+
(global.fetch as jest.Mock).mockImplementation(() => new Promise(() => {}));
8097

81-
render(<HealthBanner />);
98+
const { container } = render(<HealthBanner />);
8299

83-
// Should show skeleton (checking for the container with bg-slate-50)
84-
expect(screen.getByText(/Checking status/i)).toBeInTheDocument();
100+
// The initial render shows a skeleton (animated pulse box) before the fetch resolves
101+
const skeletonBox = container.querySelector(".animate-pulse");
102+
expect(skeletonBox).toBeInTheDocument();
103+
expect(skeletonBox).toHaveClass("rounded");
85104
});
86105
});
87106

@@ -176,7 +195,7 @@ describe("SLOStatusPanel", () => {
176195
const { container } = render(<SLOStatusPanel sloData={null} isLoading={true} />);
177196

178197
// Skeleton should render a card container with animated pulse elements
179-
const skeletonCard = container.querySelector('.card');
198+
const skeletonCard = container.querySelector(".card");
180199
expect(skeletonCard).toBeInTheDocument();
181200
});
182201

@@ -206,9 +225,6 @@ describe("SLOStatusPanel", () => {
206225

207226
describe("Transparency Page Integration (Recent Donations)", () => {
208227
it("renders waiting state when no donations", () => {
209-
// We test the inline RecentDonationsFeed by rendering the full page
210-
// with zero donations. Since the full page has complex deps (fetch,
211-
// socket), we verify the loading indicator is present.
212228
render(
213229
<div>
214230
<p className="text-[#94A3B8] dark:text-[#64748B] text-sm font-body">

0 commit comments

Comments
 (0)