- 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
121 lines
5.3 KiB
TypeScript
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('')
|
|
})
|
|
})
|