/* eslint-disable vue/one-component-per-file */ import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import FilterSideBar from '../FilterSideBar.vue' import { defineComponent } from 'vue' import { VApp } from 'vuetify/components' describe('FilterSideBar', () => { const EventSenderComponent = defineComponent({ emits: ['update:modelValue'], methods: { sentEvent() { this.$emit('update:modelValue', 'Jane Doe') }, }, template: '
lorem
', }) const TestComponent = defineComponent({ components: { VApp, FiltersSideBar: FilterSideBar, EventSenderComponent }, props: { modelValue: { type: Array, required: true, }, }, data() { return { localModelValue: this.modelValue, } }, watch: { localModelValue(newValue) { this.$emit('update:modelValue', newValue) }, }, template: ` `, }) it('renders correctly', () => { const wrapper = mount(TestComponent, { global: { stubs: { Teleport: true, }, }, props: { modelValue: [], }, }) expect(wrapper.find('.sy-filters-side-bar').exists()).toBe(true) }) it('renders correctly with an active filter', async () => { const wrapper = mount(TestComponent, { props: { modelValue: [ { name: 'name', title: 'Nom', value: 'John Doe', }, ], }, global: { stubs: { Teleport: true, }, }, }) await wrapper.find('.v-btn__content').trigger('click') await wrapper.find('.v-expansion-panel-title').trigger('click') expect(wrapper.find('.v-chip').text()).toBe('John Doe') }) it('renders correctly with multiple active filters', () => { const wrapper = mount(TestComponent, { propsData: { modelValue: [ { name: 'name', label: 'Nom', value: 'John Doe', }, { name: 'age', label: 'Âge', value: '18', }, ], }, global: { stubs: { Teleport: true, }, }, }) expect(wrapper.findAll('.v-expansion-panel').length).toBe(2) }) it('emits "update:modelValue" event when a filter is removed', async () => { const wrapper = mount(TestComponent, { props: { modelValue: [ { name: 'name', label: 'Nom', value: 'John Doe', }, ], }, global: { stubs: { Teleport: true, }, }, }) await wrapper.find('.v-btn__content').trigger('click') await wrapper.find('.v-expansion-panel-title').trigger('click') await wrapper.find('.remove-chip').trigger('click') await wrapper.find('button:nth-child(3)').trigger('click') const emittedValue = wrapper.emitted('update:modelValue')?.[0]?.[0] const expectedValue = [ { name: 'name', label: 'Nom', value: undefined, }, ] // Use JSON serialization for robust comparison in CI environments expect(JSON.stringify(emittedValue)).toBe(JSON.stringify(expectedValue)) }) it('renders the template corresponding to the filter name', async () => { const wrapper = mount(TestComponent, { props: { modelValue: [ { name: 'name', title: 'Nom', }, ], }, global: { stubs: { Teleport: true, }, }, }) expect(wrapper.find('.v-expansion-panel-title').text()).toBe('Nom') }) it('emits "update:modelValue" event when a filter is added', async () => { const wrapper = mount(TestComponent, { props: { modelValue: [ { name: 'name', title: 'Nom', }, ], }, global: { stubs: { Teleport: true, }, }, }) await wrapper.find('.v-btn__content').trigger('click') await wrapper.find('.v-expansion-panel-title').trigger('click') const EventSender = wrapper.findComponent(EventSenderComponent) await wrapper.find('button:nth-child(3)').trigger('click') EventSender.vm.sentEvent() expect(wrapper.emitted('update:modelValue')).toBeTruthy() }) it('reset one filter when the reset button of the filter is clicked', async () => { const wrapper = mount(TestComponent, { props: { modelValue: [ { name: 'name', title: 'Nom', value: 'John Doe', }, { name: 'age', title: 'Âge', value: '18', }, ], }, global: { stubs: { Teleport: true, }, }, }) await wrapper.find('.v-btn__content').trigger('click') await wrapper.find('.v-expansion-panel-title').trigger('click') await wrapper.find('[data-test-id="reset-btn"]').trigger('click') await wrapper.find('button:nth-child(3)').trigger('click') const emittedValue = wrapper.emitted('update:modelValue')?.[0]?.[0] const expectedValue = [ { name: 'name', title: 'Nom', value: undefined, }, { name: 'age', title: 'Âge', value: '18', }, ] // Use JSON serialization for robust comparison in CI environments expect(JSON.stringify(emittedValue)).toBe(JSON.stringify(expectedValue)) }) it('resets all the filters when the reset button is clicked', async () => { const wrapper = mount(TestComponent, { props: { modelValue: [ { name: 'name', title: 'Nom', value: 'John Doe', }, { name: 'age', title: 'Âge', value: '18', }, ], }, global: { stubs: { Teleport: true, }, }, }) await wrapper.find('.v-btn__content').trigger('click') await wrapper .find( '.v-navigation-drawer__content button[type="reset"]', ) .trigger('click') const emittedValue = wrapper.emitted('update:modelValue')?.[0]?.[0] const expectedValue = [ { name: 'name', title: 'Nom', value: undefined, }, { name: 'age', title: 'Âge', value: undefined, }, ] // Use JSON serialization for robust comparison in CI environments expect(JSON.stringify(emittedValue)).toBe(JSON.stringify(expectedValue)) }) it('open and close the drawer without changing the filters', async () => { const wrapper = mount(TestComponent, { props: { modelValue: [ { name: 'name', title: 'Nom', value: 'John Doe', }, { name: 'age', title: 'Âge', value: '18', }, ], }, global: { stubs: { Teleport: true, }, }, }) await wrapper.find('.v-btn__content').trigger('click') await wrapper .find( '.v-navigation-drawer__content button[aria-label="Fermer les filtres"]', ) .trigger('click') expect(wrapper.emitted('update:modelValue')).toBeUndefined() }) it('applies zIndex as inline style on the navigation drawer when provided', () => { const wrapper = mount( defineComponent({ components: { VApp, FiltersSideBar: FilterSideBar }, template: ` `, }), { global: { stubs: { Teleport: true }, }, }, ) const drawer = wrapper.find('.v-navigation-drawer') expect(drawer.attributes('style')).toContain('z-index: 2401') }) it('does not apply zIndex style on the navigation drawer when not provided', () => { const wrapper = mount( defineComponent({ components: { VApp, FiltersSideBar: FilterSideBar }, template: ` `, }), { global: { stubs: { Teleport: true }, }, }, ) const drawer = wrapper.find('.v-navigation-drawer') expect(drawer.attributes('style') ?? '').not.toContain('z-index: 2401') }) const mountWithTitle = (title?: string, headingLevel?: 1 | 2 | 3 | 4 | 5 | 6) => mount( defineComponent({ components: { VApp, FiltersSideBar: FilterSideBar }, data: () => ({ title, headingLevel }), template: ` `, }), { global: { stubs: { Teleport: true } } }, ) it('displays the title heading above the filters when title is provided', () => { const wrapper = mountWithTitle('Filtres du tableau') const heading = wrapper.find('.sy-filters-side-bar__title') expect(heading.exists()).toBe(true) expect(heading.text()).toBe('Filtres du tableau') // Niveau sémantique par défaut : h2 expect(heading.element.tagName).toBe('H2') }) it('does not render the title heading when no title is provided', () => { const wrapper = mountWithTitle() expect(wrapper.find('.sy-filters-side-bar__title').exists()).toBe(false) }) it('renders the title with the requested heading level', () => { const wrapper = mountWithTitle('Filtres', 3) expect(wrapper.find('.sy-filters-side-bar__title').element.tagName).toBe('H3') }) it('links the drawer accessible name to the title via aria-labelledby', () => { const wrapper = mountWithTitle('Filtres du tableau') const heading = wrapper.find('.sy-filters-side-bar__title') const drawer = wrapper.find('.v-navigation-drawer') const titleId = heading.attributes('id') expect(titleId).toBeTruthy() expect(drawer.attributes('aria-labelledby')).toBe(titleId) // aria-label retiré au profit de aria-labelledby quand un titre est fourni expect(drawer.attributes('aria-label')).toBeUndefined() }) it('falls back to the default aria-label when no title is provided', () => { const wrapper = mountWithTitle() const drawer = wrapper.find('.v-navigation-drawer') expect(drawer.attributes('aria-label')).toBe('Filtres') expect(drawer.attributes('aria-labelledby')).toBeUndefined() }) })