test(18.1-06): taglist and flat reorder unit matrices, partitioned flat smoke and admin specs (task 2)
- TestTaglistFieldValidate: both modes, trim, dedupe, empty, null, wrong shapes, customTags false, required; TestTaglistFieldHydrate: stored NULL and bad text as [] - TestReorderFlatNodes: parent 422, duplicate and zero id 422, missing or extra 409, no depth or cycle check; TestReorderFlatCompile checks the localized list schema - TestReorderFlatSmoke/partitioned: flat reorder within one partition on Postgres - vitest: script markup as text, Backspace, blur, paste, datalist, customTags, string mode; flat ArrowUp/ArrowDown and the before/after drop zones - export_test.go wrappers for the taglist save check, hydration and node check - check-phase18.1.sh requires the new names in the go, admin and postgres stages
This commit is contained in:
@@ -152,3 +152,96 @@ describe('TaglistField', () => {
|
||||
expect(wrapper.find('input').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
// Plan 18.1-06 coverage of the taglist control: markup safety (T-18.1-02),
|
||||
// keyboard add and remove, suggestions, customTags and string mode.
|
||||
describe('TaglistField coverage', () => {
|
||||
it('renders a tag holding a script element as text and runs nothing', async () => {
|
||||
;(window as unknown as { __pwned?: boolean }).__pwned = false
|
||||
const tag = '<script>window.__pwned = true</script>'
|
||||
const wrapper = mountField({ modelValue: [tag, '<img src=x onerror="window.__pwned=true">'] })
|
||||
await nextTick()
|
||||
expect(wrapper.find('script').exists()).toBe(false)
|
||||
expect(wrapper.find('img').exists()).toBe(false)
|
||||
expect(chips(wrapper)).toEqual([tag, '<img src=x onerror="window.__pwned=true">'])
|
||||
expect(wrapper.find('[data-taglist-remove]').attributes('aria-label')).toBe(`Remove ${tag}`)
|
||||
expect((window as unknown as { __pwned?: boolean }).__pwned).toBe(false)
|
||||
})
|
||||
|
||||
it('removes the last chip on Backspace in an empty input, and nothing while text is typed', async () => {
|
||||
const wrapper = mountField({ modelValue: ['a', 'b', 'c'] })
|
||||
await type(wrapper, 'x')
|
||||
await input(wrapper).trigger('keydown', { key: 'Backspace' })
|
||||
expect(emitted(wrapper)).toEqual([])
|
||||
await type(wrapper, '')
|
||||
await input(wrapper).trigger('keydown', { key: 'Backspace' })
|
||||
expect(emitted(wrapper)).toEqual([['a', 'b']])
|
||||
expect(chips(wrapper)).toEqual(['a', 'b'])
|
||||
await input(wrapper).trigger('keydown', { key: 'Backspace' })
|
||||
await input(wrapper).trigger('keydown', { key: 'Backspace' })
|
||||
await input(wrapper).trigger('keydown', { key: 'Backspace' })
|
||||
expect(emitted(wrapper)).toEqual([['a', 'b'], ['a'], []])
|
||||
expect(chips(wrapper)).toEqual([])
|
||||
})
|
||||
|
||||
it('adds the draft on blur', async () => {
|
||||
const wrapper = mountField({ modelValue: ['a'] })
|
||||
await type(wrapper, ' b ')
|
||||
await input(wrapper).trigger('blur')
|
||||
expect(emitted(wrapper)).toEqual([['a', 'b']])
|
||||
expect(input(wrapper).element.value).toBe('')
|
||||
})
|
||||
|
||||
it('adds every complete tag of a pasted list and keeps the unfinished rest as the draft', async () => {
|
||||
const wrapper = mountField({ modelValue: [] })
|
||||
await type(wrapper, 'a, b,,a,c')
|
||||
expect(emitted(wrapper)).toEqual([['a'], ['a', 'b']])
|
||||
expect(input(wrapper).element.value).toBe('c')
|
||||
await input(wrapper).trigger('keydown', { key: 'Enter' })
|
||||
expect(emitted(wrapper).at(-1)).toEqual(['a', 'b', 'c'])
|
||||
})
|
||||
|
||||
it('offers the options as datalist suggestions bound to the input, and none without options', () => {
|
||||
const wrapper = mountField({ modelValue: [] })
|
||||
const list = wrapper.find('datalist')
|
||||
expect(list.attributes('id')).toBe('f-tags-options')
|
||||
expect(input(wrapper).attributes('list')).toBe('f-tags-options')
|
||||
expect(list.findAll('option').map((o) => o.attributes('value'))).toEqual(['alpha', 'beta'])
|
||||
const bare = mountField({ modelValue: [], field: { options: [] } })
|
||||
expect(bare.find('datalist').exists()).toBe(false)
|
||||
expect(bare.find('input').attributes('list')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('with customTags false ignores a free tag typed with the separator but adds a suggested one', async () => {
|
||||
const wrapper = mountField({ modelValue: ['alpha'], field: { customTags: false } })
|
||||
await type(wrapper, 'gamma,')
|
||||
expect(emitted(wrapper)).toEqual([])
|
||||
expect(chips(wrapper)).toEqual(['alpha'])
|
||||
await type(wrapper, 'beta,')
|
||||
expect(emitted(wrapper)).toEqual([['alpha', 'beta']])
|
||||
})
|
||||
|
||||
it('splits a comma string-mode value, trims and drops duplicates, and emits the joined string', async () => {
|
||||
const wrapper = mountField({ modelValue: ' a , ,b,a', field: { mode: 'string', separator: 'comma', options: [] } })
|
||||
expect(chips(wrapper)).toEqual(['a', 'b'])
|
||||
await type(wrapper, 'c,d,')
|
||||
expect(emitted(wrapper)).toEqual(['a,b,c', 'a,b,c,d'])
|
||||
await wrapper.find('[data-taglist-remove]').trigger('click')
|
||||
expect(emitted(wrapper).at(-1)).toBe('b,c,d')
|
||||
await wrapper.findAll('[data-taglist-remove]').at(-1)!.trigger('click')
|
||||
await wrapper.findAll('[data-taglist-remove]').at(-1)!.trigger('click')
|
||||
await wrapper.findAll('[data-taglist-remove]').at(-1)!.trigger('click')
|
||||
expect(emitted(wrapper).at(-1)).toBe('')
|
||||
})
|
||||
|
||||
it('reads a string as no tags in array mode', () => {
|
||||
const wrapper = mountField({ modelValue: 'a,b' })
|
||||
expect(chips(wrapper)).toEqual([])
|
||||
})
|
||||
|
||||
it('marks the input required and invalid for the form', () => {
|
||||
const wrapper = mountField({ modelValue: [], invalid: true, field: { required: true } })
|
||||
expect(input(wrapper).attributes('aria-required')).toBe('true')
|
||||
expect(input(wrapper).attributes('aria-invalid')).toBe('true')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user