import { http, HttpResponse } from 'msw' import { test, expect } from './msw-setup' import type { StoredEvent } from '../src/composables/useEventStorage' const STORAGE_KEY = 'fete:events' const fullEvent = { eventToken: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', title: 'Summer BBQ', description: 'Bring your own drinks!', dateTime: '2026-03-15T20:00:00+01:00', timezone: 'Europe/Berlin', location: 'Central Park, NYC', attendeeCount: 12, cancelled: false, } const rsvpToken = 'd4e5f6a7-b8c9-0123-4567-890abcdef012' const organizerToken = 'org-token-1234' function seedEvents(events: StoredEvent[]): string { return `window.localStorage.setItem('${STORAGE_KEY}', ${JSON.stringify(JSON.stringify(events))})` } function watchSeed(): StoredEvent { return { eventToken: fullEvent.eventToken, title: fullEvent.title, dateTime: fullEvent.dateTime, } } function rsvpSeed(): StoredEvent { return { eventToken: fullEvent.eventToken, title: fullEvent.title, dateTime: fullEvent.dateTime, rsvpToken, rsvpName: 'Anna', } } function organizerSeed(): StoredEvent { return { eventToken: fullEvent.eventToken, title: fullEvent.title, dateTime: fullEvent.dateTime, organizerToken, } } test.describe('US1: Watch event from detail page', () => { test('bookmark unfilled by default, tapping watches the event', async ({ page, network }) => { network.use( http.get('*/api/events/:token', () => HttpResponse.json(fullEvent)), ) await page.goto(`/events/${fullEvent.eventToken}`) const bookmark = page.locator('.rsvp-bar__bookmark-inner') await expect(bookmark).toBeVisible() await expect(bookmark).toHaveAttribute('aria-label', 'Watch this event') await bookmark.click() await expect(bookmark).toHaveAttribute('aria-label', 'Stop watching this event') // Navigate to event list via back link await page.getByLabel('Back to home').click() // Event appears with "Watching" label await expect(page.getByText('Summer BBQ')).toBeVisible() await expect(page.getByText('Watching')).toBeVisible() }) }) test.describe('US2: Un-watch event from detail page', () => { test('tapping filled bookmark un-watches the event', async ({ page, network }) => { network.use( http.get('*/api/events/:token', () => HttpResponse.json(fullEvent)), ) await page.addInitScript(seedEvents([watchSeed()])) await page.goto(`/events/${fullEvent.eventToken}`) const bookmark = page.locator('.rsvp-bar__bookmark-inner') await expect(bookmark).toHaveAttribute('aria-label', 'Stop watching this event') await bookmark.click() await expect(bookmark).toHaveAttribute('aria-label', 'Watch this event') // Navigate to event list via back link (avoid page.goto re-running addInitScript) await page.getByLabel('Back to home').click() // Event is gone await expect(page.getByText('Summer BBQ')).not.toBeVisible() }) }) test.describe('US3: Bookmark reflects attending status', () => { test('bookmark is not visible when user has RSVPed, list shows Attendee', async ({ page, network }) => { network.use( http.get('*/api/events/:token', () => HttpResponse.json(fullEvent)), ) await page.addInitScript(seedEvents([rsvpSeed()])) await page.goto(`/events/${fullEvent.eventToken}`) // Bookmark not shown for attendees — RsvpBar shows status state const bookmark = page.locator('.rsvp-bar__bookmark-inner') await expect(bookmark).not.toBeVisible() // Navigate to list via back link await page.getByLabel('Back to home').click() await expect(page.getByText('Attending')).toBeVisible() await expect(page.getByText('Watching')).not.toBeVisible() }) }) test.describe('US4: RSVP cancellation preserves watch status', () => { test('cancel RSVP → bookmark reappears, list shows Watching', async ({ page, network }) => { network.use( http.get('*/api/events/:token', () => HttpResponse.json(fullEvent)), http.delete('*/api/events/:token/rsvps/:rsvpToken', () => { return new HttpResponse(null, { status: 204 }) }), ) await page.addInitScript(seedEvents([rsvpSeed()])) await page.goto(`/events/${fullEvent.eventToken}`) // Cancel RSVP await page.getByRole('button', { name: /You're attending/ }).click() await page.locator('.rsvp-bar__cancel').click() await page.getByRole('alertdialog').getByRole('button', { name: 'Cancel RSVP' }).click() // Bookmark reappears in CTA state, filled because event is still stored const bookmark = page.locator('.rsvp-bar__bookmark-inner') await expect(bookmark).toBeVisible() await expect(bookmark).toHaveAttribute('aria-label', 'Stop watching this event') // Navigate to list via back link await page.getByLabel('Back to home').click() await expect(page.getByText('Watching')).toBeVisible() await expect(page.getByText('Attending')).not.toBeVisible() }) }) test.describe('US5: No bookmark for attendees and organizers', () => { test('attendee does not see bookmark', async ({ page, network }) => { network.use( http.get('*/api/events/:token', () => HttpResponse.json(fullEvent)), ) await page.addInitScript(seedEvents([rsvpSeed()])) await page.goto(`/events/${fullEvent.eventToken}`) const bookmark = page.locator('.rsvp-bar__bookmark-inner') await expect(bookmark).not.toBeVisible() }) test('organizer does not see bookmark', async ({ page, network }) => { network.use( http.get('*/api/events/:token', () => HttpResponse.json(fullEvent)), ) await page.addInitScript(seedEvents([organizerSeed()])) await page.goto(`/events/${fullEvent.eventToken}`) const bookmark = page.locator('.rsvp-bar__bookmark-inner') await expect(bookmark).not.toBeVisible() }) }) test.describe('US6: Un-watch from event list', () => { test('deleting a watched event skips confirmation dialog', async ({ page }) => { await page.addInitScript(seedEvents([watchSeed()])) await page.goto('/') await expect(page.getByText('Summer BBQ')).toBeVisible() await page.getByRole('button', { name: /Remove Summer BBQ/ }).click() // No confirmation dialog — event removed immediately await expect(page.getByText('Remove event?')).not.toBeVisible() await expect(page.getByText('Summer BBQ')).not.toBeVisible() }) }) test.describe('US7: Watcher upgrades to attendee', () => { test('watch → RSVP → bookmark disappears, list shows Attendee', async ({ page, network }) => { network.use( http.get('*/api/events/:token', () => HttpResponse.json(fullEvent)), http.post('*/api/events/:token/rsvps', () => { return HttpResponse.json( { rsvpToken: 'new-rsvp-token', name: 'Max' }, { status: 201 }, ) }), ) await page.addInitScript(seedEvents([watchSeed()])) await page.goto(`/events/${fullEvent.eventToken}`) // Verify watching state — bookmark visible const bookmark = page.locator('.rsvp-bar__bookmark-inner') await expect(bookmark).toBeVisible() await expect(bookmark).toHaveAttribute('aria-label', 'Stop watching this event') // RSVP await page.getByRole('button', { name: "I'm attending" }).click() const dialog = page.getByRole('dialog', { name: 'RSVP' }) await dialog.getByLabel('Your name').fill('Max') await dialog.getByRole('button', { name: 'Count me in' }).click() // Bookmark gone — status bar shown instead await expect(bookmark).not.toBeVisible() // Navigate to list via back link await page.getByLabel('Back to home').click() await expect(page.getByText('Attending')).toBeVisible() await expect(page.getByText('Watching')).not.toBeVisible() }) })