feat(admin): render mltextarea fields per locale
- MLField.vue is the one locale-switching wrapper; MLTextField, MLMarkdownField and the new MLTextareaField are shells around it - isMLFieldType replaces the inline ML type-name checks in formState, FormView and RelationChildModal - register mltextarea as a group-labelled control; rebuild dist
This commit is contained in:
@@ -630,4 +630,27 @@ describe('password and preset on the form (UI-SPEC S7, D-19, D-27 G7)', () => {
|
||||
await localeSelect(wrapper, 'slug').setValue('pl')
|
||||
expect(editor(wrapper, 'slug').element.value).toBe('')
|
||||
})
|
||||
|
||||
it('edits an mltextarea field per locale and saves the nested map', async () => {
|
||||
const schema = mlSchema('mltextarea', 'text')
|
||||
for (const item of schema.body.data.fields) {
|
||||
if (item.name === 'name') {
|
||||
item.size = 'small'
|
||||
}
|
||||
}
|
||||
const record = clone(rosterRecordFixture)
|
||||
record.data.name = { en: 'Ada\nLovelace', pl: '' }
|
||||
const { wrapper, calls } = await mountApp(
|
||||
'/acme/roster/people/1',
|
||||
people({ [`GET ${PEOPLE}/schema/form`]: schema, [`GET ${PERSON}`]: { body: record }, [`PUT ${PERSON}`]: { body: rosterRecordFixture } }),
|
||||
)
|
||||
const textarea = wrapper.find<HTMLTextAreaElement>('#field-name-editor')
|
||||
expect(textarea.element.tagName).toBe('TEXTAREA')
|
||||
expect(textarea.element.value).toBe('Ada\nLovelace')
|
||||
await localeSelect(wrapper, 'name').setValue('pl')
|
||||
await wrapper.find<HTMLTextAreaElement>('#field-name-editor').setValue('Ada po polsku')
|
||||
await save(wrapper)
|
||||
const body = (await requestsTo(calls, 'PUT', PERSON)[0]!.clone().json()) as Record<string, unknown>
|
||||
expect(body.name).toEqual({ en: 'Ada\nLovelace', pl: 'Ada po polsku' })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,9 +4,10 @@ import { ref } from 'vue'
|
||||
import type { FormField } from '../../src/api/types'
|
||||
import MarkdownField from '../../src/components/form/fields/MarkdownField.vue'
|
||||
import MLMarkdownField from '../../src/components/form/fields/MLMarkdownField.vue'
|
||||
import MLTextareaField from '../../src/components/form/fields/MLTextareaField.vue'
|
||||
import MLTextField from '../../src/components/form/fields/MLTextField.vue'
|
||||
import { FORM_ENABLED_LOCALES } from '../../src/components/form/formContext'
|
||||
import { editablePayload, initialValues, mergeMLValue } from '../../src/components/form/formState'
|
||||
import { editablePayload, initialValues, isMLFieldType, mergeMLValue } from '../../src/components/form/formState'
|
||||
import { isRegistered, rendererFor } from '../../src/components/form/registry'
|
||||
import { API, mockApi, resetState } from '../helpers'
|
||||
|
||||
@@ -16,7 +17,7 @@ function field(type: string, name = 'title'): FormField {
|
||||
|
||||
const enabledLocales = ref<readonly string[]>(['en', 'pl'])
|
||||
|
||||
function mlMount(component: typeof MLTextField | typeof MLMarkdownField, props: object) {
|
||||
function mlMount(component: typeof MLTextField | typeof MLMarkdownField | typeof MLTextareaField, props: object) {
|
||||
return mount(component, {
|
||||
props: props as never,
|
||||
attachTo: document.body,
|
||||
@@ -198,3 +199,77 @@ describe('ML field registry and nested save body', () => {
|
||||
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([{ en: '# Hello', pl: '# Witaj' }])
|
||||
})
|
||||
})
|
||||
|
||||
describe('mltextarea field', () => {
|
||||
function excerpt(size?: string): FormField {
|
||||
return { name: 'excerpt', label: 'Excerpt', type: 'mltextarea', ...(size ? { size } : {}) } as FormField
|
||||
}
|
||||
|
||||
it('treats mltext, mltextarea and mlmarkdown as ML field types', () => {
|
||||
expect(isMLFieldType('mltext')).toBe(true)
|
||||
expect(isMLFieldType('mltextarea')).toBe(true)
|
||||
expect(isMLFieldType('mlmarkdown')).toBe(true)
|
||||
expect(isMLFieldType('text')).toBe(false)
|
||||
expect(isMLFieldType('textarea')).toBe(false)
|
||||
expect(isMLFieldType('markdown')).toBe(false)
|
||||
expect(isMLFieldType(undefined)).toBe(false)
|
||||
})
|
||||
|
||||
it('sends an mltextarea value as its locale record', () => {
|
||||
const payload = editablePayload([excerpt('small')], {
|
||||
excerpt: { en: 'Line 1\nLine 2', pl: 'Linia', de: 3 },
|
||||
})
|
||||
expect(payload).toEqual({ excerpt: { en: 'Line 1\nLine 2', pl: 'Linia' } })
|
||||
})
|
||||
|
||||
it('seeds every enabled locale on create', () => {
|
||||
const values = initialValues([excerpt('small')], ['en', 'pl'])
|
||||
expect(values.excerpt).toEqual({ en: '', pl: '' })
|
||||
})
|
||||
|
||||
it('renders a sized textarea with one locale select and edits the active locale', async () => {
|
||||
const wrapper = mlMount(MLTextareaField, {
|
||||
field: excerpt('small'),
|
||||
modelValue: { en: 'Line 1\nLine 2', pl: 'Linia' },
|
||||
controlId: 'f-excerpt',
|
||||
})
|
||||
const root = wrapper.find('[data-ml-field]')
|
||||
expect(root.attributes('data-ml-field')).toBe('mltextarea')
|
||||
expect(root.attributes('role')).toBe('group')
|
||||
expect(wrapper.findAll('[data-ml-locale]')).toHaveLength(1)
|
||||
expect(wrapper.findAll('[data-ml-locale] option').map((node) => node.text())).toEqual(['en', 'pl'])
|
||||
expect(wrapper.find('input').exists()).toBe(false)
|
||||
const textarea = wrapper.find('textarea')
|
||||
expect(textarea.attributes('rows')).toBe('4')
|
||||
expect(textarea.element.value).toBe('Line 1\nLine 2')
|
||||
await wrapper.find('[data-ml-locale]').setValue('pl')
|
||||
expect(wrapper.find('textarea').element.value).toBe('Linia')
|
||||
await wrapper.find('textarea').setValue('Nowa\nlinia')
|
||||
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([{ en: 'Line 1\nLine 2', pl: 'Nowa\nlinia' }])
|
||||
})
|
||||
|
||||
it('uses the default rows without a size', () => {
|
||||
const wrapper = mlMount(MLTextareaField, {
|
||||
field: excerpt(),
|
||||
modelValue: { en: '', pl: '' },
|
||||
controlId: 'f-excerpt',
|
||||
})
|
||||
expect(wrapper.find('textarea').attributes('rows')).toBe('5')
|
||||
})
|
||||
|
||||
it('follows the locale chosen on another ML control', async () => {
|
||||
const title = mlMount(MLTextField, {
|
||||
field: field('mltext'),
|
||||
modelValue: { en: 'Hello', pl: 'Witaj' },
|
||||
controlId: 'f-title',
|
||||
})
|
||||
const body = mlMount(MLTextareaField, {
|
||||
field: excerpt('small'),
|
||||
modelValue: { en: 'EN', pl: 'PL' },
|
||||
controlId: 'f-excerpt',
|
||||
})
|
||||
await title.find('[data-ml-locale]').setValue('pl')
|
||||
expect((body.find('[data-ml-locale]').element as HTMLSelectElement).value).toBe('pl')
|
||||
expect(body.find('textarea').element.value).toBe('PL')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,6 +9,7 @@ import TextareaField from '../../src/components/form/fields/TextareaField.vue'
|
||||
import PartialField from '../../src/components/form/fields/PartialField.vue'
|
||||
import MarkdownField from '../../src/components/form/fields/MarkdownField.vue'
|
||||
import MLMarkdownField from '../../src/components/form/fields/MLMarkdownField.vue'
|
||||
import MLTextareaField from '../../src/components/form/fields/MLTextareaField.vue'
|
||||
import MLTextField from '../../src/components/form/fields/MLTextField.vue'
|
||||
import PasswordField from '../../src/components/form/fields/PasswordField.vue'
|
||||
import PermissionEditorField from '../../src/components/form/fields/PermissionEditorField.vue'
|
||||
@@ -203,7 +204,15 @@ describe('markdown and multilingual field types (Phase 14.2.1)', () => {
|
||||
expect(groupLabelled('mlmarkdown')).toBe(true)
|
||||
})
|
||||
|
||||
it.each(['mlunknown', 'MLTEXT', 'richtext', 'markdowneditor'])(
|
||||
it('registers mltextarea as an editable group-labelled type', () => {
|
||||
expect(rendererFor('mltextarea')).toBe(MLTextareaField)
|
||||
expect(isRegistered('mltextarea')).toBe(true)
|
||||
expect(needsRecord('mltextarea')).toBe(false)
|
||||
expect(ownsLabel('mltextarea')).toBe(false)
|
||||
expect(groupLabelled('mltextarea')).toBe(true)
|
||||
})
|
||||
|
||||
it.each(['mlunknown', 'MLTEXT', 'MLTEXTAREA', 'richtext', 'markdowneditor'])(
|
||||
'falls back to UnsupportedField for unknown ML type %j',
|
||||
(type) => {
|
||||
expect(isRegistered(type)).toBe(false)
|
||||
|
||||
Reference in New Issue
Block a user