import { describe, expect, it, vi } from 'vitest' import { computed, ref } from 'vue' import { mountWithVuexy } from '../utils/mountWithVuexy' import EventTabsNav from '@/components/events/EventTabsNav.vue' /** * Programma tab smoke (Session 4 follow-up): asserts the timetable * navigation entry is present, labelled, iconed, and routed correctly * — both for flat events and for festivals (which re-order the tabs). * * useEvents queries are mocked so the component skips its skeleton/error * branches and renders the real tabs immediately. useTransitionEventStatus * is mocked too — the header status menu is out of scope here. */ interface MinimalEvent { id: string organisation_id: string name: string slug: string status: string start_date: string end_date: string event_type: 'event' | 'festival' | 'series' event_type_label: string is_festival: boolean is_sub_event: boolean parent_event_id: string | null parent: null sub_event_label: string | null children_count: number allowed_transitions: string[] } let eventFixtureCurrent: MinimalEvent function eventFixture(overrides: Partial = {}): MinimalEvent { return { id: 'ev_1', organisation_id: 'org_1', name: 'Test Event', slug: 'test-event', status: 'draft', start_date: '2026-07-10', end_date: '2026-07-12', event_type: 'event', event_type_label: 'Evenement', is_festival: false, is_sub_event: false, parent_event_id: null, parent: null, sub_event_label: null, children_count: 0, allowed_transitions: [], ...overrides, } } vi.mock('@/composables/api/useEvents', () => ({ useEventDetail: () => ({ data: computed(() => eventFixtureCurrent), isLoading: ref(false), isError: ref(false), refetch: vi.fn(), }), useEventChildren: () => ({ data: computed(() => [] as MinimalEvent[]), isLoading: ref(false), isError: ref(false), }), useTransitionEventStatus: () => ({ mutateAsync: vi.fn().mockResolvedValue(undefined), isPending: ref(false), }), // EditEventDialog is rendered as a child component; it consults // useUpdateEvent + useUploadEventImage on setup. Mock both so they // return safe no-op shapes. useUpdateEvent: () => ({ mutate: vi.fn(), mutateAsync: vi.fn().mockResolvedValue(undefined), isPending: ref(false), }), useUploadEventImage: () => ({ mutate: vi.fn(), mutateAsync: vi.fn().mockResolvedValue(undefined), isPending: ref(false), }), })) const eventsRoutes = [ { path: '/events/:id', name: 'events-id', component: { template: '
' } }, { path: '/events/:id/persons', name: 'events-id-persons', component: { template: '
' } }, { path: '/events/:id/crowd-lists', name: 'events-id-crowd-lists', component: { template: '
' } }, { path: '/events/:id/time-slots', name: 'events-id-time-slots', component: { template: '
' } }, { path: '/events/:id/sections', name: 'events-id-sections', component: { template: '
' } }, { path: '/events/:id/artists', name: 'events-id-artists', component: { template: '
' } }, { path: '/events/:id/briefings', name: 'events-id-briefings', component: { template: '
' } }, { path: '/events/:id/timetable', name: 'events-id-timetable', component: { template: '
' } }, { path: '/events/:id/settings', name: 'events-id-settings', component: { template: '
' } }, { path: '/events/:id/programmaonderdelen', name: 'events-id-programmaonderdelen', component: { template: '
' } }, { path: '/events', name: 'events', component: { template: '
' } }, { path: '/', name: 'home', component: { template: '
' } }, ] async function mountTabs(event: MinimalEvent) { eventFixtureCurrent = event const result = mountWithVuexy(EventTabsNav, { routes: eventsRoutes, initialPath: `/events/${event.id}`, initialState: { auth: { currentOrganisation: { id: 'org_1', name: 'Org' } }, }, }) await (result.wrapper as unknown as { __routerReady: Promise }).__routerReady await result.wrapper.vm.$nextTick() await result.wrapper.vm.$nextTick() return result } describe('EventTabsNav — Programma tab', () => { it('renders a tab labeled "Programma"', async () => { const { wrapper } = await mountTabs(eventFixture()) const tabs = wrapper.findAll('.v-tab') const labels = tabs.map(t => t.text()) expect(labels).toContain('Programma') }) it('uses the tabler-calendar-time icon on the Programma tab', async () => { const { wrapper } = await mountTabs(eventFixture()) const programmaTab = wrapper.findAll('.v-tab').find(t => t.text() === 'Programma') expect(programmaTab).toBeDefined() expect(programmaTab!.html()).toContain('tabler-calendar-time') }) it('the Programma tab targets the events-id-timetable route', async () => { const { wrapper } = await mountTabs(eventFixture()) const programmaTab = wrapper.findAll('.v-tab').find(t => t.text() === 'Programma') expect(programmaTab).toBeDefined() // Two independent proofs that the tab is wired to the right route: // - the rendered VTab `value` attribute equals the route name // - the resolved href (with whatever id is in the test route) ends in /timetable expect(programmaTab!.attributes('value')).toBe('events-id-timetable') expect(programmaTab!.html()).toMatch(/href="\/events\/[^/"]+\/timetable"/) }) it('also exposes the Programma tab on a festival (re-ordered tabs)', async () => { const { wrapper } = await mountTabs(eventFixture({ is_festival: true, event_type: 'festival', sub_event_label: 'dag', })) const labels = wrapper.findAll('.v-tab').map(t => t.text()) expect(labels).toContain('Programma') }) })