ItemBoardToken._showExpanded() ancrait TOUJOURS la carte détaillée vers le
BAS du jeton compact, quel que soit son emplacement sur le plateau —
adapté aux boutiques d'objets/bâtiments (positionnées en haut du canvas),
mais pas à la main de cartes Événement (positionnée en BAS du canvas,
voir eventHandLayout.js) : la carte (jusqu'à 266px de haut avec le bouton
JOUER) débordait alors largement sous le bas de l'écran, la rendant
invisible et son bouton totalement injoignable au clic.
- Ajout du paramètre vAnchor ('bottom' par défaut, 'top' pour les jetons
proches du bas de l'écran) sur ItemBoardToken, symétrique au hAnchor déjà
existant pour l'axe horizontal. La main d'événements passe désormais
vAnchor='top' pour que la carte s'étende vers le HAUT au lieu du bas.
- Extraction du calcul de décalage (expandedCardOffset) et des constantes
de dimensionnement (EXPANDED_W/H, BUY_BTN_H) dans tokenLayout.js (module
pur, sans dépendance Phaser) pour pouvoir le couvrir par de vrais tests
unitaires Vitest.
- 11 nouveaux tests unitaires (tokenLayout.test.js) : orientation dx/dy
selon hAnchor/vAnchor, alignement exact du bord du jeton avec le bord de
la carte, et surtout la régression elle-même — un test vérifie que
vAnchor='top' maintient la carte dans les limites du canvas pour un
jeton en bas d'écran (1080p et 720p), et qu'un test symétrique confirme
que l'ancien comportement implicite (vAnchor='bottom') débordait bien
sous le canvas dans ce même scénario.
- 54 tests unitaires client au vert au total (43 précédents + 11 nouveaux),
vérification privilégiée par rapport à une nouvelle suite E2E (lente vu
la pioche aléatoire à stabiliser dans ce projet).
123 lines
5.4 KiB
JavaScript
123 lines
5.4 KiB
JavaScript
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)
|
|
})
|
|
})
|
|
})
|