teqo-backup/tests/unit/cardModels.unit.spec.ts

228 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it } from 'vitest'
import {
CARD_MODELS,
NAME_CARD_SLOT,
TEAM_CARD_LABEL,
TEAM_CARD_NAME_BANNER,
TEAM_CARD_NAME_SLOT,
getCardModel,
isCardModelId,
} from '@/lib/cardModels'
describe('card model catalog (S13/S15)', () => {
it('exposes the six shipped models with unique ids', () => {
expect(CARD_MODELS.map((model) => model.id)).toEqual([
'eu-sou-solla',
'perfil-quadrado',
'perfil-retangular',
'time-de-voce',
'time-do-estadual',
'minha-colinha',
])
expect(new Set(CARD_MODELS.map((model) => model.id)).size).toBe(CARD_MODELS.length)
})
it('pins the S38 catalogue copy: description and search nicknames per model', () => {
expect(
CARD_MODELS.map((model) => ({
id: model.id,
description: model.description,
aliases: [...model.aliases],
})),
).toEqual([
{ id: 'eu-sou-solla', description: 'Coloque seu nome no card oficial.', aliases: ['nome'] },
{
id: 'perfil-quadrado',
description: 'Foto de perfil quadrada.',
aliases: ['foto de perfil'],
},
{
id: 'perfil-retangular',
description: 'Foto vertical para stories.',
aliases: ['foto de perfil'],
},
{
id: 'time-de-voce',
description: 'Entre para o time com sua foto.',
aliases: ['santinho'],
},
{
id: 'time-do-estadual',
description: 'Escolha o estadual e personalize.',
aliases: ['estadual'],
},
{
id: 'minha-colinha',
description: 'Monte sua cola de votação.',
aliases: ['santinho', 'colinha'],
},
])
})
it('serves every master from the local /cards asset folder', () => {
for (const model of CARD_MODELS) {
expect(model.assetSrc.startsWith('/cards/')).toBe(true)
}
})
it('pins the real output dimensions of the masters (not the 1080 in the file names)', () => {
expect(getCardModel('eu-sou-solla')).toMatchObject({ width: 1080, height: 1440, kind: 'name' })
expect(getCardModel('perfil-quadrado')).toMatchObject({
width: 1000,
height: 1000,
kind: 'photo',
})
expect(getCardModel('perfil-retangular')).toMatchObject({
width: 1000,
height: 1440,
kind: 'photo',
})
})
it('keeps the photo window measured from the transparent masters', () => {
expect(getCardModel('perfil-quadrado')?.photoWindow).toEqual({
x: 0,
y: 0,
width: 1000,
height: 740,
})
expect(getCardModel('perfil-retangular')?.photoWindow).toEqual({
x: 0,
y: 0,
width: 1000,
height: 1044,
})
expect(getCardModel('eu-sou-solla')?.photoWindow).toBeUndefined()
})
it('guards the name slot geometry measured from the filled master', () => {
expect(NAME_CARD_SLOT).toMatchObject({
align: 'left',
leftX: 213,
capTop: 430,
capHeight: 106,
maxInkWidth: 714,
})
})
it('pins the team model, its overlay assets and the measured photo window', () => {
expect(getCardModel('time-de-voce')).toMatchObject({
kind: 'team',
label: 'Time de você',
assetSrc: '/cards/team-card-base.png',
overlaySrc: '/cards/team-card-front.png',
previewSrc: '/cards/team-card-example.jpg',
width: 1080,
height: 1440,
photoWindow: { x: 286, y: 439, width: 592, height: 577 },
})
expect(getCardModel('eu-sou-solla')?.overlaySrc).toBeUndefined()
})
it('pins the S30 state-deputy model: same team geometry, picker flag and the approved example', () => {
expect(getCardModel('time-do-estadual')).toMatchObject({
kind: 'team',
label: 'Time do estadual',
assetSrc: '/cards/estaduais/julio-fotos.webp',
overlaySrc: '/cards/estaduais/julio-base.webp',
previewSrc: '/cards/modelo-time-de-voce-com-estadual.jpeg',
stateDeputyPicker: true,
width: 1080,
height: 1440,
photoWindow: { x: 286, y: 439, width: 592, height: 577 },
})
expect(getCardModel('time-de-voce')?.badge).toBeUndefined()
expect(getCardModel('time-do-estadual')?.stateDeputyPicker).toBe(true)
expect(getCardModel('time-de-voce')?.stateDeputyPicker).toBeUndefined()
})
it('marks only the two team models as free position (S33)', () => {
expect(getCardModel('time-de-voce')?.photoPosition).toBe('free')
expect(getCardModel('time-do-estadual')?.photoPosition).toBe('free')
// S13/S14 and the colinha keep the bounded default (omitted).
expect(getCardModel('eu-sou-solla')?.photoPosition).toBeUndefined()
expect(getCardModel('perfil-quadrado')?.photoPosition).toBeUndefined()
expect(getCardModel('perfil-retangular')?.photoPosition).toBeUndefined()
expect(getCardModel('minha-colinha')?.photoPosition).toBeUndefined()
})
it('pins the S34 colinha model: the approved art as base, picker flag and no composed assets', () => {
expect(getCardModel('minha-colinha')).toMatchObject({
kind: 'colinha',
label: 'Minha colinha',
assetSrc: '/cards/modelo-colinha.jpeg',
stateDeputyPicker: true,
badge: 'NOVO',
width: 1080,
height: 1920,
})
expect(getCardModel('minha-colinha')?.photoWindow).toBeUndefined()
expect(getCardModel('minha-colinha')?.overlaySrc).toBeUndefined()
expect(getCardModel('minha-colinha')?.previewSrc).toBeUndefined()
// The `NOVO` badge marks the newest model only (design gate scene 1).
expect(getCardModel('time-do-estadual')?.badge).toBeUndefined()
})
it('guards the team banners and name slot measured from the example card', () => {
expect(TEAM_CARD_LABEL).toMatchObject({
text: 'TIME DE',
centerX: 545,
centerY: 162,
width: 693,
maxWidth: 693,
height: 118,
rotationDeg: -3.5,
background: '#e50e2f',
fill: '#ffec01',
capHeight: 93,
})
expect(TEAM_CARD_NAME_BANNER).toMatchObject({
centerX: 545,
centerY: 313,
width: 509,
maxWidth: 1000,
height: 176,
rotationDeg: -4.1,
background: '#0061a5',
fill: '#ffffff',
})
expect(TEAM_CARD_NAME_SLOT).toMatchObject({
align: 'center',
banner: TEAM_CARD_NAME_BANNER,
capHeight: 130,
minCapHeight: 40,
maxInkWidth: 961,
maxLines: 1,
fill: '#ffffff',
})
})
it('keeps the dynamic name banner ceiling inside the measured card bounds (S16)', () => {
const model = getCardModel('time-de-voce')!
const halfWidth = TEAM_CARD_NAME_BANNER.maxWidth / 2
const halfHeight = TEAM_CARD_NAME_BANNER.height / 2
const tilt = (Math.abs(TEAM_CARD_NAME_BANNER.rotationDeg) * Math.PI) / 180
const halfSpanX = halfWidth * Math.cos(tilt) + halfHeight * Math.sin(tilt)
const lowestCorner =
TEAM_CARD_NAME_BANNER.centerY + halfHeight * Math.cos(tilt) + halfWidth * Math.sin(tilt)
// The ceiling preserves the S15 ink padding: 1000 − 961 = 509 − 470 = 39.
expect(TEAM_CARD_NAME_BANNER.maxWidth - TEAM_CARD_NAME_SLOT.maxInkWidth).toBe(39)
expect(TEAM_CARD_NAME_BANNER.centerX - halfSpanX).toBeGreaterThanOrEqual(0)
expect(TEAM_CARD_NAME_BANNER.centerX + halfSpanX).toBeLessThanOrEqual(model.width)
expect(lowestCorner).toBeLessThan(model.photoWindow!.y)
})
it('sanitizes query-param values through isCardModelId', () => {
expect(isCardModelId('eu-sou-solla')).toBe(true)
expect(isCardModelId('perfil-quadrado')).toBe(true)
expect(isCardModelId('perfil-retangular')).toBe(true)
expect(isCardModelId('time-de-voce')).toBe(true)
expect(isCardModelId('time-do-estadual')).toBe(true)
expect(isCardModelId('minha-colinha')).toBe(true)
expect(isCardModelId('modelo-inventado')).toBe(false)
expect(isCardModelId(['perfil-quadrado'])).toBe(false)
expect(isCardModelId(undefined)).toBe(false)
})
})