import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { enableAutoUnmount, flushPromises } from '@vue/test-utils' import { setSettings } from '../../src/state/useSettings' import { clone, companionsFormSchemaFixture, listFixture, listSchemaFixture } from '../fixtures/typed' import { API, mountApp, notFound, requestsTo, resetState, type Route } from '../helpers' // Phase 17 plan 06 specs for the singleton form (D-11, UI-SPEC "Singleton // form"): a settings link to a pact.AdminSingleton controller opens its one // record through GET …/singleton once the list answers 404 (WR-07: the same // for a click, a new tab and a bookmark); the form has no Delete, and back, // Cancel and Save and close return to the settings index. const PROFILES = `${API}/acme/companions/profiles` function singletonSchema() { const schema = clone(companionsFormSchemaFixture) schema.data.singleton = true schema.data.fields = schema.data.fields.filter((field) => field.name === 'title') return schema } function routes(overrides: Record = {}): Record { return { [`GET ${PROFILES}/singleton`]: { body: { data: { id: 9 }, meta: {} } }, [`GET ${PROFILES}/schema/form`]: { body: singletonSchema() }, [`GET ${PROFILES}/9`]: { body: { data: { id: 9, title: 'Main site' }, meta: {} } }, ...overrides, } } beforeEach(() => { resetState() setSettings([ { code: 'profile', label: 'Site profile', description: 'The one site profile', icon: 'settings', category: 'CMS', keywords: [], model: '', controller: 'acme.companions.profiles', order: 1, }, ]) }) afterEach(() => { document.body.innerHTML = '' }) enableAutoUnmount(afterEach) describe('SingletonForm', () => { it('opens through /singleton from settings, has no Delete, and Save and close returns to settings', async () => { let body: Record | null = null const { wrapper, router, calls } = await mountApp( '/settings', routes({ [`PUT ${PROFILES}/9`]: async (request) => { body = (await request.json()) as Record return { body: { data: { id: 9, ...body }, meta: {} } } }, }), { attach: true }, ) const link = wrapper.find('[data-settings="profile"]') expect(link.attributes('href')).toBe('/acme/companions/profiles') await link.trigger('click', { button: 0 }) await flushPromises() expect(requestsTo(calls, 'GET', `${PROFILES}/singleton`)).toHaveLength(1) expect(router.currentRoute.value.path).toBe('/acme/companions/profiles/9') expect(wrapper.find('[data-action="delete"]').exists()).toBe(false) expect(wrapper.find('[data-action="back"]').attributes('aria-label')).toBe('Wróć do ustawień') expect(wrapper.find('#field-title').element.value).toBe('Main site') // Save stays on the form. await wrapper.find('#field-title').setValue('Renamed') await wrapper.find('[data-action="save"]').trigger('click') await flushPromises() expect(body).toEqual({ title: 'Renamed' }) expect(router.currentRoute.value.path).toBe('/acme/companions/profiles/9') // Save and close returns to the settings index. await wrapper.find('[data-action="save-close"]').trigger('click') await flushPromises() expect(requestsTo(calls, 'PUT', `${PROFILES}/9`)).toHaveLength(2) expect(router.currentRoute.value.path).toBe('/settings') }) it('back returns to the settings index', async () => { const { wrapper, router } = await mountApp('/acme/companions/profiles/9', routes(), { attach: true }) expect(wrapper.find('[data-action="delete"]').exists()).toBe(false) await wrapper.find('[data-action="back"]').trigger('click') await flushPromises() expect(router.currentRoute.value.path).toBe('/settings') }) it('a link to a controller that is not a singleton opens its list', async () => { const { wrapper, router, calls } = await mountApp( '/settings', routes({ [`GET ${PROFILES}/singleton`]: { status: 404, body: notFound }, [`GET ${PROFILES}/schema/list`]: { body: listSchemaFixture }, [`GET ${PROFILES}`]: { body: listFixture }, }), { attach: true }, ) await wrapper.find('[data-settings="profile"]').trigger('click', { button: 0 }) await flushPromises() expect(requestsTo(calls, 'GET', `${PROFILES}/singleton`)).toHaveLength(0) expect(router.currentRoute.value.path).toBe('/acme/companions/profiles') expect(wrapper.find('[data-list-failed]').exists()).toBe(false) }) it('the list route of a singleton, opened directly (a new tab), opens its record', async () => { const { wrapper, router, calls } = await mountApp('/acme/companions/profiles', routes(), { attach: true }) await vi.waitFor(() => expect(router.currentRoute.value.path).toBe('/acme/companions/profiles/9')) await flushPromises() expect(requestsTo(calls, 'GET', `${PROFILES}/singleton`)).toHaveLength(1) expect(wrapper.find('#field-title').element.value).toBe('Main site') }) it('a list that answers 404 for a controller that is not a singleton shows the failure', async () => { const { wrapper, router } = await mountApp( '/acme/companions/profiles', routes({ [`GET ${PROFILES}/singleton`]: { status: 404, body: notFound } }), { attach: true }, ) await flushPromises() expect(router.currentRoute.value.path).toBe('/acme/companions/profiles') expect(wrapper.find('[data-list-failed]').exists()).toBe(true) }) it('a modified click opens the link as it is, without asking for the record', async () => { const { wrapper, router, calls } = await mountApp('/settings', routes(), { attach: true }) await wrapper.find('[data-settings="profile"]').trigger('click', { button: 0, ctrlKey: true }) await flushPromises() expect(requestsTo(calls, 'GET', `${PROFILES}/singleton`)).toHaveLength(0) expect(router.currentRoute.value.path).toBe('/settings') }) it('a form without the singleton flag keeps Delete and returns to its list', async () => { const schema = singletonSchema() schema.data.singleton = false const { wrapper, router } = await mountApp( '/acme/companions/profiles/9', routes({ [`GET ${PROFILES}/schema/form`]: { body: schema }, [`GET ${PROFILES}/singleton`]: { status: 404, body: notFound } }), { attach: true }, ) expect(wrapper.find('[data-action="delete"]').exists()).toBe(true) await wrapper.find('[data-action="back"]').trigger('click') await flushPromises() expect(router.currentRoute.value.path).toBe('/acme/companions/profiles') }) })