test(17-06): framework and admin SPA unit tests for the blog companions
- signpost: activation refusals, host overrides, D-02 pattern layers incl. PatternFrom, ReferenceOptions depths - seaglass: removal report, bluemonday bypass corpus, Check - pact Option.Depth omitempty and the menu item type contracts; party activates menu item types after Register and before the first Boot - cabana: repeater compile and decode, dependsOn compile and recheck, richeditor keys and notices, reorder and singleton boot checks (Docker-free); repeater save, dependent recheck, richeditor save, reorder and singleton routes on Postgres; conformance cases for the documented 404, 409 and 422 answers of the new routes - admin: richeditor value contract, link popover, notices and chunk failure; repeater bounds, remove and reorder, collapsed rows; dependent dropdown states and last request wins; reorder announcements, partition switch and tree rules; SingletonForm spec - gate: required names in the go, admin and postgres stages
This commit is contained in:
126
admin/tests/form/SingletonForm.test.ts
Normal file
126
admin/tests/form/SingletonForm.test.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { enableAutoUnmount, flushPromises } from '@vue/test-utils'
|
||||
import { setSettings } from '../../src/state/useSettings'
|
||||
import { clone, companionsFormSchemaFixture } 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; 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', async () => {
|
||||
const { wrapper, router, calls } = await mountApp(
|
||||
'/settings',
|
||||
routes({ [`GET ${PROFILES}/singleton`]: { status: 404, body: notFound } }),
|
||||
{ attach: true },
|
||||
)
|
||||
await wrapper.find('[data-settings="profile"]').trigger('click', { button: 0 })
|
||||
await flushPromises()
|
||||
expect(requestsTo(calls, 'GET', `${PROFILES}/singleton`)).toHaveLength(1)
|
||||
expect(router.currentRoute.value.path).toBe('/acme/companions/profiles')
|
||||
})
|
||||
|
||||
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 } }), { 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')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user