Files
parenthese/frontend/src/utils/sheetDrag.test.js
T
IliesAllaliandClaude Haiku 5.5 6faaefebee Fiche : fermer en glissant vers le bas sur téléphone
La feuille de la fiche (PersonCard) se ferme en glissant sa poignée ou son en-tête vers le bas, comme le menu de la barre. Le geste est factorisé : logique pure dans utils/sheetDrag.js, hook dans hooks/useSheetDrag.js, utilisé aussi par NavMenu (poignée, seuil de 70 px inchangé).

- Le geste part seulement de la poignée et de l'en-tête. Jamais d'un champ de saisie, d'un libellé ni pendant une sélection de texte. Le contenu des onglets défile comme avant.
- La feuille suit le doigt (transform), ne monte jamais au-delà de sa position de repos.
- Fermeture au-delà d'un tiers de la hauteur, ou sur un lâcher rapide vers le bas. Sinon retour court.
- La fermeture passe par le même chemin que la croix (enregistrement automatique compris). Si l'enregistrement est refusé, la fiche revient et affiche son erreur.
- Mode feuille seulement (PHONE_QUERY et largeur de la feuille CSS). Sur ordinateur, rien ne change.
- prefers-reduced-motion respecté.

Tests : logique du geste (zones, direction, seuil, vitesse) en vitest. Recette Playwright iPhone 13 émulé, gestes tactiles CDP : fermeture par la poignée, retour après un petit glisser, lâcher rapide, défilement du contenu sans fermer, pas de geste depuis un champ, enregistrement puis fermeture, ordinateur inchangé, croix.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-19 11:06:35 +02:00

98 lines
3.2 KiB
JavaScript

import { describe, expect, it } from 'vitest'
import {
NO_DRAG_SELECTOR,
canStartSheetDrag,
shouldCloseSheet,
sheetDragIntent,
velocityBetween,
} from './sheetDrag'
const ZONE = '.pc-grab, .pc-head, .pc-close'
// Cible factice : `closest` ne répond que pour la zone et les champs demandés
function target({ inZone = true, inField = false } = {}) {
return {
closest(selector) {
if (selector === ZONE) return inZone ? {} : null
if (selector === NO_DRAG_SELECTOR) return inField ? {} : null
return null
},
}
}
describe('canStartSheetDrag (zones autorisées)', () => {
it('part de la poignée, de l\'en-tête ou de la croix', () => {
expect(canStartSheetDrag(target(), ZONE)).toBe(true)
})
it('ne part pas du contenu hors zone (onglets, sections)', () => {
expect(canStartSheetDrag(target({ inZone: false }), ZONE)).toBe(false)
})
it('ne part jamais d\'un champ de saisie ou d\'un libellé de l\'en-tête', () => {
expect(canStartSheetDrag(target({ inField: true }), ZONE)).toBe(false)
})
it('ne part pas pendant une sélection de texte', () => {
expect(canStartSheetDrag(target(), ZONE, 'Marie')).toBe(false)
expect(canStartSheetDrag(target(), ZONE, '')).toBe(true)
})
it('refuse une cible sans closest (pas un élément DOM)', () => {
expect(canStartSheetDrag(null, ZONE)).toBe(false)
expect(canStartSheetDrag({}, ZONE)).toBe(false)
})
})
describe('sheetDragIntent (direction)', () => {
it('attend un mouvement suffisant avant de trancher', () => {
expect(sheetDragIntent(0, 0)).toBe('pending')
expect(sheetDragIntent(3, 5)).toBe('pending')
})
it('reconnaît un glisser vers le bas', () => {
expect(sheetDragIntent(0, 20)).toBe('down')
expect(sheetDragIntent(10, 20)).toBe('down')
})
it('rejette le haut et l\'horizontal', () => {
expect(sheetDragIntent(0, -20)).toBe('other')
expect(sheetDragIntent(30, 20)).toBe('other')
})
})
describe('shouldCloseSheet (seuil et vitesse)', () => {
it('ferme au-delà d\'un tiers de la hauteur', () => {
expect(shouldCloseSheet({ offset: 100, height: 300 })).toBe(true)
expect(shouldCloseSheet({ offset: 99, height: 300 })).toBe(false)
})
it('ferme sur un lâcher rapide vers le bas, même petit', () => {
expect(shouldCloseSheet({ offset: 30, height: 600, velocity: 0.7 })).toBe(true)
})
it('ne ferme pas sur un lâcher rapide mais trop court', () => {
expect(shouldCloseSheet({ offset: 10, height: 600, velocity: 2 })).toBe(false)
})
it('ne ferme pas sur une course lente et courte', () => {
expect(shouldCloseSheet({ offset: 60, height: 600, velocity: 0.1 })).toBe(false)
})
it('ne ferme jamais sans déplacement vers le bas', () => {
expect(shouldCloseSheet({ offset: 0, height: 300, velocity: 5 })).toBe(false)
})
})
describe('velocityBetween', () => {
it('donne la vitesse vers le bas en px/ms', () => {
expect(velocityBetween({ y: 100, t: 0 }, { y: 160, t: 60 })).toBe(1)
})
it('est nulle si les points manquent, ne se suivent pas ou sont trop espacés', () => {
expect(velocityBetween(null, { y: 1, t: 1 })).toBe(0)
expect(velocityBetween({ y: 0, t: 10 }, { y: 50, t: 10 })).toBe(0)
expect(velocityBetween({ y: 0, t: 0 }, { y: 50, t: 500 })).toBe(0)
})
})