Add event list feature (009-list-events)
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>
This commit is contained in:
79
frontend/src/components/__tests__/EventList.spec.ts
Normal file
79
frontend/src/components/__tests__/EventList.spec.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import EventList from '../EventList.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', component: { template: '<div />' } },
|
||||
{ path: '/events/:eventToken', name: 'event', component: { template: '<div />' } },
|
||||
],
|
||||
})
|
||||
|
||||
const mockEvents = [
|
||||
{ eventToken: 'past-1', title: 'Past Event', dateTime: '2025-01-01T10:00:00Z', expiryDate: '' },
|
||||
{ eventToken: 'future-1', title: 'Future Event', dateTime: '2027-06-15T10:00:00Z', expiryDate: '' },
|
||||
{ eventToken: 'future-2', title: 'Soon Event', dateTime: '2027-01-01T10:00:00Z', expiryDate: '' },
|
||||
]
|
||||
|
||||
vi.mock('../../composables/useEventStorage', () => ({
|
||||
isValidStoredEvent: (e: unknown) => {
|
||||
if (typeof e !== 'object' || e === null) return false
|
||||
const obj = e as Record<string, unknown>
|
||||
return typeof obj.eventToken === 'string' && obj.eventToken.length > 0
|
||||
&& typeof obj.title === 'string' && obj.title.length > 0
|
||||
&& typeof obj.dateTime === 'string' && obj.dateTime.length > 0
|
||||
},
|
||||
useEventStorage: () => ({
|
||||
getStoredEvents: () => mockEvents,
|
||||
removeEvent: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../../composables/useRelativeTime', () => ({
|
||||
formatRelativeTime: (dateTime: string) => {
|
||||
if (dateTime.startsWith('2025')) return '1 year ago'
|
||||
if (dateTime.includes('06-15')) return 'in 1 year'
|
||||
return 'in 10 months'
|
||||
},
|
||||
}))
|
||||
|
||||
function mountList() {
|
||||
return mount(EventList, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
}
|
||||
|
||||
describe('EventList', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(new Date('2026-03-08T12:00:00Z'))
|
||||
})
|
||||
|
||||
it('renders all valid events', () => {
|
||||
const wrapper = mountList()
|
||||
const cards = wrapper.findAll('.event-card')
|
||||
expect(cards).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('sorts upcoming events before past events', () => {
|
||||
const wrapper = mountList()
|
||||
const titles = wrapper.findAll('.event-card__title').map((el) => el.text())
|
||||
// Upcoming events first (sorted ascending), then past events
|
||||
expect(titles[0]).toBe('Soon Event')
|
||||
expect(titles[1]).toBe('Future Event')
|
||||
expect(titles[2]).toBe('Past Event')
|
||||
})
|
||||
|
||||
it('marks past events with isPast class', () => {
|
||||
const wrapper = mountList()
|
||||
const cards = wrapper.findAll('.event-card')
|
||||
expect(cards).toHaveLength(3)
|
||||
// Last card should be past
|
||||
expect(cards[2]!.classes()).toContain('event-card--past')
|
||||
// First two should not be past
|
||||
expect(cards[0]!.classes()).not.toContain('event-card--past')
|
||||
expect(cards[1]!.classes()).not.toContain('event-card--past')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user