/* 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()
})
})