Add RSVP frontend: bottom sheet form, RsvpBar, and localStorage persistence

Introduces BottomSheet and RsvpBar components, integrates the RSVP
submission flow into EventDetailView, extends useEventStorage with
saveRsvp/getRsvp, and adds unit tests plus an E2E spec for the RSVP
workflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 12:47:53 +01:00
parent d9136481d8
commit be1c5062a2
11 changed files with 856 additions and 42 deletions

View File

@@ -0,0 +1,51 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import BottomSheet from '../BottomSheet.vue'
function mountSheet(open = true) {
return mount(BottomSheet, {
props: { open, label: 'Test Sheet' },
slots: { default: '<p>Sheet content</p>' },
attachTo: document.body,
})
}
describe('BottomSheet', () => {
it('renders slot content when open', () => {
const wrapper = mountSheet(true)
expect(document.body.textContent).toContain('Sheet content')
wrapper.unmount()
})
it('does not render content when closed', () => {
const wrapper = mountSheet(false)
expect(document.body.querySelector('[role="dialog"]')).toBeNull()
wrapper.unmount()
})
it('has aria-modal and aria-label on the dialog', () => {
const wrapper = mountSheet(true)
const dialog = document.body.querySelector('[role="dialog"]')!
expect(dialog.getAttribute('aria-modal')).toBe('true')
expect(dialog.getAttribute('aria-label')).toBe('Test Sheet')
wrapper.unmount()
})
it('emits close when backdrop is clicked', async () => {
const wrapper = mountSheet(true)
const backdrop = document.body.querySelector('.sheet-backdrop')! as HTMLElement
await backdrop.click()
// Vue test utils tracks emitted events on the wrapper
expect(wrapper.emitted('close')).toBeTruthy()
wrapper.unmount()
})
it('emits close on Escape key', async () => {
const wrapper = mountSheet(true)
const backdrop = document.body.querySelector('.sheet-backdrop')! as HTMLElement
backdrop.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))
await wrapper.vm.$nextTick()
expect(wrapper.emitted('close')).toBeTruthy()
wrapper.unmount()
})
})