feat: Ajout des composants modal, accordeon, datetime avec selecteur d'heure à la molette (#56)
Release / release (push) Successful in 2m38s

| 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é

---------

Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-authored-by: matthieu <matthieu@yuno.malio.fr>
Reviewed-on: #56
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #56.
This commit is contained in:
2026-05-27 12:11:51 +00:00
committed by Autin
parent 7d7b2fb720
commit acd531f69e
36 changed files with 6581 additions and 30 deletions
@@ -0,0 +1,120 @@
import {describe, expect, it, vi} from 'vitest'
import {defineComponent, nextTick, ref} from 'vue'
import {mount} from '@vue/test-utils'
import {
CENTER_OFFSET,
VISIBLE_ROWS,
loopCorrection,
scrollTopForValueIndex,
useInfiniteWheel,
valueIndexFromScroll,
} from './useInfiniteWheel'
const H = 40 // itemHeight
const LEN = 24 // ex. heures
describe('useInfiniteWheel — math pure', () => {
it('expose 5 lignes visibles et un offset central de 2', () => {
expect(VISIBLE_ROWS).toBe(5)
expect(CENTER_OFFSET).toBe(2)
})
it('scrollTopForValueIndex et valueIndexFromScroll font un aller-retour', () => {
for (const index of [0, 1, 9, 23]) {
const top = scrollTopForValueIndex(index, H, LEN)
expect(valueIndexFromScroll(top, H, LEN)).toBe(index)
}
})
it('valueIndexFromScroll boucle en modulo', () => {
const top = scrollTopForValueIndex(0, H, LEN)
expect(valueIndexFromScroll(top + LEN * H, H, LEN)).toBe(0)
})
it('loopCorrection laisse le scroll de la copie du milieu inchangé', () => {
const top = scrollTopForValueIndex(12, H, LEN)
expect(loopCorrection(top, H, LEN)).toBe(top)
})
it('loopCorrection ramène vers le milieu quand on dérive vers le haut', () => {
const drifted = scrollTopForValueIndex(0, H, LEN) - LEN * H
expect(loopCorrection(drifted, H, LEN)).toBe(drifted + LEN * H)
})
it('loopCorrection ramène vers le milieu quand on dérive vers le bas', () => {
const drifted = scrollTopForValueIndex(0, H, LEN) + LEN * H
expect(loopCorrection(drifted, H, LEN)).toBe(drifted - LEN * H)
})
})
function mountWheelHarness(initialIndex: number, onChange: (i: number) => void) {
let api!: ReturnType<typeof useInfiniteWheel>
const Harness = defineComponent({
setup() {
const container = ref<HTMLElement | null>(null)
api = useInfiniteWheel(container, {
length: 24,
itemHeight: 40,
initialIndex: () => initialIndex,
onChange,
})
return {container}
},
template: '<div ref="container" style="height:200px;overflow:auto"><div style="height:2880px" /></div>',
})
const wrapper = mount(Harness, {attachTo: document.body})
return {wrapper, api: () => api}
}
describe('useInfiniteWheel — composable', () => {
it('step(+1) émet l\'index suivant', async () => {
const changes: number[] = []
const {api} = mountWheelHarness(9, (i) => changes.push(i))
await nextTick()
api().step(1)
expect(changes.at(-1)).toBe(10)
})
it('step boucle de 23 à 0', async () => {
const changes: number[] = []
const {api} = mountWheelHarness(23, (i) => changes.push(i))
await nextTick()
api().step(1)
expect(changes.at(-1)).toBe(0)
})
it('onKeydown ArrowUp décrémente (avec wrap)', async () => {
const changes: number[] = []
const {api} = mountWheelHarness(0, (i) => changes.push(i))
await nextTick()
api().onKeydown(new KeyboardEvent('keydown', {key: 'ArrowUp'}))
expect(changes.at(-1)).toBe(23)
})
// Anti-boucle navigateur : un scroll programmatique déclenche une rafale d'évènements
// scroll (animation/snap). Ils ne doivent PAS être pris pour du scroll utilisateur,
// sinon settle() ré-émet en boucle et corrompt le patch DOM de Vue.
it('n\'émet pas en double quand un scroll programmatique déclenche une rafale de scroll', async () => {
vi.useFakeTimers()
try {
const changes: number[] = []
const {wrapper, api} = mountWheelHarness(9, (i) => changes.push(i))
await nextTick()
const el = wrapper.element as HTMLElement
changes.length = 0
api().scrollToIndex(12)
el.dispatchEvent(new Event('scroll'))
el.dispatchEvent(new Event('scroll'))
el.dispatchEvent(new Event('scroll'))
vi.advanceTimersByTime(300)
expect(changes).toEqual([12])
}
finally {
vi.useRealTimers()
}
})
})