test(admin): import the untested SPA modules directly for check-phase10 hygiene
- mlLocale: type predicates, localeRecord, the ML_LOCALE_CHANGE broadcast - MLField: locale switch, whole-map emit, broadcast follow, content-locale - FileCaptionModal: prefill, save via FileRoutes.update, 422 stays, danger toast - ApiTokensPanel and ApiTokenCreateDialog mounted directly next to the app tests
This commit is contained in:
120
admin/tests/form/MLField.test.ts
Normal file
120
admin/tests/form/MLField.test.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
// MLField, the locale-switching wrapper of the ML controls, mounted on its own
|
||||
// with the enabled locales FormView would provide: it edits only the active
|
||||
// locale, emits the whole map, follows ML_LOCALE_CHANGE from every other ML
|
||||
// control and hands content-locale only to an inner control that asks for it.
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
|
||||
import { defineComponent, h, markRaw, ref, type Component } from 'vue'
|
||||
import type { FormField } from '../../src/api/types'
|
||||
import MLField from '../../src/components/form/fields/MLField.vue'
|
||||
import TextField from '../../src/components/form/fields/TextField.vue'
|
||||
import { FORM_ENABLED_LOCALES } from '../../src/components/form/formContext'
|
||||
import { ML_LOCALE_CHANGE } from '../../src/components/form/mlLocale'
|
||||
import { resetState } from '../helpers'
|
||||
|
||||
const enabledLocales = ref<readonly string[]>(['en', 'pl'])
|
||||
|
||||
function field(): FormField {
|
||||
return { name: 'title', label: 'Title', type: 'mltext' } as FormField
|
||||
}
|
||||
|
||||
function mountML(props: { inner?: Component; contentLocale?: boolean; modelValue?: unknown; controlId?: string } = {}) {
|
||||
return mount(MLField, {
|
||||
props: {
|
||||
field: field(),
|
||||
modelValue: props.modelValue ?? { en: 'Hello', pl: 'Cześć' },
|
||||
controlId: props.controlId ?? 'f-title',
|
||||
// markRaw: test-utils props are reactive, a component prop must not be.
|
||||
inner: markRaw(props.inner ?? TextField),
|
||||
innerType: 'text',
|
||||
mlType: 'mltext',
|
||||
contentLocale: props.contentLocale,
|
||||
},
|
||||
attachTo: document.body,
|
||||
global: { provide: { [FORM_ENABLED_LOCALES as symbol]: enabledLocales } },
|
||||
})
|
||||
}
|
||||
|
||||
/** An inner control that declares content-locale and shows what it got. */
|
||||
const LocaleProbe = defineComponent({
|
||||
props: {
|
||||
field: { type: Object, required: true },
|
||||
modelValue: { type: null, default: '' },
|
||||
controlId: { type: String, default: '' },
|
||||
contentLocale: { type: String, default: undefined },
|
||||
},
|
||||
setup(props) {
|
||||
return () => h('span', { 'data-probe': '', 'data-content-locale': props.contentLocale ?? 'none' }, String(props.modelValue))
|
||||
},
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
resetState()
|
||||
enabledLocales.value = ['en', 'pl']
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
|
||||
enableAutoUnmount(afterEach)
|
||||
|
||||
describe('MLField mounted directly', () => {
|
||||
it('renders the ML type, the enabled locales and the first locale text', () => {
|
||||
const wrapper = mountML()
|
||||
expect(wrapper.attributes('data-ml-field')).toBe('mltext')
|
||||
const options = wrapper.findAll('[data-ml-locale] option').map((option) => option.attributes('value'))
|
||||
expect(options).toEqual(['en', 'pl'])
|
||||
expect((wrapper.find('[data-ml-locale]').element as HTMLSelectElement).value).toBe('en')
|
||||
expect((wrapper.find('input').element as HTMLInputElement).value).toBe('Hello')
|
||||
})
|
||||
|
||||
it('emits the whole map with only the active locale changed', async () => {
|
||||
const wrapper = mountML()
|
||||
await wrapper.find('input').setValue('Hi there')
|
||||
expect(wrapper.emitted('update:modelValue')).toEqual([[{ en: 'Hi there', pl: 'Cześć' }]])
|
||||
})
|
||||
|
||||
it('switches locale from the select and broadcasts it to every other ML control', async () => {
|
||||
const first = mountML({ controlId: 'f-one' })
|
||||
const second = mountML({ controlId: 'f-two' })
|
||||
await first.find('[data-ml-locale]').setValue('pl')
|
||||
await flushPromises()
|
||||
expect((first.find('input').element as HTMLInputElement).value).toBe('Cześć')
|
||||
expect((second.find('[data-ml-locale]').element as HTMLSelectElement).value).toBe('pl')
|
||||
expect((second.find('input').element as HTMLInputElement).value).toBe('Cześć')
|
||||
})
|
||||
|
||||
it('edits the switched locale only', async () => {
|
||||
const wrapper = mountML()
|
||||
await wrapper.find('[data-ml-locale]').setValue('pl')
|
||||
await wrapper.find('input').setValue('Witaj')
|
||||
expect(wrapper.emitted('update:modelValue')).toEqual([[{ en: 'Hello', pl: 'Witaj' }]])
|
||||
})
|
||||
|
||||
it('follows a window ML_LOCALE_CHANGE event and ignores an empty one', async () => {
|
||||
const wrapper = mountML()
|
||||
window.dispatchEvent(new CustomEvent(ML_LOCALE_CHANGE, { detail: 'pl' }))
|
||||
await flushPromises()
|
||||
expect((wrapper.find('input').element as HTMLInputElement).value).toBe('Cześć')
|
||||
window.dispatchEvent(new CustomEvent(ML_LOCALE_CHANGE, { detail: '' }))
|
||||
await flushPromises()
|
||||
expect((wrapper.find('[data-ml-locale]').element as HTMLSelectElement).value).toBe('pl')
|
||||
})
|
||||
|
||||
it('hands the active locale to an inner control only with contentLocale', async () => {
|
||||
const withLocale = mountML({ inner: LocaleProbe, contentLocale: true, controlId: 'f-with' })
|
||||
expect(withLocale.find('[data-probe]').attributes('data-content-locale')).toBe('en')
|
||||
await withLocale.find('[data-ml-locale]').setValue('pl')
|
||||
expect(withLocale.find('[data-probe]').attributes('data-content-locale')).toBe('pl')
|
||||
expect(withLocale.find('[data-probe]').text()).toBe('Cześć')
|
||||
|
||||
const without = mountML({ inner: LocaleProbe, controlId: 'f-without' })
|
||||
expect(without.find('[data-probe]').attributes('data-content-locale')).toBe('none')
|
||||
})
|
||||
|
||||
it('shows an empty input for a locale without text and for a non-map value', () => {
|
||||
const wrapper = mountML({ modelValue: 'not a map' })
|
||||
expect((wrapper.find('input').element as HTMLInputElement).value).toBe('')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user