Moves formBuilder types, formValidation, useConditionalLogic, useFormSteps, and formatFieldValue from apps/portal/src to packages/form-schema/src. Adds @form-schema path alias to both apps/portal and apps/app. Vue field components remain per-app to allow independent visual evolution. Behavior-neutral: all 35 Vitest tests green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
87 lines
2.6 KiB
TypeScript
87 lines
2.6 KiB
TypeScript
import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query'
|
|
import { mount } from '@vue/test-utils'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { defineComponent, h, ref } from 'vue'
|
|
|
|
vi.mock('@/lib/axios', () => ({
|
|
apiClient: { get: vi.fn() },
|
|
}))
|
|
|
|
import { apiClient } from '@/lib/axios'
|
|
import { usePublicFormTimeSlots } from '@/composables/api/usePublicFormTimeSlots'
|
|
import type { PublicFormTimeSlot } from '@form-schema/types/formBuilder'
|
|
|
|
interface MockedApi { get: ReturnType<typeof vi.fn> }
|
|
const mocked = apiClient as unknown as MockedApi
|
|
|
|
function mountHook(tokenValue: string) {
|
|
const result: { query: ReturnType<typeof usePublicFormTimeSlots> | null } = { query: null }
|
|
|
|
const Host = defineComponent({
|
|
setup() {
|
|
result.query = usePublicFormTimeSlots(ref(tokenValue))
|
|
|
|
return () => h('div')
|
|
},
|
|
})
|
|
|
|
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
|
const wrapper = mount(Host, {
|
|
global: { plugins: [[VueQueryPlugin, { queryClient }]] },
|
|
})
|
|
|
|
return { wrapper, result }
|
|
}
|
|
|
|
function timeSlot(partial: Partial<PublicFormTimeSlot> = {}): PublicFormTimeSlot {
|
|
return {
|
|
id: partial.id ?? '01A',
|
|
name: partial.name ?? 'Zaterdag middag',
|
|
date: partial.date ?? '2026-07-11',
|
|
start_time: partial.start_time ?? '12:00:00',
|
|
end_time: partial.end_time ?? '18:00:00',
|
|
duration_hours: partial.duration_hours ?? 6,
|
|
event_id: partial.event_id ?? 'evt_1',
|
|
event_name: partial.event_name ?? 'Echt Feesten 2026',
|
|
}
|
|
}
|
|
|
|
function flush(): Promise<void> {
|
|
return new Promise(resolve => setTimeout(resolve, 0))
|
|
}
|
|
|
|
describe('usePublicFormTimeSlots', () => {
|
|
beforeEach(() => { vi.clearAllMocks() })
|
|
afterEach(() => { vi.clearAllMocks() })
|
|
|
|
it('fetches and parses PublicFormTimeSlot[] on happy path', async () => {
|
|
const slot = timeSlot()
|
|
mocked.get.mockResolvedValueOnce({ data: { data: [slot] } })
|
|
|
|
const { result } = mountHook('TKN42')
|
|
await flush()
|
|
await flush()
|
|
|
|
expect(mocked.get).toHaveBeenCalledWith('/public/forms/TKN42/time-slots')
|
|
expect(result.query?.data.value).toEqual([slot])
|
|
})
|
|
|
|
it('is disabled when the token ref is empty', async () => {
|
|
const { result } = mountHook('')
|
|
await flush()
|
|
|
|
expect(mocked.get).not.toHaveBeenCalled()
|
|
expect(result.query?.isFetching.value).toBe(false)
|
|
})
|
|
|
|
it('surfaces errors via isError', async () => {
|
|
mocked.get.mockRejectedValueOnce(new Error('network'))
|
|
|
|
const { result } = mountHook('TKN99')
|
|
await flush()
|
|
await flush()
|
|
|
|
expect(result.query?.isError.value).toBe(true)
|
|
})
|
|
})
|