From cb5f0dcbd8fd322e4b9045137cc6758eee7056da Mon Sep 17 00:00:00 2001 From: Shreeya Adhikari Date: Mon, 3 Aug 2026 18:29:36 -0400 Subject: [PATCH 1/5] wire frontend to real APIs --- apps/frontend/src/app/accounts/page.tsx | 35 +++++++- apps/frontend/src/app/donations/page.tsx | 108 +++++++++++++++++------ apps/frontend/src/app/donors/page.tsx | 50 +++++++---- 3 files changed, 146 insertions(+), 47 deletions(-) diff --git a/apps/frontend/src/app/accounts/page.tsx b/apps/frontend/src/app/accounts/page.tsx index 1c5cd0f4..0905c3da 100644 --- a/apps/frontend/src/app/accounts/page.tsx +++ b/apps/frontend/src/app/accounts/page.tsx @@ -1,6 +1,7 @@ -'use client'; +"use client"; -import React from 'react'; +import React, { useEffect, useState } from 'react'; +import { useApi } from '@/hooks/useApi'; import StaffCard from '../components/StaffCard'; import { User } from '@/types'; @@ -28,18 +29,44 @@ export const teamMembers = mockUsers.filter(u => !u.is_admin); export default function AccountsPage() { + const api = useApi(); + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + async function fetchUsers() { + try { + const json = await api.get('http://localhost:3001/users'); + const list = Array.isArray(json) ? json : (json && 'data' in json ? json.data : []); + setUsers(list); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load users'); + setUsers([]); + } finally { + setLoading(false); + } + } + fetchUsers(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const shownFacilitation = users.length ? users.filter(u => u.is_admin) : facilitationTeam; + const shownTeam = users.length ? users.filter(u => !u.is_admin) : teamMembers; return (

Accounts

Core BRANCH Facilitation Team

- {facilitationTeam.map(user => ( + {loading &&

Loading users...

} + {error &&

{error}

} + {!loading && !error && shownFacilitation.map(user => ( ))}

BRANCH Team Members

- {teamMembers.map(user => ( + {!loading && !error && shownTeam.map(user => ( ))}
diff --git a/apps/frontend/src/app/donations/page.tsx b/apps/frontend/src/app/donations/page.tsx index 33c60627..ef47e8c6 100644 --- a/apps/frontend/src/app/donations/page.tsx +++ b/apps/frontend/src/app/donations/page.tsx @@ -1,5 +1,5 @@ 'use client' -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import NavBar from "../components/Navbar"; import { HStack, Input, Button, Table, Dialog, Portal, CloseButton, Stack } from "@chakra-ui/react"; import TextInputField from '../components/TextInputField'; @@ -14,29 +14,79 @@ type Donation = { project_name: string; amount: number; }; +import { useApi } from '@/hooks/useApi'; -const mockDonors = ['Green Future Foundation', 'Horizon Trust', 'Bright Path Nonprofit', 'Unity Giving Circle', 'Sunrise Community Fund']; -const mockProjects = ['Clean Water Initiative', 'Youth Mentorship Program', 'Food Security Drive', 'Urban Garden Project', 'STEM Education Fund']; +const donorsBase = 'http://localhost:3003'; +const projectsBase = 'http://localhost:3002'; -const mockDonations: Donation[] = [ - { donor_id: 1, date: '03/12/2024', project_name: 'Clean Water Initiative', amount: 5000 }, - { donor_id: 2, date: '01/05/2024', project_name: 'Youth Mentorship Program', amount: 12000 }, - { donor_id: 3, date: '02/28/2024', project_name: 'Food Security Drive', amount: 750 }, - { donor_id: 4, date: '03/30/2024', project_name: 'Urban Garden Project', amount: 3200 }, - { donor_id: 5, date: '04/01/2024', project_name: 'STEM Education Fund', amount: 8500 }, - { donor_id: 6, date: '02/14/2024', project_name: 'Shelter Renovation', amount: 1500 }, - { donor_id: 7, date: '01/20/2024', project_name: 'Mental Health Outreach', amount: 20000 }, - { donor_id: 8, date: '03/05/2024', project_name: 'Digital Literacy Program', amount: 9750 }, - { donor_id: 9, date: '04/10/2024', project_name: 'Community Health Fair', amount: 4300 }, - { donor_id: 10, date: '03/22/2024', project_name: 'After-School Arts', amount: 600 }, -]; export default function DonationsPage() { const [currentPage, setCurrentPage] = useState(1); const rowsPerPage = 10; - const totalPages = Math.ceil(mockDonations.length / rowsPerPage); - const currentDonations = mockDonations.slice( + const api = useApi(); + const [donations, setDonations] = useState([]); + const [donorNames, setDonorNames] = useState([]); + const [projectNames, setProjectNames] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + async function loadAll() { + try { + const [donationsJson, donorsJson, projectsJson] = await Promise.all([ + api.get(`${donorsBase}/donations`), + api.get(`${donorsBase}/donors`), + api.get(`${projectsBase}/projects`), + ]); + + const dList = Array.isArray(donationsJson) ? donationsJson : (donationsJson && 'data' in donationsJson ? donationsJson.data : []); + const dn = Array.isArray(donorsJson) ? donorsJson : (donorsJson && 'data' in donorsJson ? donorsJson.data : []); + const pn = Array.isArray(projectsJson) ? projectsJson : (projectsJson && 'data' in projectsJson ? projectsJson.data : []); + + setDonations(dList); + // donors API may return objects; if so map to organization names + const dnArray: unknown[] = dn as unknown[]; + const donorNamesMapped = dnArray + .map((d) => { + if (typeof d === 'string') return d; + if (d && typeof d === 'object' && 'organization' in d) { + const maybeOrg = (d as { [key: string]: unknown })['organization']; + if (typeof maybeOrg === 'string') return maybeOrg; + } + return ''; + }) + .filter((s): s is string => Boolean(s)); + + const pnArray: unknown[] = pn as unknown[]; + const projectNamesMapped = pnArray + .map((p) => { + if (typeof p === 'string') return p; + if (p && typeof p === 'object' && 'name' in p) { + const maybeName = (p as { [key: string]: unknown })['name']; + if (typeof maybeName === 'string') return maybeName; + } + return ''; + }) + .filter((s): s is string => Boolean(s)); + + setDonorNames(donorNamesMapped); + setProjectNames(projectNamesMapped); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load donations data'); + setDonations([]); + setDonorNames([]); + setProjectNames([]); + } finally { + setLoading(false); + } + } + loadAll(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const totalPages = Math.max(1, Math.ceil(donations.length / rowsPerPage)); + const currentDonations = donations.slice( (currentPage - 1) * rowsPerPage, currentPage * rowsPerPage ); @@ -104,7 +154,7 @@ export default function DonationsPage() { {showFilter && (
{showSort && (
- setSelectedSort(val as string)} - /> + setSelectedSort(val as string)} + />
)}
@@ -174,7 +224,7 @@ export default function DonationsPage() { {dateError && Enter a valid date}
{donorError && Select a donor} + {loading &&

Loading donations...

} + {error &&

{error}

} + {!loading && !error && ( @@ -234,6 +287,7 @@ export default function DonationsPage() { ))} + )}
diff --git a/apps/frontend/src/app/donors/page.tsx b/apps/frontend/src/app/donors/page.tsx index 152d00ab..7cd4e43b 100644 --- a/apps/frontend/src/app/donors/page.tsx +++ b/apps/frontend/src/app/donors/page.tsx @@ -1,5 +1,5 @@ 'use client' -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import NavBar from "../components/Navbar"; import { HStack, Input, Button, Table, Dialog, Portal, CloseButton, Stack } from "@chakra-ui/react"; import TextInputField from '../components/TextInputField'; @@ -16,26 +16,40 @@ type Donor = { num_projects: number; last_donation: string | null; }; +import { useApi } from '@/hooks/useApi'; + +// fetched from API +const apiBase = 'http://localhost:3003'; -const mockDonors: Donor[] = [ - { donor_id: 1, organization: 'Green Future Foundation', contact_name: 'Alice Chen', contact_email: 'alice@greenfuture.org', num_projects: 4, last_donation: '03/12/2024' }, - { donor_id: 2, organization: 'Horizon Trust', contact_name: 'James Patel', contact_email: 'james@horizontrust.org', num_projects: 2, last_donation: '01/05/2024' }, - { donor_id: 3, organization: 'Bright Path Nonprofit', contact_name: null, contact_email: null, num_projects: 7, last_donation: '02/28/2024' }, - { donor_id: 4, organization: 'Unity Giving Circle', contact_name: 'Maria Lopez', contact_email: 'maria@unitygiving.org', num_projects: 1, last_donation: '03/30/2024' }, - { donor_id: 5, organization: 'Sunrise Community Fund', contact_name: 'David Kim', contact_email: 'david@sunrisefund.org', num_projects: 3, last_donation: '04/01/2024' }, - { donor_id: 6, organization: 'Blue Ridge Giving', contact_name: 'Sarah Thompson', contact_email: 'sarah@blueridge.org', num_projects: 5, last_donation: '02/14/2024' }, - { donor_id: 7, organization: 'Maple Leaf Charitable Trust', contact_name: null, contact_email: null, num_projects: 2, last_donation: '01/20/2024' }, - { donor_id: 8, organization: 'Evergreen Partners', contact_name: 'Rachel Singh', contact_email: 'rachel@evergreenpartners.org', num_projects: 6, last_donation: '03/05/2024' }, - { donor_id: 9, organization: 'New Horizons Society', contact_name: 'Tom Bradley', contact_email: 'tom@newhorizons.org', num_projects: 9, last_donation: '04/10/2024' }, - { donor_id: 10, organization: 'Coastal Care Foundation', contact_name: 'Nina Rossi', contact_email: 'nina@coastalcare.org', num_projects: 3, last_donation: '03/22/2024' }, -]; export default function DonorsPage() { const [currentPage, setCurrentPage] = useState(1); const rowsPerPage = 10; - const totalPages = Math.ceil(mockDonors.length / rowsPerPage); - const currentDonors = mockDonors.slice( + const api = useApi(); + const [donors, setDonors] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + async function fetchDonors() { + try { + const json = await api.get(`${apiBase}/donors`); + const list = Array.isArray(json) ? json : (json && 'data' in json ? json.data : []); + setDonors(list); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load donors'); + setDonors([]); + } finally { + setLoading(false); + } + } + fetchDonors(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const totalPages = Math.max(1, Math.ceil(donors.length / rowsPerPage)); + const currentDonors = donors.slice( (currentPage - 1) * rowsPerPage, currentPage * rowsPerPage ); @@ -49,7 +63,7 @@ export default function DonorsPage() { const [showFilter, setShowFilter] = useState(false); const [selectedDonor, setSelectedDonor] = useState(''); - const donorNames = mockDonors.map(d => d.organization); + const donorNames = donors.map(d => d.organization); const [showSort, setShowSort] = useState(false); const [selectedSort, setSelectedSort] = useState(''); @@ -186,6 +200,9 @@ export default function DonorsPage() { + {loading &&

Loading donors...

} + {error &&

{error}

} + {!loading && !error && ( @@ -212,6 +229,7 @@ export default function DonorsPage() { ))} + )}
From 9fcf41bef68169aa209622fce6e190314012a70f Mon Sep 17 00:00:00 2001 From: Shreeya Adhikari Date: Mon, 3 Aug 2026 19:01:50 -0400 Subject: [PATCH 2/5] updated tests to reflect changes --- .../test/components/AccountsPage.test.tsx | 38 +++++++++++-------- .../test/components/Donations.test.tsx | 10 ++--- apps/frontend/test/components/Donors.test.tsx | 10 ++--- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/apps/frontend/test/components/AccountsPage.test.tsx b/apps/frontend/test/components/AccountsPage.test.tsx index 1f9e6c90..9c253062 100644 --- a/apps/frontend/test/components/AccountsPage.test.tsx +++ b/apps/frontend/test/components/AccountsPage.test.tsx @@ -9,27 +9,33 @@ describe('AccountsPage', () => { expect(screen.getByText('BRANCH Team Members')).toBeInTheDocument(); }); - it('renders the correct staff cards in the facilitation section', () => { + it('renders the correct staff cards in the facilitation section', async () => { render(); - const section = screen.getByText('Core BRANCH Facilitation Team').closest('div'); - const cards = section?.querySelectorAll('[data-testid="staff-card"]'); + const sectionHeader = await screen.findByText('Core BRANCH Facilitation Team'); + await waitFor(() => { + const section = sectionHeader.closest('div'); + const cards = section?.querySelectorAll('[data-testid="staff-card"]'); - if (facilitationTeam.length === 0) { - expect(cards?.length).toBe(0); - } else { - expect(cards?.length).toBeGreaterThan(0); - } + if (facilitationTeam.length === 0) { + expect(cards?.length).toBe(0); + } else { + expect(cards?.length).toBeGreaterThan(0); + } + }); }); - it('renders the correct staff cards in the team members section', () => { + it('renders the correct staff cards in the team members section', async () => { render(); - const section = screen.getByText('BRANCH Team Members').closest('div'); - const cards = section?.querySelectorAll('[data-testid="staff-card"]'); + const sectionHeader = await screen.findByText('BRANCH Team Members'); + await waitFor(() => { + const section = sectionHeader.closest('div'); + const cards = section?.querySelectorAll('[data-testid="staff-card"]'); - if (teamMembers.length === 0) { - expect(cards?.length).toBe(0); - } else { - expect(cards?.length).toBeGreaterThan(0); - } + if (teamMembers.length === 0) { + expect(cards?.length).toBe(0); + } else { + expect(cards?.length).toBeGreaterThan(0); + } + }); }); }); \ No newline at end of file diff --git a/apps/frontend/test/components/Donations.test.tsx b/apps/frontend/test/components/Donations.test.tsx index 1ae3c91e..56379393 100644 --- a/apps/frontend/test/components/Donations.test.tsx +++ b/apps/frontend/test/components/Donations.test.tsx @@ -19,12 +19,12 @@ describe('Donations Page Component', () => { expect(screen.getByText('New Donation')).toBeInTheDocument(); }); - it('renders the table with correct headers', () => { + it('renders the table with correct headers', async () => { render(); - expect(screen.getByText('Date')).toBeInTheDocument(); - expect(screen.getByText('Donor ID')).toBeInTheDocument(); - expect(screen.getByText('Project Name')).toBeInTheDocument(); - expect(screen.getByText('Amount')).toBeInTheDocument(); + expect(await screen.findByText('Date')).toBeInTheDocument(); + expect(await screen.findByText('Donor ID')).toBeInTheDocument(); + expect(await screen.findByText('Project Name')).toBeInTheDocument(); + expect(await screen.findByText('Amount')).toBeInTheDocument(); }); it('renders left and right pagination arrows', () => { diff --git a/apps/frontend/test/components/Donors.test.tsx b/apps/frontend/test/components/Donors.test.tsx index dda34b53..085b9353 100644 --- a/apps/frontend/test/components/Donors.test.tsx +++ b/apps/frontend/test/components/Donors.test.tsx @@ -19,12 +19,12 @@ describe('Donors Page', () => { expect(screen.getByText('New Donor')).toBeInTheDocument(); }); - it('renders the table with correct headers', () => { + it('renders the table with correct headers', async () => { render(); - expect(screen.getByText('Donor ID')).toBeInTheDocument(); - expect(screen.getByText('Donor Name')).toBeInTheDocument(); - expect(screen.getByText('# of Projects')).toBeInTheDocument(); - expect(screen.getByText('Last Donation')).toBeInTheDocument(); + expect(await screen.findByText('Donor ID')).toBeInTheDocument(); + expect(await screen.findByText('Donor Name')).toBeInTheDocument(); + expect(await screen.findByText('# of Projects')).toBeInTheDocument(); + expect(await screen.findByText('Last Donation')).toBeInTheDocument(); }); it('renders left and right pagination arrows', () => { From 45eaa69863a5e1dfeabeb8631edc220e52033016 Mon Sep 17 00:00:00 2001 From: Shreeya Adhikari Date: Mon, 3 Aug 2026 19:05:13 -0400 Subject: [PATCH 3/5] fixed typescript error --- apps/frontend/test/components/AccountsPage.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/test/components/AccountsPage.test.tsx b/apps/frontend/test/components/AccountsPage.test.tsx index 9c253062..8ce3ea6b 100644 --- a/apps/frontend/test/components/AccountsPage.test.tsx +++ b/apps/frontend/test/components/AccountsPage.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '../utils'; +import { render, screen, waitFor } from '../utils'; import AccountsPage, { facilitationTeam, teamMembers } from '@/app/accounts/page'; describe('AccountsPage', () => { From 6ee81dfa1759a22fcbcb9d81035e0575b1f92f55 Mon Sep 17 00:00:00 2001 From: Shreeya Adhikari Date: Mon, 3 Aug 2026 19:29:48 -0400 Subject: [PATCH 4/5] updated tests --- apps/frontend/test/components/AccountsPage.test.tsx | 9 ++++++++- apps/frontend/test/components/Donations.test.tsx | 9 ++++++++- apps/frontend/test/components/Donors.test.tsx | 9 ++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/apps/frontend/test/components/AccountsPage.test.tsx b/apps/frontend/test/components/AccountsPage.test.tsx index 8ce3ea6b..b9190bc0 100644 --- a/apps/frontend/test/components/AccountsPage.test.tsx +++ b/apps/frontend/test/components/AccountsPage.test.tsx @@ -1,7 +1,14 @@ -import { render, screen, waitFor } from '../utils'; +import { render, screen, waitFor, signIn, signOut } from '../utils'; import AccountsPage, { facilitationTeam, teamMembers } from '@/app/accounts/page'; describe('AccountsPage', () => { + beforeEach(() => { + signIn(); + }); + afterEach(() => { + signOut(); + }); + it('renders the headings', () => { render(); expect(screen.getByText('Accounts')).toBeInTheDocument(); diff --git a/apps/frontend/test/components/Donations.test.tsx b/apps/frontend/test/components/Donations.test.tsx index 56379393..54b34328 100644 --- a/apps/frontend/test/components/Donations.test.tsx +++ b/apps/frontend/test/components/Donations.test.tsx @@ -1,7 +1,14 @@ -import { render, screen, fireEvent, waitFor } from '../utils'; +import { render, screen, fireEvent, waitFor, signIn, signOut } from '../utils'; import Donations from '@/app/donations/page'; describe('Donations Page Component', () => { + beforeEach(() => { + signIn(); + }); + afterEach(() => { + signOut(); + }); + it('renders the donations heading', () => { render(); expect(screen.getByText('Donations', { selector: 'h1' })).toBeInTheDocument(); diff --git a/apps/frontend/test/components/Donors.test.tsx b/apps/frontend/test/components/Donors.test.tsx index 085b9353..c29462e6 100644 --- a/apps/frontend/test/components/Donors.test.tsx +++ b/apps/frontend/test/components/Donors.test.tsx @@ -1,7 +1,14 @@ -import { render, screen, fireEvent, waitFor } from '../utils'; +import { render, screen, fireEvent, waitFor, signIn, signOut } from '../utils'; import Donors from '@/app/donors/page'; describe('Donors Page', () => { + beforeEach(() => { + signIn(); + }); + afterEach(() => { + signOut(); + }); + it('renders the Donors heading', () => { render(); expect(screen.getByText('Donors', { selector: 'h1' })).toBeInTheDocument(); From 3c062148f041330cac69aefa76da98c4804092d0 Mon Sep 17 00:00:00 2001 From: Shreeya Adhikari Date: Mon, 3 Aug 2026 19:44:14 -0400 Subject: [PATCH 5/5] tests: inline auth/fetch mocks for tests --- .../test/components/AccountsPage.test.tsx | 18 ++++++++++-- .../test/components/Donations.test.tsx | 28 +++++++++++++++++-- apps/frontend/test/components/Donors.test.tsx | 21 ++++++++++++-- 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/apps/frontend/test/components/AccountsPage.test.tsx b/apps/frontend/test/components/AccountsPage.test.tsx index b9190bc0..1072453b 100644 --- a/apps/frontend/test/components/AccountsPage.test.tsx +++ b/apps/frontend/test/components/AccountsPage.test.tsx @@ -1,12 +1,24 @@ -import { render, screen, waitFor, signIn, signOut } from '../utils'; +import { render, screen, waitFor } from '../utils'; import AccountsPage, { facilitationTeam, teamMembers } from '@/app/accounts/page'; describe('AccountsPage', () => { beforeEach(() => { - signIn(); + localStorage.setItem('branch_access_token', 'fake.access.token'); + localStorage.setItem('branch_id_token', 'fake.id.token'); + localStorage.setItem('branch_refresh_token', 'fake.refresh'); + + global.fetch = jest.fn().mockImplementation((input: RequestInfo) => { + const url = typeof input === 'string' ? input : (input as Request).url; + if (url.includes('/auth/me')) { + return Promise.resolve({ ok: true, status: 200, json: async () => ({ userId: 1, cognitoSub: 'sub-test', email: 'test@example.com', name: 'Test User', isAdmin: false }) } as unknown as Response); + } + return Promise.resolve({ ok: true, status: 200, json: async () => [] } as unknown as Response); + }); }); + afterEach(() => { - signOut(); + jest.restoreAllMocks(); + localStorage.clear(); }); it('renders the headings', () => { diff --git a/apps/frontend/test/components/Donations.test.tsx b/apps/frontend/test/components/Donations.test.tsx index 54b34328..4ff70170 100644 --- a/apps/frontend/test/components/Donations.test.tsx +++ b/apps/frontend/test/components/Donations.test.tsx @@ -1,12 +1,34 @@ -import { render, screen, fireEvent, waitFor, signIn, signOut } from '../utils'; +import { render, screen, fireEvent, waitFor } from '../utils'; import Donations from '@/app/donations/page'; describe('Donations Page Component', () => { beforeEach(() => { - signIn(); + // seed tokens expected by the app + localStorage.setItem('branch_access_token', 'fake.access.token'); + localStorage.setItem('branch_id_token', 'fake.id.token'); + localStorage.setItem('branch_refresh_token', 'fake.refresh'); + + global.fetch = jest.fn().mockImplementation((input: RequestInfo) => { + const url = typeof input === 'string' ? input : (input as Request).url; + if (url.includes('/auth/me')) { + return Promise.resolve({ ok: true, status: 200, json: async () => ({ userId: 1, cognitoSub: 'sub-test', email: 'test@example.com', name: 'Test User', isAdmin: false }) } as unknown as Response); + } + if (url.includes('/donations')) { + return Promise.resolve({ ok: true, status: 200, json: async () => ([{ donor_id: 1, date: '2026-01-01', project_name: 'Proj Alpha', amount: 100 }]) } as unknown as Response); + } + if (url.includes('/donors')) { + return Promise.resolve({ ok: true, status: 200, json: async () => ([{ donor_id: 1, organization: 'Org A' }]) } as unknown as Response); + } + if (url.includes('/projects')) { + return Promise.resolve({ ok: true, status: 200, json: async () => ([{ name: 'Proj Alpha' }]) } as unknown as Response); + } + return Promise.resolve({ ok: true, status: 200, json: async () => [] } as unknown as Response); + }); }); + afterEach(() => { - signOut(); + jest.restoreAllMocks(); + localStorage.clear(); }); it('renders the donations heading', () => { diff --git a/apps/frontend/test/components/Donors.test.tsx b/apps/frontend/test/components/Donors.test.tsx index c29462e6..fd58f8d2 100644 --- a/apps/frontend/test/components/Donors.test.tsx +++ b/apps/frontend/test/components/Donors.test.tsx @@ -1,12 +1,27 @@ -import { render, screen, fireEvent, waitFor, signIn, signOut } from '../utils'; +import { render, screen, fireEvent, waitFor } from '../utils'; import Donors from '@/app/donors/page'; describe('Donors Page', () => { beforeEach(() => { - signIn(); + localStorage.setItem('branch_access_token', 'fake.access.token'); + localStorage.setItem('branch_id_token', 'fake.id.token'); + localStorage.setItem('branch_refresh_token', 'fake.refresh'); + + global.fetch = jest.fn().mockImplementation((input: RequestInfo) => { + const url = typeof input === 'string' ? input : (input as Request).url; + if (url.includes('/auth/me')) { + return Promise.resolve({ ok: true, status: 200, json: async () => ({ userId: 1, cognitoSub: 'sub-test', email: 'test@example.com', name: 'Test User', isAdmin: false }) } as unknown as Response); + } + if (url.includes('/donors')) { + return Promise.resolve({ ok: true, status: 200, json: async () => ([{ donor_id: 1, organization: 'Org A', contact_name: null, contact_email: null, num_projects: 1, last_donation: '2026-01-01' }]) } as unknown as Response); + } + return Promise.resolve({ ok: true, status: 200, json: async () => [] } as unknown as Response); + }); }); + afterEach(() => { - signOut(); + jest.restoreAllMocks(); + localStorage.clear(); }); it('renders the Donors heading', () => {