import { test, expect } from './msw-setup' import type { StoredEvent } from '../src/composables/useEventStorage' const STORAGE_KEY = 'fete:events' const organizerEvent: StoredEvent = { eventToken: 'org-event-aaa', title: 'Summer BBQ', dateTime: '2027-06-15T18:00:00Z', organizerToken: 'org-secret-token', } const attendeeEvent: StoredEvent = { eventToken: 'att-event-bbb', title: 'Team Meeting', dateTime: '2027-01-10T09:00:00Z', rsvpToken: 'rsvp-token-1', rsvpName: 'Alice', } function seedEvents(events: StoredEvent[]): string { return `window.localStorage.setItem('${STORAGE_KEY}', ${JSON.stringify(JSON.stringify(events))})` } test.describe('US1: Organizer Cancels Event from List', () => { test('T001: organizer taps delete, confirms, event is removed after successful API call', async ({ page, network, }) => { await page.addInitScript(seedEvents([organizerEvent, attendeeEvent])) const { http, HttpResponse } = await import('msw') let patchCalled = false network.use( http.patch('*/api/events/:token', ({ request, params }) => { const url = new URL(request.url) if ( params['token'] === organizerEvent.eventToken && url.searchParams.get('organizerToken') === organizerEvent.organizerToken ) { patchCalled = true return new HttpResponse(null, { status: 204 }) } return HttpResponse.json( { type: 'about:blank', title: 'Forbidden', status: 403 }, { status: 403, headers: { 'Content-Type': 'application/problem+json' } }, ) }), ) await page.goto('/') await expect(page.getByText('Summer BBQ')).toBeVisible() // Click delete on organizer event await page.getByRole('button', { name: /Remove Summer BBQ/ }).click() // Confirmation dialog appears with organizer-specific text await expect(page.getByRole('alertdialog')).toBeVisible() // Confirm cancellation await page.getByRole('button', { name: 'Remove', exact: true }).click() // Event is removed from list await expect(page.getByText('Summer BBQ')).not.toBeVisible() // Other event remains await expect(page.getByText('Team Meeting')).toBeVisible() expect(patchCalled).toBe(true) }) test('T002: organizer confirms cancellation, API fails, event stays in list and error shown', async ({ page, network, }) => { await page.addInitScript(seedEvents([organizerEvent])) const { http, HttpResponse } = await import('msw') network.use( http.patch('*/api/events/:token', () => { return HttpResponse.json( { type: 'about:blank', title: 'Internal Server Error', status: 500, }, { status: 500, headers: { 'Content-Type': 'application/problem+json' } }, ) }), ) await page.goto('/') await page.getByRole('button', { name: /Remove Summer BBQ/ }).click() await expect(page.getByRole('alertdialog')).toBeVisible() await page.getByRole('button', { name: 'Remove', exact: true }).click() // Event stays in list await expect(page.getByText('Summer BBQ')).toBeVisible() }) test('T003: organizer confirms cancellation, API returns 409 Conflict, event is silently removed', async ({ page, network, }) => { await page.addInitScript(seedEvents([organizerEvent])) const { http, HttpResponse } = await import('msw') network.use( http.patch('*/api/events/:token', () => { return HttpResponse.json( { type: 'about:blank', title: 'Conflict', status: 409, detail: 'Event is already cancelled.', }, { status: 409, headers: { 'Content-Type': 'application/problem+json' } }, ) }), ) await page.goto('/') await page.getByRole('button', { name: /Remove Summer BBQ/ }).click() await expect(page.getByRole('alertdialog')).toBeVisible() await page.getByRole('button', { name: 'Remove', exact: true }).click() // 409 treated as success — event removed await expect(page.getByText('Summer BBQ')).not.toBeVisible() }) test('T004: organizer opens cancel dialog then dismisses (cancel button), event remains', async ({ page, }) => { await page.addInitScript(seedEvents([organizerEvent])) await page.goto('/') await page.getByRole('button', { name: /Remove Summer BBQ/ }).click() await expect(page.getByRole('alertdialog')).toBeVisible() // Dismiss via Cancel button await page.getByRole('button', { name: 'Cancel' }).click() await expect(page.getByRole('alertdialog')).not.toBeVisible() await expect(page.getByText('Summer BBQ')).toBeVisible() }) test('T004b: organizer opens cancel dialog then dismisses via Escape', async ({ page }) => { await page.addInitScript(seedEvents([organizerEvent])) await page.goto('/') await page.getByRole('button', { name: /Remove Summer BBQ/ }).click() await expect(page.getByRole('alertdialog')).toBeVisible() await page.keyboard.press('Escape') await expect(page.getByRole('alertdialog')).not.toBeVisible() await expect(page.getByText('Summer BBQ')).toBeVisible() }) test('T004c: organizer opens cancel dialog then dismisses via overlay click', async ({ page, }) => { await page.addInitScript(seedEvents([organizerEvent])) await page.goto('/') await page.getByRole('button', { name: /Remove Summer BBQ/ }).click() await expect(page.getByRole('alertdialog')).toBeVisible() // Click on overlay (outside dialog) await page.locator('.confirm-dialog__overlay').click({ position: { x: 10, y: 10 } }) await expect(page.getByRole('alertdialog')).not.toBeVisible() await expect(page.getByText('Summer BBQ')).toBeVisible() }) }) test.describe('US2: Distinct Dialog for Organizer vs. Attendee', () => { test('T011: organizer dialog shows event-cancellation warning', async ({ page }) => { await page.addInitScript(seedEvents([organizerEvent])) await page.goto('/') await page.getByRole('button', { name: /Remove Summer BBQ/ }).click() const dialog = page.getByRole('alertdialog') await expect(dialog).toBeVisible() // Organizer-specific title and message await expect(dialog.locator('.confirm-dialog__title')).toHaveText('Cancel event?') await expect(dialog.locator('.confirm-dialog__message')).toContainText( 'all attendees', ) }) test('T012: attendee dialog preserves existing RSVP-cancellation message', async ({ page, }) => { await page.addInitScript(seedEvents([attendeeEvent])) await page.goto('/') await page.getByRole('button', { name: /Remove Team Meeting/ }).click() const dialog = page.getByRole('alertdialog') await expect(dialog).toBeVisible() // Attendee-specific title and message await expect(dialog.locator('.confirm-dialog__title')).toHaveText('Remove event?') await expect(dialog.locator('.confirm-dialog__message')).toContainText( 'attendance will be cancelled', ) }) })