Enable users to see all their saved events on the home screen, sorted by date with upcoming events first. Key capabilities: - EventCard with title, relative time display, and organizer/attendee role badge - Sortable EventList with past-event visual distinction (faded style) - Empty state when no events are stored - Swipe-to-delete gesture with confirmation dialog - Floating action button for quick event creation - Rename router param :token → :eventToken across all views - useRelativeTime composable (Intl.RelativeTimeFormat) - useEventStorage: add validation, removeEvent(), reactive versioning - Full E2E and unit test coverage for all new components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
112 lines
3.2 KiB
TypeScript
112 lines
3.2 KiB
TypeScript
import { describe, it, expect, afterEach } from 'vitest'
|
|
import { mount, VueWrapper } from '@vue/test-utils'
|
|
import ConfirmDialog from '../ConfirmDialog.vue'
|
|
|
|
let wrapper: VueWrapper
|
|
|
|
function mountDialog(props: Record<string, unknown> = {}) {
|
|
wrapper = mount(ConfirmDialog, {
|
|
props: {
|
|
open: true,
|
|
...props,
|
|
},
|
|
attachTo: document.body,
|
|
})
|
|
return wrapper
|
|
}
|
|
|
|
function dialog() {
|
|
return document.body.querySelector('.confirm-dialog')
|
|
}
|
|
|
|
function overlay() {
|
|
return document.body.querySelector('.confirm-dialog__overlay')
|
|
}
|
|
|
|
afterEach(() => {
|
|
wrapper?.unmount()
|
|
})
|
|
|
|
describe('ConfirmDialog', () => {
|
|
it('renders when open is true', () => {
|
|
mountDialog()
|
|
expect(dialog()).not.toBeNull()
|
|
})
|
|
|
|
it('does not render when open is false', () => {
|
|
mountDialog({ open: false })
|
|
expect(dialog()).toBeNull()
|
|
})
|
|
|
|
it('displays default title', () => {
|
|
mountDialog()
|
|
expect(dialog()!.querySelector('.confirm-dialog__title')!.textContent).toBe('Are you sure?')
|
|
})
|
|
|
|
it('displays custom title and message', () => {
|
|
mountDialog({
|
|
title: 'Remove event?',
|
|
message: 'This cannot be undone.',
|
|
})
|
|
expect(dialog()!.querySelector('.confirm-dialog__title')!.textContent).toBe('Remove event?')
|
|
expect(dialog()!.querySelector('.confirm-dialog__message')!.textContent).toBe('This cannot be undone.')
|
|
})
|
|
|
|
it('displays custom button labels', () => {
|
|
mountDialog({
|
|
confirmLabel: 'Delete',
|
|
cancelLabel: 'Keep',
|
|
})
|
|
const buttons = dialog()!.querySelectorAll('.confirm-dialog__btn')
|
|
expect(buttons[0]!.textContent!.trim()).toBe('Keep')
|
|
expect(buttons[1]!.textContent!.trim()).toBe('Delete')
|
|
})
|
|
|
|
it('emits confirm when confirm button is clicked', async () => {
|
|
mountDialog()
|
|
const btn = dialog()!.querySelector('.confirm-dialog__btn--confirm') as HTMLElement
|
|
btn.click()
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.emitted('confirm')).toHaveLength(1)
|
|
})
|
|
|
|
it('emits cancel when cancel button is clicked', async () => {
|
|
mountDialog()
|
|
const btn = dialog()!.querySelector('.confirm-dialog__btn--cancel') as HTMLElement
|
|
btn.click()
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.emitted('cancel')).toHaveLength(1)
|
|
})
|
|
|
|
it('emits cancel when overlay is clicked', async () => {
|
|
mountDialog()
|
|
const el = overlay() as HTMLElement
|
|
el.click()
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.emitted('cancel')).toHaveLength(1)
|
|
})
|
|
|
|
it('emits cancel when Escape key is pressed', async () => {
|
|
mountDialog()
|
|
const el = dialog() as HTMLElement
|
|
el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.emitted('cancel')).toHaveLength(1)
|
|
})
|
|
|
|
it('focuses cancel button when opened', async () => {
|
|
mountDialog({ open: false })
|
|
await wrapper.setProps({ open: true })
|
|
await wrapper.vm.$nextTick()
|
|
const cancelBtn = dialog()!.querySelector('.confirm-dialog__btn--cancel')
|
|
expect(document.activeElement).toBe(cancelBtn)
|
|
})
|
|
|
|
it('has alertdialog role and aria-modal', () => {
|
|
mountDialog()
|
|
const el = dialog() as HTMLElement
|
|
expect(el.getAttribute('role')).toBe('alertdialog')
|
|
expect(el.getAttribute('aria-modal')).toBe('true')
|
|
})
|
|
})
|