Files
crewli/apps/portal/tests/composables/api/usePublicFormSections.spec.ts
bert.hausmans 9256c05db0 feat(portal): implement TAG_PICKER, AVAILABILITY_PICKER, SECTION_PRIORITY field types
- FieldTagPicker: VAutocomplete multiple with grouped category slots,
  empty/null category normalised to "Overig", empty-state info alert
  when the server delivers no tags.
- FieldAvailabilityPicker: date-grouped checkbox list, festival-aware
  via usePublicFormTimeSlots. Event-name subheaders only surface when
  the time-slots span multiple events. Time format strips seconds.
- FieldSectionPriority: tap-to-rank + drag-to-reorder via vuedraggable
  for desktop; mobile tap-only. Renumbers priorities on every mutation.
  Self-heals malformed modelValue. UI soft cap via
  validation_rules.max_priorities clamped to the backend hard cap of 5.
- FieldRenderer: three new types removed from isStubbed.
- publicFormInjection: page-level provide/inject for the public token.
- IdentityMatchBanner: prefers backend-provided Dutch copy with
  frontend defaults as defensive fallback.
- FormConfirmation wires the banner inline.
- usePublicFormTimeSlots and usePublicFormSections TanStack composables.
- 40 new Vitest assertions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 20:00:40 +02:00

84 lines
2.4 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 { usePublicFormSections } from '@/composables/api/usePublicFormSections'
import type { PublicFormSectionOption } from '@/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 usePublicFormSections> | null } = { query: null }
const Host = defineComponent({
setup() {
result.query = usePublicFormSections(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 section(partial: Partial<PublicFormSectionOption> = {}): PublicFormSectionOption {
return {
id: partial.id ?? '01B',
name: partial.name ?? 'Bar',
category: partial.category ?? 'Horeca',
icon: partial.icon ?? 'tabler-beer',
registration_description: partial.registration_description ?? 'Tappen en serveren',
}
}
function flush(): Promise<void> {
return new Promise(resolve => setTimeout(resolve, 0))
}
describe('usePublicFormSections', () => {
beforeEach(() => { vi.clearAllMocks() })
afterEach(() => { vi.clearAllMocks() })
it('fetches and parses PublicFormSectionOption[] on happy path', async () => {
const s = section()
mocked.get.mockResolvedValueOnce({ data: { data: [s] } })
const { result } = mountHook('TKN42')
await flush()
await flush()
expect(mocked.get).toHaveBeenCalledWith('/public/forms/TKN42/sections')
expect(result.query?.data.value).toEqual([s])
})
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('boom'))
const { result } = mountHook('TKN99')
await flush()
await flush()
expect(result.query?.isError.value).toBe(true)
})
})