A settings link to a singleton controller only worked on a plain left click; a new tab landed on the list route, which answers 404, and every non-singleton link paid an extra /singleton request. The settings index now links like any controller link, and the list view opens the controller's one record when its list answers 404 and the singleton route answers an id.
156 lines
6.6 KiB
TypeScript
156 lines
6.6 KiB
TypeScript
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<string, Route> = {}): Record<string, Route> {
|
|
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<string, unknown> | null = null
|
|
const { wrapper, router, calls } = await mountApp(
|
|
'/settings',
|
|
routes({
|
|
[`PUT ${PROFILES}/9`]: async (request) => {
|
|
body = (await request.json()) as Record<string, unknown>
|
|
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<HTMLInputElement>('#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 without asking for a record', 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<HTMLInputElement>('#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')
|
|
})
|
|
})
|