Files
inq-roi-simulador-web/tests/lib/export/pdf-sections-shared.test.ts
Marcos Benítez 63c4fbac99 "chore(claude-code): configurar permisos compartidos del proyecto
- settings.json: deny patterns críticos (rm -rf root, force push, etc.)
- settings.local.json en gitignore — cada dev configura sus allows

Refs: methodology/PATTERNS.md B.4 (reglas no negociables en repo)
2026-05-17 07:49:00 -03:00

149 lines
6.2 KiB
TypeScript

/**
* Tests del módulo compartido de header/footer PDF — Etapa 4 Sprint 1.5.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { PDF_COLORS, drawSharedHeader, drawSharedFooter, applyFootersToAllPages } from '@/lib/export/pdf-sections-shared'
// ─── Mock DocLike ─────────────────────────────────────────────────────────────
function makeMockDoc(pageW = 210, pageH = 297, totalPages = 2) {
return {
setFont: vi.fn(),
setFontSize: vi.fn(),
setTextColor: vi.fn(),
setFillColor: vi.fn(),
setDrawColor: vi.fn(),
setLineWidth: vi.fn(),
text: vi.fn(),
rect: vi.fn(),
roundedRect: vi.fn(),
line: vi.fn(),
addImage: vi.fn(),
addPage: vi.fn(),
setPage: vi.fn(),
splitTextToSize: vi.fn().mockImplementation((t: string) => [t]),
internal: {
getNumberOfPages: vi.fn().mockReturnValue(totalPages),
pageSize: {
getWidth: vi.fn().mockReturnValue(pageW),
getHeight: vi.fn().mockReturnValue(pageH),
},
},
}
}
const BASE_OPTS = { processName: 'Proceso de prueba', simulatedAt: new Date('2026-05-17T10:00:00Z').getTime() }
// ─── PDF_COLORS ───────────────────────────────────────────────────────────────
describe('PDF_COLORS', () => {
it('expone inqOrange como array RGB válido [245, 152, 69]', () => {
expect(PDF_COLORS.inqOrange).toEqual([245, 152, 69])
})
it('NO incluye colores de CONCILIA (#572FA2 → [87, 47, 162])', () => {
const all = JSON.stringify(PDF_COLORS)
expect(all).not.toContain('87, 47, 162') // #572FA2 morado CONCILIA
expect(all).not.toContain('203, 24, 137') // #CB1889 magenta CONCILIA
})
it('tiene al menos 8 colores definidos', () => {
expect(Object.keys(PDF_COLORS).length).toBeGreaterThanOrEqual(8)
})
})
// ─── drawSharedHeader ─────────────────────────────────────────────────────────
describe('drawSharedHeader', () => {
let doc: ReturnType<typeof makeMockDoc>
beforeEach(() => { doc = makeMockDoc() })
it('retorna un número mayor que el margen (20mm) — espacio del header', () => {
const y = drawSharedHeader(doc, { ...BASE_OPTS, pageNumber: 1 })
expect(y).toBeGreaterThan(20)
})
it('dibuja border-bottom más grueso en página 1 que en páginas siguientes', () => {
drawSharedHeader(doc, { ...BASE_OPTS, pageNumber: 1 })
const page1LineWidths = doc.setLineWidth.mock.calls.map((c: number[]) => c[0])
doc.setLineWidth.mockClear()
drawSharedHeader(doc, { ...BASE_OPTS, pageNumber: 2 })
const page2LineWidths = doc.setLineWidth.mock.calls.map((c: number[]) => c[0])
// El grosor máximo de página 1 debe ser mayor que el de página 2
expect(Math.max(...page1LineWidths)).toBeGreaterThan(Math.max(...page2LineWidths))
})
it('dibuja el texto "InQ ROI" en la primera llamada a text()', () => {
drawSharedHeader(doc, { ...BASE_OPTS, pageNumber: 1 })
const firstTextCall = doc.text.mock.calls[0]
expect(firstTextCall[0]).toBe('InQ ROI')
})
it('usa inqOrange para el texto del logo', () => {
drawSharedHeader(doc, { ...BASE_OPTS, pageNumber: 1 })
// La primera llamada a setTextColor debe ser el naranja InQ
const firstColor = doc.setTextColor.mock.calls[0]
expect(firstColor).toEqual(PDF_COLORS.inqOrange)
})
it('usa doc.internal.pageSize.getWidth() — compatible con landscape', () => {
const landscapeDoc = makeMockDoc(297, 210)
const y = drawSharedHeader(landscapeDoc, { ...BASE_OPTS, pageNumber: 1 })
expect(landscapeDoc.internal.pageSize.getWidth).toHaveBeenCalled()
expect(y).toBeGreaterThan(20)
})
})
// ─── drawSharedFooter ─────────────────────────────────────────────────────────
describe('drawSharedFooter', () => {
let doc: ReturnType<typeof makeMockDoc>
beforeEach(() => { doc = makeMockDoc() })
it('incluye "InQ ROI" en el texto del footer', () => {
drawSharedFooter(doc, { ...BASE_OPTS, pageNumber: 1, totalPages: 3 })
const textCalls = doc.text.mock.calls.map((c: unknown[]) => c[0])
expect(textCalls.some((t: unknown) => typeof t === 'string' && t.includes('InQ ROI'))).toBe(true)
})
it('incluye "Powered by InQuality" en el texto del footer', () => {
drawSharedFooter(doc, { ...BASE_OPTS, pageNumber: 1, totalPages: 3 })
const textCalls = doc.text.mock.calls.map((c: unknown[]) => c[0])
expect(textCalls.some((t: unknown) => typeof t === 'string' && t.includes('Powered by InQuality'))).toBe(true)
})
it('muestra paginación correcta "Página X de Y"', () => {
drawSharedFooter(doc, { ...BASE_OPTS, pageNumber: 2, totalPages: 4 })
const textCalls = doc.text.mock.calls.map((c: unknown[]) => c[0])
expect(textCalls.some((t: unknown) => typeof t === 'string' && t === 'Página 2 de 4')).toBe(true)
})
it('NO incluye versión del producto (v0.1.0)', () => {
drawSharedFooter(doc, { ...BASE_OPTS, pageNumber: 1, totalPages: 3 })
const allText = doc.text.mock.calls.map((c: unknown[]) => c[0]).join(' ')
expect(allText).not.toContain('v0.')
expect(allText).not.toContain('v0.1')
})
it('usa doc.internal.pageSize.getHeight() para posición inferior', () => {
drawSharedFooter(doc, { ...BASE_OPTS, pageNumber: 1, totalPages: 3 })
expect(doc.internal.pageSize.getHeight).toHaveBeenCalled()
})
})
// ─── applyFootersToAllPages ───────────────────────────────────────────────────
describe('applyFootersToAllPages', () => {
it('llama setPage para cada página del documento', () => {
const doc = makeMockDoc(210, 297, 4)
applyFootersToAllPages(doc, BASE_OPTS)
expect(doc.setPage).toHaveBeenCalledTimes(4)
expect(doc.setPage).toHaveBeenNthCalledWith(1, 1)
expect(doc.setPage).toHaveBeenNthCalledWith(4, 4)
})
})