feat(18.1-01): task 2 taglist string mode, customTags and all save sites

- string mode: split on the separator (comma or whitespace), normalised,
  stored joined by a comma or one space; null stores an empty string
- customTags false refuses a tag outside options (taglist.not_allowed)
- taglist checked and hydrated on relation forms and settings forms;
  settings bind taglist columns; required checked on the tags
- admin: TaglistField string mode and closed option lists, preview renders
  the tags read-only as a comma-separated list; rebuilt dist
- docs: Taglist section covers both modes, separator and customTags
This commit is contained in:
Jakub Zych
2026-10-14 15:52:52 +02:00
parent 349bad5596
commit cf63fb3152
17 changed files with 308 additions and 60 deletions

View File

@@ -5,6 +5,7 @@ import { enableAutoUnmount, mount, type VueWrapper } from '@vue/test-utils'
import { nextTick } from 'vue'
import type { FormField } from '../../src/api/types'
import { setBundle } from '../../src/app/i18n'
import PreviewField from '../../src/components/form/PreviewField.vue'
import TaglistField from '../../src/components/form/fields/TaglistField.vue'
import { langFixture } from '../fixtures/typed'
import { resetState } from '../helpers'
@@ -115,4 +116,39 @@ describe('TaglistField', () => {
expect(wrapper.find('b').exists()).toBe(false)
expect(chips(wrapper)).toEqual(['<b>bold</b>'])
})
it('splits and joins a string-mode value on its space separator', async () => {
const wrapper = mountField({ modelValue: 'x y', field: { mode: 'string', separator: 'space', options: [] } })
expect(chips(wrapper)).toEqual(['x', 'y'])
await type(wrapper, 'z ')
expect(emitted(wrapper)).toEqual(['x y z'])
})
it('joins a string-mode value with a comma by default', async () => {
const wrapper = mountField({ modelValue: 'a,b', field: { mode: 'string', separator: 'comma' } })
expect(chips(wrapper)).toEqual(['a', 'b'])
await type(wrapper, 'c')
await input(wrapper).trigger('keydown', { key: 'Enter' })
expect(emitted(wrapper)).toEqual(['a,b,c'])
})
it('refuses a tag outside the options when customTags is false', async () => {
const wrapper = mountField({ modelValue: [], field: { customTags: false } })
await type(wrapper, 'delta')
await input(wrapper).trigger('keydown', { key: 'Enter' })
expect(emitted(wrapper)).toEqual([])
await type(wrapper, 'beta')
await input(wrapper).trigger('keydown', { key: 'Enter' })
expect(emitted(wrapper)).toEqual([['beta']])
})
it('renders the tags joined as text on the preview screen', () => {
const wrapper = mount(PreviewField, {
props: { field: taglistField(), value: ['alpha', '<i>beta</i>'] },
attachTo: document.body,
})
expect(wrapper.find('[data-taglist-readonly]').text()).toBe('alpha, <i>beta</i>')
expect(wrapper.find('i').exists()).toBe(false)
expect(wrapper.find('input').exists()).toBe(false)
})
})