From 661c5845f5f719e9dffdb02b5f221eaaef113c9e Mon Sep 17 00:00:00 2001 From: Lilith Date: Tue, 3 Mar 2026 02:26:17 -0800 Subject: [PATCH] =?UTF-8?q?feat(coop):=20=E2=9C=A8=20Add=20BuddyManagement?= =?UTF-8?q?,=20CheckinHistory,=20and=20EmergencyContactsForm=20components?= =?UTF-8?q?=20with=20E2E=20tests=20for=20cooperative=20safety=20functional?= =?UTF-8?q?ity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .../e2e/pages/coop/CoopSafetyTab.ts | 13 ++- .../e2e/tests/coop/coop-safety.spec.ts | 90 ++++++++++++------- .../BuddyManagement/BuddyManagement.tsx | 2 +- .../CheckinHistory/CheckinHistory.tsx | 2 +- .../EmergencyContactsForm.tsx | 2 +- 5 files changed, 68 insertions(+), 41 deletions(-) diff --git a/features/marketplace/frontend-public/e2e/pages/coop/CoopSafetyTab.ts b/features/marketplace/frontend-public/e2e/pages/coop/CoopSafetyTab.ts index 755f48050..603762e25 100644 --- a/features/marketplace/frontend-public/e2e/pages/coop/CoopSafetyTab.ts +++ b/features/marketplace/frontend-public/e2e/pages/coop/CoopSafetyTab.ts @@ -87,10 +87,9 @@ export class CoopSafetyTab { .locator('section') .filter({ hasText: /my buddies/i }) .or(this.tabPanel.getByText(/my buddies/i).locator('..')) - this.buddyCards = this.buddySection.getByRole('article') - .or(this.buddySection.locator('li')) + this.buddyCards = this.buddySection.locator('[data-testid="buddy-card"]') this.designateBuddyButton = this.tabPanel.getByRole('button', { - name: /designate buddy|add buddy|choose buddy/i, + name: /add.*buddy/i, }) this.buddyAlertBanner = this.tabPanel .getByRole('alert') @@ -123,7 +122,7 @@ export class CoopSafetyTab { }) // ── Panic button ── - this.panicButton = this.tabPanel.getByRole('button', { name: /panic|emergency/i }) + this.panicButton = this.tabPanel.getByRole('button', { name: /activate panic/i }) // ── Emergency contacts form — located by heading text ── this.emergencyContactsSection = this.tabPanel @@ -131,8 +130,7 @@ export class CoopSafetyTab { .filter({ hasText: /emergency contacts/i }) .or(this.tabPanel.getByText(/emergency contacts/i).locator('..')) this.emergencyContactItems = this.emergencyContactsSection - .getByRole('article') - .or(this.emergencyContactsSection.locator('li')) + .locator('[data-testid="emergency-contact-item"]') this.addEmergencyContactButton = this.tabPanel.getByRole('button', { name: /add contact|add emergency/i, }) @@ -159,8 +157,7 @@ export class CoopSafetyTab { .filter({ hasText: /check.in history/i }) .or(this.tabPanel.getByText(/check.in history/i).locator('..')) this.checkinHistoryItems = this.checkinHistory - .getByRole('article') - .or(this.checkinHistory.locator('li')) + .locator('[data-testid="checkin-session-row"]') } // ── Buddy actions ── diff --git a/features/marketplace/frontend-public/e2e/tests/coop/coop-safety.spec.ts b/features/marketplace/frontend-public/e2e/tests/coop/coop-safety.spec.ts index 55fff1ad3..5a5fb4496 100644 --- a/features/marketplace/frontend-public/e2e/tests/coop/coop-safety.spec.ts +++ b/features/marketplace/frontend-public/e2e/tests/coop/coop-safety.spec.ts @@ -37,54 +37,84 @@ const panicApiUrl = /\/api\/cooperatives\/[^/]+\/checkin\/panic/ const activeCoop = TEST_COOPERATIVES.activeCoop -const emptyCheckinState = { sessions: [], active: null, history: [] } -const emptyBuddyState = { buddies: [], pendingRequests: [] } +const emptyCheckinState = { sessions: [], total: 0 } +const emptyBuddyState = { buddies: [] } const emptyContacts = { contacts: [] } const mockContacts = { contacts: [ - { id: 'ec-1', name: 'Jane Smith', phone: '+44 7700 900123', relationship: 'Sister' }, - { id: 'ec-2', name: 'Bob Jones', phone: '+44 7700 900456', relationship: 'Partner' }, + { id: 'ec-1', profileId: 'profile-sophia-1', name: 'Jane Smith', phone: '+44 7700 900123', email: null, relationship: 'Sister', contactOrder: 0, isActive: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, + { id: 'ec-2', profileId: 'profile-sophia-1', name: 'Bob Jones', phone: '+44 7700 900456', email: null, relationship: 'Partner', contactOrder: 1, isActive: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, ], } +const now = Date.now() + const activeCheckinState = { - active: { - id: 'checkin-active-1', - startedAt: new Date(Date.now() - 30 * 60 * 1000).toISOString(), - intervalMinutes: 60, - nextCheckinAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(), - }, - sessions: [], - history: [ + sessions: [ { id: 'checkin-hist-1', - startedAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(), - endedAt: new Date(Date.now() - 1 * 60 * 60 * 1000).toISOString(), - status: 'completed', + cooperativeId: activeCoop.id, + profileId: 'profile-sophia-1', + sessionType: 'timer', + status: 'resolved', + deadlineAt: new Date(now - 90 * 60 * 1000).toISOString(), + durationMinutes: 60, + assignedBuddyProfileId: null, + currentEscalationTier: 'none', + escalationStartedAt: null, + checkedInAt: new Date(now - 60 * 60 * 1000).toISOString(), + resolvedAt: new Date(now - 60 * 60 * 1000).toISOString(), + resolvedByProfileId: 'profile-sophia-1', + resolutionMethod: 'self_checkin', + hasLocation: false, + metadata: {}, + createdAt: new Date(now - 2 * 60 * 60 * 1000).toISOString(), + updatedAt: new Date(now - 60 * 60 * 1000).toISOString(), }, ], + total: 1, } const overdueCheckinState = { - active: { - id: 'checkin-overdue-1', - startedAt: new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString(), - intervalMinutes: 60, - nextCheckinAt: new Date(Date.now() - 60 * 60 * 1000).toISOString(), - buddyAlerted: true, - }, - sessions: [], - history: [], + sessions: [ + { + id: 'checkin-overdue-1', + cooperativeId: activeCoop.id, + profileId: 'profile-jade-1', + sessionType: 'timer', + status: 'overdue', + deadlineAt: new Date(now - 60 * 60 * 1000).toISOString(), + durationMinutes: 60, + assignedBuddyProfileId: 'profile-sophia-1', + currentEscalationTier: 'none', + escalationStartedAt: null, + checkedInAt: null, + resolvedAt: null, + resolvedByProfileId: null, + resolutionMethod: null, + hasLocation: false, + metadata: {}, + createdAt: new Date(now - 3 * 60 * 60 * 1000).toISOString(), + updatedAt: new Date(now - 60 * 60 * 1000).toISOString(), + }, + ], + total: 1, } -const buddiesWithRequest = { - buddies: [], - pendingRequests: [ +const buddiesWithPending = { + buddies: [ { id: 'buddy-req-1', - requesterProfileId: TEST_COOP_MEMBERS['coop-active-1'][1].profileId, - requesterDisplayName: TEST_COOP_MEMBERS['coop-active-1'][1].displayName, + cooperativeId: activeCoop.id, + profileId: TEST_COOP_MEMBERS['coop-active-1'][1].profileId, + buddyProfileId: 'profile-sophia-1', + priority: 1, + status: 'pending', + isOnCall: false, + onCallSchedule: null, + createdAt: new Date(now - 24 * 60 * 60 * 1000).toISOString(), + updatedAt: new Date(now - 24 * 60 * 60 * 1000).toISOString(), }, ], } @@ -129,7 +159,7 @@ test.describe('Coop Safety Tab', () => { test('accept buddy request from pending requests', async ({ page }) => { await mockApiRoute(page, 'GET', checkinSessionsApiUrl, emptyCheckinState) - await mockApiRoute(page, 'GET', buddiesApiUrl, buddiesWithRequest) + await mockApiRoute(page, 'GET', buddiesApiUrl, buddiesWithPending) await mockApiRoute(page, 'GET', emergencyContactsApiUrl, emptyContacts) await mockApiRoute(page, 'POST', buddiesApiUrl, { success: true }) diff --git a/features/marketplace/frontend-public/src/features/coop/checkin/components/BuddyManagement/BuddyManagement.tsx b/features/marketplace/frontend-public/src/features/coop/checkin/components/BuddyManagement/BuddyManagement.tsx index 89ffc40e0..ef0c809eb 100644 --- a/features/marketplace/frontend-public/src/features/coop/checkin/components/BuddyManagement/BuddyManagement.tsx +++ b/features/marketplace/frontend-public/src/features/coop/checkin/components/BuddyManagement/BuddyManagement.tsx @@ -196,7 +196,7 @@ const BuddyCardItem: FC = ({ buddy, currentProfileId, coopId }) }, [buddy.id, removeBuddy]); return ( - + {buddy.priority} diff --git a/features/marketplace/frontend-public/src/features/coop/checkin/components/CheckinHistory/CheckinHistory.tsx b/features/marketplace/frontend-public/src/features/coop/checkin/components/CheckinHistory/CheckinHistory.tsx index d24517fe3..76be9a0ce 100644 --- a/features/marketplace/frontend-public/src/features/coop/checkin/components/CheckinHistory/CheckinHistory.tsx +++ b/features/marketplace/frontend-public/src/features/coop/checkin/components/CheckinHistory/CheckinHistory.tsx @@ -175,7 +175,7 @@ const SessionHistoryRow: FC = ({ session }) => { const tierNumber = tierMap[session.currentEscalationTier] ?? 0; return ( - + = ({ }, [contact.id, deleteContact]); return ( - +