mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
228 lines
7.7 KiB
TypeScript
228 lines
7.7 KiB
TypeScript
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)
|
||
})
|
||
})
|