Update E2E tests for kebab menu and add iCal download tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -64,12 +64,14 @@ test.describe('US1: Organizer cancels event with reason', () => {
|
||||
await page.addInitScript(seedEvents([organizerSeed()]))
|
||||
await page.goto(`/events/${fullEvent.eventToken}`)
|
||||
|
||||
// Cancel button visible for organizer
|
||||
const cancelBtn = page.getByRole('button', { name: /Cancel event/i })
|
||||
await expect(cancelBtn).toBeVisible()
|
||||
// Open kebab menu, then cancel event
|
||||
const kebabBtn = page.getByRole('button', { name: /Event actions/i })
|
||||
await expect(kebabBtn).toBeVisible()
|
||||
await kebabBtn.click()
|
||||
|
||||
// Open cancel bottom sheet
|
||||
await cancelBtn.click()
|
||||
const cancelItem = page.getByRole('menuitem', { name: /Cancel event/i })
|
||||
await expect(cancelItem).toBeVisible()
|
||||
await cancelItem.click()
|
||||
|
||||
// Fill in reason
|
||||
const reasonField = page.getByLabel(/reason/i)
|
||||
@@ -83,8 +85,8 @@ test.describe('US1: Organizer cancels event with reason', () => {
|
||||
await expect(page.getByText(/This event has been cancelled/i)).toBeVisible()
|
||||
await expect(page.getByText('Venue closed')).toBeVisible()
|
||||
|
||||
// Cancel button should be gone
|
||||
await expect(cancelBtn).not.toBeVisible()
|
||||
// Kebab menu should be gone (event is cancelled)
|
||||
await expect(kebabBtn).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -118,7 +120,8 @@ test.describe('US1: Organizer cancels event without reason', () => {
|
||||
await page.addInitScript(seedEvents([organizerSeed()]))
|
||||
await page.goto(`/events/${fullEvent.eventToken}`)
|
||||
|
||||
await page.getByRole('button', { name: /Cancel event/i }).click()
|
||||
await page.getByRole('button', { name: /Event actions/i }).click()
|
||||
await page.getByRole('menuitem', { name: /Cancel event/i }).click()
|
||||
|
||||
// Don't fill in reason, just confirm
|
||||
await page.getByRole('button', { name: /Confirm cancellation/i }).click()
|
||||
@@ -150,7 +153,8 @@ test.describe('US1: Cancel API failure', () => {
|
||||
await page.addInitScript(seedEvents([organizerSeed()]))
|
||||
await page.goto(`/events/${fullEvent.eventToken}`)
|
||||
|
||||
await page.getByRole('button', { name: /Cancel event/i }).click()
|
||||
await page.getByRole('button', { name: /Event actions/i }).click()
|
||||
await page.getByRole('menuitem', { name: /Cancel event/i }).click()
|
||||
await page.getByRole('button', { name: /Confirm cancellation/i }).click()
|
||||
|
||||
// Error message in bottom sheet
|
||||
|
||||
108
frontend/e2e/ical-download.spec.ts
Normal file
108
frontend/e2e/ical-download.spec.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
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: 'Sommerfest am See',
|
||||
description: 'Bring your own drinks!',
|
||||
dateTime: '2026-07-15T18:00:00+02:00',
|
||||
timezone: 'Europe/Berlin',
|
||||
location: 'Stadtpark Berlin',
|
||||
attendeeCount: 12,
|
||||
cancelled: false,
|
||||
}
|
||||
|
||||
const cancelledEvent = {
|
||||
...fullEvent,
|
||||
cancelled: true,
|
||||
cancellationReason: 'Bad weather',
|
||||
}
|
||||
|
||||
function seedEvents(events: StoredEvent[]): string {
|
||||
return `window.localStorage.setItem('${STORAGE_KEY}', ${JSON.stringify(JSON.stringify(events))})`
|
||||
}
|
||||
|
||||
function rsvpSeed(): StoredEvent {
|
||||
return {
|
||||
eventToken: fullEvent.eventToken,
|
||||
title: fullEvent.title,
|
||||
dateTime: fullEvent.dateTime,
|
||||
rsvpToken: 'd4e5f6a7-b8c9-0123-4567-890abcdef012',
|
||||
rsvpName: 'Anna',
|
||||
}
|
||||
}
|
||||
|
||||
function organizerSeed(): StoredEvent {
|
||||
return {
|
||||
eventToken: fullEvent.eventToken,
|
||||
title: fullEvent.title,
|
||||
dateTime: fullEvent.dateTime,
|
||||
organizerToken: 'org-token-1234',
|
||||
}
|
||||
}
|
||||
|
||||
test.describe('iCal download: calendar button visibility', () => {
|
||||
test('calendar button visible for pre-RSVP visitor', async ({ page, network }) => {
|
||||
network.use(
|
||||
http.get('*/api/events/:token', () => HttpResponse.json(fullEvent)),
|
||||
)
|
||||
await page.goto(`/events/${fullEvent.eventToken}`)
|
||||
|
||||
const calendarBtn = page.getByRole('button', { name: /add to calendar/i })
|
||||
await expect(calendarBtn).toBeVisible()
|
||||
})
|
||||
|
||||
test('calendar button visible for post-RSVP 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}`)
|
||||
|
||||
const calendarBtn = page.getByRole('button', { name: /add to calendar/i })
|
||||
await expect(calendarBtn).toBeVisible()
|
||||
})
|
||||
|
||||
test('calendar button visible for organizer', async ({ page, network }) => {
|
||||
network.use(
|
||||
http.get('*/api/events/:token', () => HttpResponse.json(fullEvent)),
|
||||
http.get('*/api/events/:token/attendees*', () =>
|
||||
HttpResponse.json({ attendees: [] }),
|
||||
),
|
||||
)
|
||||
await page.addInitScript(seedEvents([organizerSeed()]))
|
||||
await page.goto(`/events/${fullEvent.eventToken}`)
|
||||
|
||||
const calendarBtn = page.getByRole('button', { name: /add to calendar/i })
|
||||
await expect(calendarBtn).toBeVisible()
|
||||
})
|
||||
|
||||
test('calendar button NOT visible for cancelled event', async ({ page, network }) => {
|
||||
network.use(
|
||||
http.get('*/api/events/:token', () => HttpResponse.json(cancelledEvent)),
|
||||
)
|
||||
await page.goto(`/events/${fullEvent.eventToken}`)
|
||||
|
||||
const calendarBtn = page.getByRole('button', { name: /add to calendar/i })
|
||||
await expect(calendarBtn).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('iCal download: file generation', () => {
|
||||
test('triggers download with correct filename', async ({ page, network }) => {
|
||||
network.use(
|
||||
http.get('*/api/events/:token', () => HttpResponse.json(fullEvent)),
|
||||
)
|
||||
await page.goto(`/events/${fullEvent.eventToken}`)
|
||||
|
||||
// Intercept the download by overriding the click-link mechanism
|
||||
const downloadPromise = page.waitForEvent('download')
|
||||
await page.getByRole('button', { name: /add to calendar/i }).click()
|
||||
const download = await downloadPromise
|
||||
|
||||
expect(download.suggestedFilename()).toBe('sommerfest-am-see.ics')
|
||||
})
|
||||
})
|
||||
@@ -56,7 +56,7 @@ test.describe('US1: Watch event from detail page', () => {
|
||||
)
|
||||
await page.goto(`/events/${fullEvent.eventToken}`)
|
||||
|
||||
const bookmark = page.locator('.rsvp-bar__bookmark-inner')
|
||||
const bookmark = page.getByLabel(/watch.*this event/i)
|
||||
await expect(bookmark).toBeVisible()
|
||||
await expect(bookmark).toHaveAttribute('aria-label', 'Watch this event')
|
||||
|
||||
@@ -81,7 +81,7 @@ test.describe('US2: Un-watch event from detail page', () => {
|
||||
await page.addInitScript(seedEvents([watchSeed()]))
|
||||
await page.goto(`/events/${fullEvent.eventToken}`)
|
||||
|
||||
const bookmark = page.locator('.rsvp-bar__bookmark-inner')
|
||||
const bookmark = page.getByLabel(/watch.*this event/i)
|
||||
await expect(bookmark).toHaveAttribute('aria-label', 'Stop watching this event')
|
||||
|
||||
await bookmark.click()
|
||||
@@ -105,7 +105,7 @@ test.describe('US3: Bookmark reflects attending status', () => {
|
||||
await page.goto(`/events/${fullEvent.eventToken}`)
|
||||
|
||||
// Bookmark not shown for attendees — RsvpBar shows status state
|
||||
const bookmark = page.locator('.rsvp-bar__bookmark-inner')
|
||||
const bookmark = page.getByLabel(/watch.*this event/i)
|
||||
await expect(bookmark).not.toBeVisible()
|
||||
|
||||
// Navigate to list via back link
|
||||
@@ -132,7 +132,7 @@ test.describe('US4: RSVP cancellation preserves watch status', () => {
|
||||
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')
|
||||
const bookmark = page.getByLabel(/watch.*this event/i)
|
||||
await expect(bookmark).toBeVisible()
|
||||
await expect(bookmark).toHaveAttribute('aria-label', 'Stop watching this event')
|
||||
|
||||
@@ -151,7 +151,7 @@ test.describe('US5: No bookmark for attendees and organizers', () => {
|
||||
await page.addInitScript(seedEvents([rsvpSeed()]))
|
||||
await page.goto(`/events/${fullEvent.eventToken}`)
|
||||
|
||||
const bookmark = page.locator('.rsvp-bar__bookmark-inner')
|
||||
const bookmark = page.getByLabel(/watch.*this event/i)
|
||||
await expect(bookmark).not.toBeVisible()
|
||||
})
|
||||
|
||||
@@ -162,7 +162,7 @@ test.describe('US5: No bookmark for attendees and organizers', () => {
|
||||
await page.addInitScript(seedEvents([organizerSeed()]))
|
||||
await page.goto(`/events/${fullEvent.eventToken}`)
|
||||
|
||||
const bookmark = page.locator('.rsvp-bar__bookmark-inner')
|
||||
const bookmark = page.getByLabel(/watch.*this event/i)
|
||||
await expect(bookmark).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
@@ -197,7 +197,7 @@ test.describe('US7: Watcher upgrades to attendee', () => {
|
||||
await page.goto(`/events/${fullEvent.eventToken}`)
|
||||
|
||||
// Verify watching state — bookmark visible
|
||||
const bookmark = page.locator('.rsvp-bar__bookmark-inner')
|
||||
const bookmark = page.getByLabel(/watch.*this event/i)
|
||||
await expect(bookmark).toBeVisible()
|
||||
await expect(bookmark).toHaveAttribute('aria-label', 'Stop watching this event')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user