Files
fete/frontend/e2e/event-rsvp.spec.ts
nitrix 0441ca0c33
All checks were successful
CI / backend-test (push) Successful in 58s
CI / frontend-test (push) Successful in 23s
CI / frontend-e2e (push) Successful in 1m12s
CI / build-and-publish (push) Has been skipped
Make expiryDate an internal concern, auto-set to event date + 7 days
The expiry date is no longer user-facing: it is removed from the API
(request and response) and the frontend. The backend now automatically
calculates it as the event date plus 7 days. The expired banner and
RSVP-bar filtering by expired status are also removed from the UI,
since expiry is purely an internal data-retention mechanism.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:29:12 +01:00

173 lines
5.3 KiB
TypeScript

import { http, HttpResponse } from 'msw'
import { test, expect } from './msw-setup'
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,
}
test.describe('US1: RSVP submission flow', () => {
test('submits RSVP, updates attendee count, and persists in localStorage', async ({ page, network }) => {
network.use(
http.get('*/api/events/:token', () => {
return HttpResponse.json(fullEvent)
}),
http.post('*/api/events/:token/rsvps', () => {
return HttpResponse.json(
{ rsvpToken: 'd4e5f6a7-b8c9-0123-4567-890abcdef012', name: 'Max Mustermann' },
{ status: 201 },
)
}),
)
await page.goto(`/events/${fullEvent.eventToken}`)
// CTA is visible
const cta = page.getByRole('button', { name: "I'm attending" })
await expect(cta).toBeVisible()
// Open bottom sheet
await cta.click()
const dialog = page.getByRole('dialog', { name: 'RSVP' })
await expect(dialog).toBeVisible()
// Fill name and submit
await dialog.getByLabel('Your name').fill('Max Mustermann')
await dialog.getByRole('button', { name: 'Count me in' }).click()
// Bottom sheet closes, status bar appears
await expect(dialog).not.toBeVisible()
await expect(page.getByText("You're attending!")).toBeVisible()
await expect(cta).not.toBeVisible()
// Attendee count incremented
await expect(page.getByText('13')).toBeVisible()
// Verify localStorage
const stored = await page.evaluate(() => {
const raw = localStorage.getItem('fete:events')
return raw ? JSON.parse(raw) : null
})
expect(stored).toEqual(
expect.arrayContaining([
expect.objectContaining({
eventToken: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
rsvpToken: 'd4e5f6a7-b8c9-0123-4567-890abcdef012',
rsvpName: 'Max Mustermann',
}),
]),
)
})
test('shows validation error when name is empty', async ({ page, network }) => {
network.use(
http.get('*/api/events/:token', () => {
return HttpResponse.json(fullEvent)
}),
)
await page.goto(`/events/${fullEvent.eventToken}`)
await page.getByRole('button', { name: "I'm attending" }).click()
const dialog = page.getByRole('dialog', { name: 'RSVP' })
await dialog.getByRole('button', { name: 'Count me in' }).click()
await expect(page.getByText('Please enter your name.')).toBeVisible()
})
test('restores RSVP status from localStorage on page load', async ({ page, network }) => {
network.use(
http.get('*/api/events/:token', () => {
return HttpResponse.json(fullEvent)
}),
)
// Pre-seed localStorage
await page.goto('/')
await page.evaluate(() => {
localStorage.setItem(
'fete:events',
JSON.stringify([
{
eventToken: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
title: 'Summer BBQ',
dateTime: '2026-03-15T20:00:00+01:00',
expiryDate: '',
rsvpToken: 'existing-rsvp-token',
rsvpName: 'Anna',
},
]),
)
})
await page.goto(`/events/${fullEvent.eventToken}`)
// Status bar should show, not CTA
await expect(page.getByText("You're attending!")).toBeVisible()
await expect(page.getByRole('button', { name: "I'm attending" })).not.toBeVisible()
})
test('shows error when server is unreachable during RSVP', async ({ page, network }) => {
network.use(
http.get('*/api/events/:token', () => {
return HttpResponse.json(fullEvent)
}),
http.post('*/api/events/:token/rsvps', () => {
return HttpResponse.json(
{ type: 'about:blank', title: 'Bad Request', status: 400 },
{ status: 400, headers: { 'Content-Type': 'application/problem+json' } },
)
}),
)
await page.goto(`/events/${fullEvent.eventToken}`)
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()
await expect(page.getByText('Could not submit RSVP. Please try again.')).toBeVisible()
})
test('does not show RSVP bar for organizer', async ({ page, network }) => {
network.use(
http.get('*/api/events/:token', () => {
return HttpResponse.json(fullEvent)
}),
)
// Pre-seed localStorage with organizer token
await page.goto('/')
await page.evaluate(() => {
localStorage.setItem(
'fete:events',
JSON.stringify([
{
eventToken: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
organizerToken: 'org-token-123',
title: 'Summer BBQ',
dateTime: '2026-03-15T20:00:00+01:00',
expiryDate: '',
},
]),
)
})
await page.goto(`/events/${fullEvent.eventToken}`)
// Event content should load
await expect(page.getByText('Summer BBQ')).toBeVisible()
// But no RSVP bar
await expect(page.getByRole('button', { name: "I'm attending" })).not.toBeVisible()
await expect(page.getByText("You're attending!")).not.toBeVisible()
})
})