import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import SelectBtnField from '../SelectBtnField.vue' describe('SelectBtnField', () => { it('renders correctly', () => { const wrapper = mount(SelectBtnField) expect(wrapper.html()).toMatchSnapshot() }) it('renders correctly with props', () => { const wrapper = mount(SelectBtnField, { props: { label: 'Test', hint: 'Test', items: [ { text: 'Test', value: 'test', }, { text: 'Test 2', value: '', }, { text: 'Test 3', value: 'test3', }, ], }, }) expect(wrapper.html()).toMatchSnapshot() }) it('render correctly in multiple mode', () => { const wrapper = mount(SelectBtnField, { props: { label: 'Test', hint: 'Test', items: [ { text: 'Test', value: 'test', }, { text: 'Test 2', value: '', }, { text: 'Test 3', value: 'test3', }, ], multiple: true, }, }) expect(wrapper.html()).toMatchSnapshot() }) it('emits an update event when the value change in single mode', async () => { const wrapper = mount(SelectBtnField, { props: { label: 'Test', hint: 'Test', items: [ { text: 'Test', value: 'test', }, ], }, }) await wrapper.find('[role="option"]').trigger('click') expect(wrapper.emitted()).toHaveProperty('update:modelValue') await wrapper.find('[role="option"]').trigger('click') expect(wrapper.emitted('update:modelValue')).toEqual([['test'], [null]]) }) it(`emits an array of values when the value changes in multiple mode`, async () => { const wrapper = mount(SelectBtnField, { props: { label: 'Test', hint: 'Test', items: [ { text: 'Test', value: 'test', }, { text: 'Test 2', value: 'test2', }, { text: 'Test 3', value: 'test3', }, ], multiple: true, }, }) await wrapper.find('li:nth-child(2)[role="option"]').trigger('click') await wrapper.find('li:nth-child(3)[role="option"]').trigger('click') await wrapper.find('li:nth-child(2)[role="option"]').trigger('click') expect(wrapper.emitted('update:modelValue')).toEqual([ [['test2']], [['test2', 'test3']], [['test3']], ]) }) it('handles multiple mode safely when modelValue is null', async () => { const wrapper = mount(SelectBtnField, { props: { label: 'Test', hint: 'Test', items: [ { text: 'Test', value: 'test', }, { text: 'Test 2', value: 'test2', }, { text: 'Test 3', value: 'test3', }, ], multiple: true, modelValue: null, }, }) await wrapper.find('[role="option"]').trigger('click') expect(wrapper.emitted('update:modelValue')).toEqual([ [['test']], ]) }) it(`display correctly with an error`, () => { const wrapper = mount(SelectBtnField, { props: { label: 'Test', hint: 'Test', items: [ { text: 'Test', value: 'test', }, { text: 'Test 2', value: 'test2', }, { text: 'Test 3', value: 'test3', }, ], error: true, errorMessages: ['Test'], }, }) expect(wrapper.html()).toMatchSnapshot() }) it(`clear the others values when defined to unique`, async () => { const wrapper = mount(SelectBtnField, { props: { label: 'Test', items: [ { text: 'Test 1', value: 'test1', }, { text: 'Test 2', value: 'test2', }, { text: 'Other', value: 'other', unique: true, }, ], multiple: true, }, }) await wrapper.find('li:nth-child(1)[role="option"]').trigger('click') await wrapper.find('li:nth-child(2)[role="option"]').trigger('click') await wrapper.find('li:nth-child(3)[role="option"]').trigger('click') await wrapper.find('li:nth-child(2)[role="option"]').trigger('click') expect(wrapper.emitted('update:modelValue')).toEqual([ [['test1']], [['test1', 'test2']], [['other']], [['test2']], ]) }) it(`display correctly in dark mode with an error`, () => { const DarkMode = { template: `
`, components: { SelectBtnField, }, data() { return { props: { label: 'Test', hint: 'Test', items: [ { text: 'Test 1', value: 'test1', }, { text: 'Test 2', value: 'test2', }, ], error: true, errorMessages: ['Test'], multiple: true, inline: true, }, } }, } const wrapper = mount(DarkMode) wrapper.find('li:nth-child(1)[role="option"]').trigger('click') wrapper.find('li:nth-child(2)[role="option"]').trigger('click') expect(wrapper.html()).toMatchSnapshot() }) it(`display correctly with in dark mode with an hint`, () => { const DarkMode = { template: `
`, components: { SelectBtnField, }, data() { return { props: { label: 'Test', hint: 'Test', items: [ { text: 'Test 1', value: 'test1', }, { text: 'Test 2', value: 'test2', }, ], multiple: true, inline: true, }, } }, } const wrapper = mount(DarkMode) expect(wrapper.html()).toMatchSnapshot() }) it('do not allow to select an item when the readonly prop is defined', async () => { const wrapper = mount(SelectBtnField, { props: { label: 'Test', hint: 'Test', items: [ { text: 'Test 1', value: 'test1', }, { text: 'Test 2', value: 'test2', }, ], readonly: true, }, }) await wrapper.find('[role="option"]').trigger('click') expect(wrapper.emitted()).not.toHaveProperty('update:modelValue') }) it('emits update:error and update:error-messages when an item is selected', async () => { const wrapper = mount(SelectBtnField, { props: { items: [{ text: 'Test', value: 'test' }], error: true, errorMessages: ['Champ requis'], }, }) await wrapper.find('[role="option"]').trigger('click') expect(wrapper.emitted('update:error')).toEqual([[false]]) expect(wrapper.emitted('update:error-messages')).toEqual([[undefined]]) }) it('filters out items with null or undefined value', () => { const wrapper = mount(SelectBtnField, { props: { items: [ { text: 'Valide', value: 'valide' }, { text: 'Null', value: null as unknown as string }, { text: 'Undefined', value: undefined as unknown as string }, ], }, }) const options = wrapper.findAll('[role="option"]') expect(options).toHaveLength(1) expect(options[0]!.text()).toContain('Valide') }) it('syncs internalValue when modelValue prop changes externally', async () => { const wrapper = mount(SelectBtnField, { props: { items: [ { text: 'A', value: 'a' }, { text: 'B', value: 'b' }, ], modelValue: 'a', }, }) let selectedItem = wrapper.find('[aria-selected="true"]') expect(selectedItem.text()).toContain('A') await wrapper.setProps({ modelValue: 'b' }) selectedItem = wrapper.find('[aria-selected="true"]') expect(selectedItem.text()).toContain('B') }) it('syncs internalValue array when modelValue changes in multiple mode', async () => { const wrapper = mount(SelectBtnField, { props: { items: [ { text: 'A', value: 'a' }, { text: 'B', value: 'b' }, ], multiple: true, modelValue: ['a'], }, }) expect(wrapper.find('li:nth-child(1)').attributes('aria-checked')).toBe('true') expect(wrapper.find('li:nth-child(2)').attributes('aria-checked')).toBe('false') await wrapper.setProps({ modelValue: ['a', 'b'] }) expect(wrapper.find('li:nth-child(2)').attributes('aria-checked')).toBe('true') }) it('sets correct ARIA attributes on the listbox', () => { const wrapper = mount(SelectBtnField, { props: { label: 'Choix', multiple: true, inline: true, error: true, }, }) const listbox = wrapper.find('[role="listbox"]') expect(listbox.attributes('aria-label')).toBe('Choix') expect(listbox.attributes('aria-multiselectable')).toBe('true') expect(listbox.attributes('aria-orientation')).toBe('horizontal') expect(listbox.attributes('aria-invalid')).toBe('true') }) it('sets aria-readonly on the listbox when readonly', () => { const wrapper = mount(SelectBtnField, { props: { readonly: true }, }) expect(wrapper.find('[role="listbox"]').attributes('aria-readonly')).toBe('true') }) it('navigates to next item with ArrowRight key', async () => { const wrapper = mount(SelectBtnField, { props: { items: [ { text: 'A', value: 'a' }, { text: 'B', value: 'b' }, { text: 'C', value: 'c' }, ], }, }) await wrapper.find('[role="listbox"]').trigger('keydown', { key: 'ArrowRight' }) await wrapper.find('[role="listbox"]').trigger('keydown', { key: 'ArrowRight' }) const emitted = wrapper.emitted('update:modelValue') expect(emitted).toBeTruthy() expect(emitted!.length).toBeGreaterThanOrEqual(1) }) it('wraps around to first item when navigating past last with ArrowDown', async () => { const wrapper = mount(SelectBtnField, { props: { items: [ { text: 'A', value: 'a' }, { text: 'B', value: 'b' }, ], modelValue: 'b', }, }) await wrapper.find('[role="listbox"]').trigger('keydown', { key: 'ArrowDown' }) const emitted = wrapper.emitted('update:modelValue') expect(emitted).toBeTruthy() expect(emitted![emitted!.length - 1]![0]).toBe('a') }) it('wraps around to last item when navigating before first with ArrowUp', async () => { const wrapper = mount(SelectBtnField, { props: { items: [ { text: 'A', value: 'a' }, { text: 'B', value: 'b' }, ], modelValue: 'a', }, }) await wrapper.find('[role="listbox"]').trigger('keydown', { key: 'ArrowUp' }) const emitted = wrapper.emitted('update:modelValue') expect(emitted).toBeTruthy() expect(emitted![emitted!.length - 1]![0]).toBe('b') }) it('selects item with Space key', async () => { const wrapper = mount(SelectBtnField, { props: { items: [{ text: 'Test', value: 'test' }], }, }) await wrapper.find('[role="option"]').trigger('keydown', { key: ' ' }) expect(wrapper.emitted('update:modelValue')).toEqual([['test']]) }) it('selecting unique item when others are selected replaces all', async () => { const wrapper = mount(SelectBtnField, { props: { multiple: true, items: [ { text: 'A', value: 'a' }, { text: 'B', value: 'b' }, { text: 'Unique', value: 'unique', unique: true }, ], modelValue: ['a', 'b'], }, }) await wrapper.find('li:nth-child(3)[role="option"]').trigger('click') const emitted = wrapper.emitted('update:modelValue') expect(emitted![emitted!.length - 1]![0]).toEqual(['unique']) }) it('re-selecting a unique item in multiple mode deselects it', async () => { const wrapper = mount(SelectBtnField, { props: { multiple: true, items: [ { text: 'Unique', value: 'unique', unique: true }, ], modelValue: ['unique'], }, }) await wrapper.find('[role="option"]').trigger('click') const emitted = wrapper.emitted('update:modelValue') expect(emitted![emitted!.length - 1]![0]).toEqual([]) }) it('displays hint when no errorMessages', () => { const wrapper = mount(SelectBtnField, { props: { hint: 'Aide contextuelle', items: [{ text: 'A', value: 'a' }], }, }) expect(wrapper.text()).toContain('Aide contextuelle') }) it('displays errorMessages instead of hint when both are provided', () => { const wrapper = mount(SelectBtnField, { props: { hint: 'Aide contextuelle', errorMessages: ['Champ invalide'], items: [{ text: 'A', value: 'a' }], }, }) expect(wrapper.text()).toContain('Champ invalide') expect(wrapper.text()).not.toContain('Aide contextuelle') }) })