Files
summercms/admin/tests/form/MLField.test.ts
Jakub Zych 1541cd4167 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
2026-10-11 16:13:19 +02:00

121 lines
5.3 KiB
TypeScript

// 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('')
})
})