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