import { describe, it, expect, vi, beforeEach } from 'vitest' import { mount, flushPromises } from '@vue/test-utils' import { createRouter, createMemoryHistory } from 'vue-router' import EventDetailView from '../EventDetailView.vue' import { api } from '@/api/client' vi.mock('@/api/client', () => ({ api: { GET: vi.fn(), }, })) function createTestRouter() { return createRouter({ history: createMemoryHistory(), routes: [ { path: '/', name: 'home', component: { template: '
' } }, { path: '/events/:token', name: 'event', component: EventDetailView }, ], }) } async function mountWithToken(token = 'test-token') { const router = createTestRouter(token) await router.push(`/events/${token}`) await router.isReady() return mount(EventDetailView, { global: { plugins: [router] }, }) } const fullEvent = { eventToken: 'abc-123', 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, expired: false, } beforeEach(() => { vi.restoreAllMocks() }) describe('EventDetailView', () => { // T014: Loading state it('renders skeleton shimmer placeholders while loading', async () => { vi.mocked(api.GET).mockReturnValue(new Promise(() => {})) const wrapper = await mountWithToken() expect(wrapper.find('[aria-busy="true"]').exists()).toBe(true) expect(wrapper.findAll('.skeleton').length).toBeGreaterThanOrEqual(3) }) // T013: Loaded state — all fields it('renders all event fields when loaded', async () => { vi.mocked(api.GET).mockResolvedValue({ data: fullEvent, error: undefined, response: new Response(null, { status: 200 }), } as never) const wrapper = await mountWithToken() await flushPromises() expect(wrapper.find('.detail__title').text()).toBe('Summer BBQ') expect(wrapper.text()).toContain('Bring your own drinks!') expect(wrapper.text()).toContain('Central Park, NYC') expect(wrapper.text()).toContain('12') expect(wrapper.text()).toContain('Europe/Berlin') }) // T013: Loaded state — locale-formatted date/time it('formats date/time with Intl.DateTimeFormat and timezone', async () => { vi.mocked(api.GET).mockResolvedValue({ data: fullEvent, error: undefined, response: new Response(null, { status: 200 }), } as never) const wrapper = await mountWithToken() await flushPromises() const dateField = wrapper.findAll('.detail__value')[0] expect(dateField.text()).toContain('(Europe/Berlin)') // The formatted date part is locale-dependent but should contain the year expect(dateField.text()).toContain('2026') }) // T013: Loaded state — optional fields absent it('does not render description and location when absent', async () => { vi.mocked(api.GET).mockResolvedValue({ data: { ...fullEvent, description: undefined, location: undefined, attendeeCount: 0, }, error: undefined, response: new Response(null, { status: 200 }), } as never) const wrapper = await mountWithToken() await flushPromises() expect(wrapper.text()).not.toContain('Description') expect(wrapper.text()).not.toContain('Location') expect(wrapper.text()).toContain('0') }) // T020 (US2): Expired state it('renders "event has ended" banner when expired', async () => { vi.mocked(api.GET).mockResolvedValue({ data: { ...fullEvent, expired: true }, error: undefined, response: new Response(null, { status: 200 }), } as never) const wrapper = await mountWithToken() await flushPromises() expect(wrapper.text()).toContain('This event has ended.') expect(wrapper.find('.detail__banner--expired').exists()).toBe(true) }) // T020 (US2): No expired banner when not expired it('does not render expired banner when event is active', async () => { vi.mocked(api.GET).mockResolvedValue({ data: fullEvent, error: undefined, response: new Response(null, { status: 200 }), } as never) const wrapper = await mountWithToken() await flushPromises() expect(wrapper.find('.detail__banner--expired').exists()).toBe(false) }) // T023 (US4): Not found state it('renders "event not found" when API returns 404', async () => { vi.mocked(api.GET).mockResolvedValue({ data: undefined, error: { type: 'about:blank', title: 'Not Found', status: 404 }, response: new Response(null, { status: 404 }), } as never) const wrapper = await mountWithToken() await flushPromises() expect(wrapper.text()).toContain('Event not found.') // No event data in DOM expect(wrapper.find('.detail__title').exists()).toBe(false) }) // T027: Server error + retry it('renders error state with retry button on server error', async () => { vi.mocked(api.GET).mockResolvedValue({ data: undefined, error: { type: 'about:blank', title: 'Internal Server Error', status: 500 }, response: new Response(null, { status: 500 }), } as never) const wrapper = await mountWithToken() await flushPromises() expect(wrapper.text()).toContain('Something went wrong.') expect(wrapper.find('button').text()).toBe('Retry') }) // T027: Retry button re-fetches it('retry button triggers a new fetch', async () => { vi.mocked(api.GET) .mockResolvedValueOnce({ data: undefined, error: { type: 'about:blank', title: 'Error', status: 500 }, response: new Response(null, { status: 500 }), } as never) .mockResolvedValueOnce({ data: fullEvent, error: undefined, response: new Response(null, { status: 200 }), } as never) const wrapper = await mountWithToken() await flushPromises() expect(wrapper.text()).toContain('Something went wrong.') await wrapper.find('button').trigger('click') await flushPromises() expect(wrapper.find('.detail__title').text()).toBe('Summer BBQ') }) })