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>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user