Files
malio-layer-ui/app/components/malio/sidebar/Sidebar.test.ts
tristan 3deba3f369 [#MUI-20] Développer le composant Menu (#17)
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [ ] Pas de régression
- [ ] TU/TI/TF rédigée
- [ ] TU/TI/TF OK
- [ ] CHANGELOG modifié

Reviewed-on: #17
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-03-23 16:36:16 +00:00

206 lines
6.4 KiB
TypeScript

import {describe, expect, it} from 'vitest'
import {mount} from '@vue/test-utils'
import type {DefineComponent} from 'vue'
import {Icon as IconifyIcon} from '@iconify/vue'
import Sidebar from './Sidebar.vue'
type SidebarItem = {
label: string
to: string
}
type SidebarSection = {
label?: string
icon?: string
items: SidebarItem[]
}
type SidebarProps = {
sections: SidebarSection[]
modelValue?: boolean
id?: string
sidebarClass?: string
toggleClass?: string
}
const SidebarForTest = Sidebar as DefineComponent<SidebarProps>
const sections: SidebarSection[] = [
{
label: 'LOGISTIQUE / TRANSPORT',
icon: 'mdi:truck-delivery',
items: [
{label: 'Réception / Expédition', to: '/reception'},
{label: 'Validation expédition', to: '/validation'},
],
},
{
label: 'COMMERCIAL',
icon: 'mdi:handshake',
items: [
{label: 'Répertoire Fournisseurs', to: '/fournisseurs'},
],
},
]
const stubs = {
NuxtLink: {
template: '<a :href="to" v-bind="$attrs"><slot /></a>',
props: ['to'],
},
}
function mountComponent(props: SidebarProps, slots?: Record<string, string>) {
return mount(SidebarForTest, {
props,
slots,
global: {stubs},
})
}
describe('MalioSidebar', () => {
it('renders expanded by default', () => {
const wrapper = mountComponent({sections})
const aside = wrapper.find('aside')
expect(aside.classes()).toContain('w-[280px]')
})
it('renders section labels with icons when expanded', () => {
const wrapper = mountComponent({sections})
const sectionHeaders = wrapper.findAll('nav > div > div')
expect(sectionHeaders).toHaveLength(2)
expect(sectionHeaders[0].text()).toContain('LOGISTIQUE / TRANSPORT')
expect(sectionHeaders[1].text()).toContain('COMMERCIAL')
})
it('renders all menu items with icons and labels', () => {
const wrapper = mountComponent({sections})
const links = wrapper.findAll('a')
expect(links).toHaveLength(3)
expect(links[0].text()).toContain('Réception / Expédition')
expect(links[1].text()).toContain('Validation expédition')
expect(links[2].text()).toContain('Répertoire Fournisseurs')
})
it('renders NuxtLink with correct to prop', () => {
const wrapper = mountComponent({sections})
const links = wrapper.findAll('a')
expect(links[0].attributes('href')).toBe('/reception')
expect(links[2].attributes('href')).toBe('/fournisseurs')
})
it('renders section icons via IconifyIcon', () => {
const wrapper = mountComponent({sections})
const icons = wrapper.findAllComponents(IconifyIcon)
// 2 section icons + 1 toggle chevron = 3
expect(icons).toHaveLength(3)
expect(icons[0].props('icon')).toBe('mdi:truck-delivery')
expect(icons[1].props('icon')).toBe('mdi:handshake')
})
it('toggle button shows chevron-left when expanded', () => {
const wrapper = mountComponent({sections})
const toggleIcon = wrapper.findAllComponents(IconifyIcon).at(-1)!
expect(toggleIcon.props('icon')).toBe('mdi:chevron-left')
})
it('collapses on toggle click in uncontrolled mode', async () => {
const wrapper = mountComponent({sections})
const toggleBtn = wrapper.find('button')
await toggleBtn.trigger('click')
const aside = wrapper.find('aside')
expect(aside.classes()).toContain('w-[72px]')
})
it('hides section label text when collapsed but keeps section icon', async () => {
const wrapper = mountComponent({sections})
await wrapper.find('button').trigger('click')
const sectionHeaders = wrapper.findAll('nav > div > div')
expect(sectionHeaders).toHaveLength(2)
// Label text spans are hidden
sectionHeaders.forEach((header) => {
expect(header.findAll('span').filter(s => s.classes().includes('text-[11px]'))).toHaveLength(0)
})
})
it('hides item text when collapsed', async () => {
const wrapper = mountComponent({sections})
await wrapper.find('button').trigger('click')
const itemSpans = wrapper.findAll('a span')
expect(itemSpans).toHaveLength(0)
})
it('toggle button shows chevron-right when collapsed', async () => {
const wrapper = mountComponent({sections})
await wrapper.find('button').trigger('click')
const toggleIcon = wrapper.findAllComponents(IconifyIcon).at(-1)!
expect(toggleIcon.props('icon')).toBe('mdi:chevron-right')
})
it('emits update:modelValue on toggle click', async () => {
const wrapper = mountComponent({sections, modelValue: false})
await wrapper.find('button').trigger('click')
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([true])
})
it('respects modelValue in controlled mode', () => {
const wrapper = mountComponent({sections, modelValue: true})
const aside = wrapper.find('aside')
expect(aside.classes()).toContain('w-[72px]')
})
it('renders logo slot when expanded', () => {
const wrapper = mountComponent({sections}, {
logo: '<img alt="Malio" src="/logo.svg" />',
})
expect(wrapper.find('img[alt="Malio"]').exists()).toBe(true)
})
it('renders logo-collapsed slot when collapsed', async () => {
const wrapper = mountComponent({sections}, {
'logo-collapsed': '<img alt="M" src="/logo-m.svg" />',
})
await wrapper.find('button').trigger('click')
expect(wrapper.find('img[alt="M"]').exists()).toBe(true)
})
it('uses custom id when provided', () => {
const wrapper = mountComponent({sections, id: 'my-sidebar'})
expect(wrapper.find('aside').attributes('id')).toBe('my-sidebar')
})
it('toggle button has correct aria-label', async () => {
const wrapper = mountComponent({sections})
const btn = wrapper.find('button')
expect(btn.attributes('aria-label')).toBe('Plier le menu')
await btn.trigger('click')
expect(btn.attributes('aria-label')).toBe('Déplier le menu')
})
it('section without label does not render a section header', () => {
const noLabelSections: SidebarSection[] = [
{items: [{label: 'Item', to: '/'}]},
]
const wrapper = mountComponent({sections: noLabelSections})
expect(wrapper.findAll('nav > div > div')).toHaveLength(0)
})
it('renders section icon in collapsed mode', async () => {
const wrapper = mountComponent({sections})
await wrapper.find('button').trigger('click')
const icons = wrapper.findAllComponents(IconifyIcon)
// 2 section icons + 1 toggle = 3
expect(icons[0].props('icon')).toBe('mdi:truck-delivery')
expect(icons[1].props('icon')).toBe('mdi:handshake')
})
})