import { describe, it, expect } from 'vitest' import { ICON_SIZE, EXPANDED_W, EXPANDED_H, BUY_BTN_H, EXPANDED_H_WITH_BTN, expandedCardOffset } from '../../src/game/objects/tokenLayout' describe('game/objects/tokenLayout', () => { describe('constantes', () => { it('EXPANDED_H_WITH_BTN est plus grand que EXPANDED_H (place pour le bouton)', () => { expect(EXPANDED_H_WITH_BTN).toBeGreaterThan(EXPANDED_H) expect(EXPANDED_H_WITH_BTN).toBe(EXPANDED_H + BUY_BTN_H + 10) }) }) describe('expandedCardOffset', () => { const cardH = EXPANDED_H_WITH_BTN it('hAnchor="left" (défaut) : la carte s\'étend vers la DROITE (dx positif)', () => { const { dx } = expandedCardOffset(cardH, 1, 'left', 'bottom') expect(dx).toBeGreaterThan(0) }) it('hAnchor="right" : la carte s\'étend vers la GAUCHE (dx négatif)', () => { const { dx } = expandedCardOffset(cardH, 1, 'right', 'bottom') expect(dx).toBeLessThan(0) }) it('vAnchor="bottom" (défaut) : la carte s\'étend vers le BAS (dy positif)', () => { const { dy } = expandedCardOffset(cardH, 1, 'left', 'bottom') expect(dy).toBeGreaterThan(0) }) it('vAnchor="top" : la carte s\'étend vers le HAUT (dy négatif) — régression corrigée', () => { // Bug corrigé : la main de cartes Événement (positionnée en bas du // canvas) utilisait implicitement vAnchor="bottom" (comportement par // défaut, pensé pour les jetons en haut du canvas comme la boutique // d'objets), faisant déborder la carte détaillée sous le bas du // canvas — invisible et injoignable au clic (bouton JOUER inatteignable). const { dy } = expandedCardOffset(cardH, 1, 'left', 'top') expect(dy).toBeLessThan(0) }) it('dx et dy ont la même magnitude quel que soit le signe de l\'ancrage (symétrie)', () => { const left = expandedCardOffset(cardH, 1, 'left', 'bottom') const right = expandedCardOffset(cardH, 1, 'right', 'bottom') expect(Math.abs(left.dx)).toBeCloseTo(Math.abs(right.dx), 10) const bottom = expandedCardOffset(cardH, 1, 'left', 'bottom') const top = expandedCardOffset(cardH, 1, 'left', 'top') expect(Math.abs(bottom.dy)).toBeCloseTo(Math.abs(top.dy), 10) }) it('un displayScale réduit rapproche le décalage de 0 (jeton plus petit, moins de compensation nécessaire)', () => { const fullScale = expandedCardOffset(cardH, 1, 'left', 'bottom') const halfScale = expandedCardOffset(cardH, 0.5, 'left', 'bottom') expect(halfScale.dx).toBeGreaterThan(fullScale.dx) expect(halfScale.dy).toBeGreaterThan(fullScale.dy) }) it('avec displayScale=1, le coin du jeton (ICON_SIZE/2) s\'aligne exactement sur le coin de la carte', () => { // Vérifie la propriété fondamentale de ce calcul : le bord du jeton // compact doit coïncider avec le bord correspondant de la carte // détaillée, sans espace ni chevauchement. const { dx, dy } = expandedCardOffset(cardH, 1, 'left', 'bottom') const iconHalf = ICON_SIZE / 2 // hAnchor='left' : le bord gauche du jeton (-iconHalf, relatif au // centre du jeton) coïncide avec le bord gauche de la carte // (dx - EXPANDED_W/2, puisque le container carte est positionné à (dx, dy)). expect(-iconHalf).toBeCloseTo(dx - EXPANDED_W / 2, 10) // vAnchor='bottom' : le bord HAUT du jeton (-iconHalf) coïncide avec // le bord HAUT de la carte (dy - cardH/2) — la carte s'étend vers le // BAS à partir du haut du jeton. expect(-iconHalf).toBeCloseTo(dy - cardH / 2, 10) }) }) describe('non-régression : carte de la main d\'événements reste dans le canvas (vAnchor="top")', () => { it('pour un jeton positionné en bas du canvas, la carte détaillée (vAnchor="top") ne déborde jamais sous le canvas', () => { const canvasH = 1080 const marginBottom = 60 const tokenY = canvasH - marginBottom - ICON_SIZE / 2 // même formule que eventHandGeometry const { dy } = expandedCardOffset(EXPANDED_H_WITH_BTN, 1, 'right', 'top') const cardCenterY = tokenY + dy const cardBottom = cardCenterY + EXPANDED_H_WITH_BTN / 2 const cardTop = cardCenterY - EXPANDED_H_WITH_BTN / 2 expect(cardBottom).toBeLessThanOrEqual(canvasH) expect(cardTop).toBeGreaterThanOrEqual(0) }) it('avec l\'ancien comportement implicite (vAnchor="bottom"), la carte aurait débordé — confirme la nécessité du fix', () => { const canvasH = 1080 const marginBottom = 60 const tokenY = canvasH - marginBottom - ICON_SIZE / 2 const { dy } = expandedCardOffset(EXPANDED_H_WITH_BTN, 1, 'right', 'bottom') const cardCenterY = tokenY + dy const cardBottom = cardCenterY + EXPANDED_H_WITH_BTN / 2 expect(cardBottom).toBeGreaterThan(canvasH) // déborde bien, confirmant le bug d'origine }) it('sur une petite résolution (720p), la carte (vAnchor="top") reste aussi dans les limites', () => { const canvasH = 720 const marginBottom = 60 const tokenY = canvasH - marginBottom - ICON_SIZE / 2 const { dy } = expandedCardOffset(EXPANDED_H_WITH_BTN, 1, 'right', 'top') const cardCenterY = tokenY + dy const cardBottom = cardCenterY + EXPANDED_H_WITH_BTN / 2 const cardTop = cardCenterY - EXPANDED_H_WITH_BTN / 2 expect(cardBottom).toBeLessThanOrEqual(canvasH) expect(cardTop).toBeGreaterThanOrEqual(0) }) }) })