Files
malio-layer-ui/app/components/malio/site/SiteSelector.test.ts
tristan e77bf49146 [#MUI-27] Création d'un composant sélection de site (#29)
Composant MalioSiteSelector : bande horizontale pour choisir un site
(usine ou lieu) parmi une liste. Tuiles flex proportionnelles, couleur
du site sélectionné partagée par toutes les tuiles (opacité 1 / 0.4).
Expose update:modelValue (id) + change (objet site complet) pour
faciliter les appels API côté consommateur.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

| 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: #29
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-04-17 12:28:44 +00:00

155 lines
5.4 KiB
TypeScript

import {describe, expect, it} from 'vitest'
import {mount} from '@vue/test-utils'
import type {DefineComponent} from 'vue'
import SiteSelector from './SiteSelector.vue'
type Site = {
id: string
name: string
color: string
}
type SiteSelectorProps = {
sites: Site[]
modelValue?: string
id?: string
groupClass?: string
tileClass?: string
labelClass?: string
}
const SiteSelectorForTest = SiteSelector as DefineComponent<SiteSelectorProps>
const sites: Site[] = [
{id: 'chatellerault', name: 'Châtellerault', color: '#2563eb'},
{id: 'saint-jean', name: 'Saint-Jean', color: '#16a34a'},
{id: 'pommevic', name: 'Pommevic', color: '#dc2626'},
]
function mountComponent(props: SiteSelectorProps) {
return mount(SiteSelectorForTest, {props})
}
describe('MalioSiteSelector', () => {
it('renders one tile per site with the site name', () => {
const wrapper = mountComponent({sites})
const tiles = wrapper.findAll('[role="radio"]')
expect(tiles).toHaveLength(3)
expect(tiles[0]!.text()).toBe('Châtellerault')
expect(tiles[1]!.text()).toBe('Saint-Jean')
expect(tiles[2]!.text()).toBe('Pommevic')
})
it('has role="radiogroup" on the wrapper', () => {
const wrapper = mountComponent({sites})
expect(wrapper.find('[role="radiogroup"]').exists()).toBe(true)
})
it('selects the first site by default in uncontrolled mode', () => {
const wrapper = mountComponent({sites})
const tiles = wrapper.findAll('[role="radio"]')
expect(tiles[0]!.attributes('aria-checked')).toBe('true')
expect(tiles[1]!.attributes('aria-checked')).toBe('false')
expect(tiles[2]!.attributes('aria-checked')).toBe('false')
})
it('paints all tiles with the selected site color', () => {
const wrapper = mountComponent({sites, modelValue: 'saint-jean'})
const tiles = wrapper.findAll('[role="radio"]')
for (const tile of tiles) {
expect(tile.attributes('style')).toContain('background-color: rgb(22, 163, 74)')
}
})
it('applies opacity 1 on the selected tile and 0.4 on the others', () => {
const wrapper = mountComponent({sites, modelValue: 'chatellerault'})
const tiles = wrapper.findAll('[role="radio"]')
expect(tiles[0]!.attributes('style')).toContain('opacity: 1')
expect(tiles[1]!.attributes('style')).toContain('opacity: 0.4')
expect(tiles[2]!.attributes('style')).toContain('opacity: 0.4')
})
it('updates the shared color when the selection changes', async () => {
const wrapper = mountComponent({sites})
let tiles = wrapper.findAll('[role="radio"]')
expect(tiles[0]!.attributes('style')).toContain('background-color: rgb(37, 99, 235)')
await tiles[2]!.trigger('click')
tiles = wrapper.findAll('[role="radio"]')
for (const tile of tiles) {
expect(tile.attributes('style')).toContain('background-color: rgb(220, 38, 38)')
}
})
it('emits update:modelValue with the clicked site id', async () => {
const wrapper = mountComponent({sites, modelValue: 'chatellerault'})
const tiles = wrapper.findAll('[role="radio"]')
await tiles[1]!.trigger('click')
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['saint-jean'])
})
it('emits change with the full selected site object', async () => {
const wrapper = mountComponent({sites, modelValue: 'chatellerault'})
const tiles = wrapper.findAll('[role="radio"]')
await tiles[2]!.trigger('click')
expect(wrapper.emitted('change')?.[0]).toEqual([
{id: 'pommevic', name: 'Pommevic', color: '#dc2626'},
])
})
it('respects modelValue in controlled mode', () => {
const wrapper = mountComponent({sites, modelValue: 'pommevic'})
const tiles = wrapper.findAll('[role="radio"]')
expect(tiles[0]!.attributes('aria-checked')).toBe('false')
expect(tiles[1]!.attributes('aria-checked')).toBe('false')
expect(tiles[2]!.attributes('aria-checked')).toBe('true')
})
it('switches selection on click in uncontrolled mode', async () => {
const wrapper = mountComponent({sites})
const tiles = wrapper.findAll('[role="radio"]')
await tiles[1]!.trigger('click')
expect(tiles[0]!.attributes('aria-checked')).toBe('false')
expect(tiles[1]!.attributes('aria-checked')).toBe('true')
expect(tiles[2]!.attributes('aria-checked')).toBe('false')
})
it('sets roving tabindex (active = 0, others = -1)', () => {
const wrapper = mountComponent({sites, modelValue: 'saint-jean'})
const tiles = wrapper.findAll('[role="radio"]')
expect(tiles[0]!.attributes('tabindex')).toBe('-1')
expect(tiles[1]!.attributes('tabindex')).toBe('0')
expect(tiles[2]!.attributes('tabindex')).toBe('-1')
})
it('merges groupClass, tileClass and labelClass via twMerge', () => {
const wrapper = mountComponent({
sites,
groupClass: 'rounded-none bg-black',
tileClass: 'py-10',
labelClass: 'text-xs',
})
const group = wrapper.find('[role="radiogroup"]')
expect(group.classes()).toContain('rounded-none')
expect(group.classes()).toContain('bg-black')
const tile = wrapper.find('[role="radio"]')
expect(tile.classes()).toContain('py-10')
expect(tile.classes()).not.toContain('py-4')
const label = tile.find('span')
expect(label.classes()).toContain('text-xs')
})
it('uses a custom id when provided', () => {
const wrapper = mountComponent({sites, id: 'my-selector'})
expect(wrapper.find('[role="radiogroup"]').attributes('id')).toBe('my-selector')
})
})