Files
inq-roi-simulador-web/tests/lib/export/pdf-orientation.test.ts
Marcos Benítez d6e7226842 feat(process-repo): extender fromRow/toRow/getById con areaId y areaName
- fromRow: extrae areaId y areaName (vía JOIN en getById)
- toRow y updateEditable: incluyen area_id en escrituras a Supabase
- getById: JOIN con areas!area_id(name) para obtener nombre en una query
- Fixtures de tests actualizados con areaId: null (campo requerido)
- 4 tests nuevos en tests/persistence/area-repo.test.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 18:18:14 -03:00

212 lines
7.4 KiB
TypeScript
Raw Permalink 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.
/**
* Tests de orientación de páginas PDF — Etapa 5 Sprint 1.5.
*
* Verifica que:
* - PDF Actual/Automatizado: portrait → landscape → portrait
* - PDF ROI: todas portrait (no tiene página BPMN)
* - addPage se llama con el argumento correcto en cada caso
*
* Nota: estos tests usan mocks de jsPDF y verifican las llamadas a addPage.
* La orientación real del PDF se valida en los tests E2E (etapa-5-pdfs.spec.ts).
*/
import { describe, it, expect, vi, beforeEach } from 'vitest'
// ─── Mock de jsPDF ─────────────────────────────────────────────────────────────
const mockDoc = {
setProperties: vi.fn(),
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(),
save: vi.fn(),
splitTextToSize: vi.fn().mockImplementation((t: string) => [t]),
lastAutoTable: { finalY: 150 },
internal: {
getNumberOfPages: vi.fn().mockReturnValue(3),
pageSize: {
getWidth: vi.fn().mockReturnValue(210),
getHeight: vi.fn().mockReturnValue(297),
},
},
}
vi.mock('jspdf', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function MockJsPDF(this: any) { return mockDoc }
return { jsPDF: MockJsPDF }
})
vi.mock('jspdf-autotable', () => ({
default: vi.fn(),
}))
import type { Process, Simulation } from '@/domain/types'
const mockProcess: Process = {
id: 'p1', name: 'Test Process',
clientName: 'Test Client',
bpmnXml: '', currency: 'USD',
overheadPercentage: 0.2,
annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 50000,
createdAt: 0, updatedAt: 0,
groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null,
}
const mockSimulation: Simulation = {
id: 'sim1', processId: 'p1',
executedAt: new Date('2026-05-17T10:00:00Z').getTime(),
result: {
totalCost: 1200, totalDirectCost: 1000, totalIndirectCost: 200,
totalTimeMinutes: 90,
perActivity: [{
activityId: 'a1', bpmnElementId: 'task1', activityName: 'Tarea 1',
expectedDirectCost: 1200, expectedIndirectCost: 200, expectedTotalCost: 1200,
percentOfTotal: 100, executionProbability: 1.0, expectedExecutions: 1.0,
executionTimeMinutes: 30, resourceCostBreakdown: [],
}],
perResource: [], warnings: [],
},
resultAutomated: {
totalCost: 300, totalDirectCost: 250, totalIndirectCost: 50,
totalTimeMinutes: 20,
perActivity: [{
activityId: 'a1', bpmnElementId: 'task1', activityName: 'Tarea 1',
expectedDirectCost: 300, expectedIndirectCost: 50, expectedTotalCost: 300,
percentOfTotal: 100, executionProbability: 1.0, expectedExecutions: 1.0,
executionTimeMinutes: 10, resourceCostBreakdown: [],
}],
perResource: [], warnings: [],
},
}
// ─── Tests ─────────────────────────────────────────────────────────────────────
describe('PDF orientation — Etapa 5', () => {
beforeEach(() => {
vi.clearAllMocks()
// Simular que getWidth/getHeight devuelven 210×297 por defecto (portrait)
// En landscape, jsPDF realmente cambia los valores internamente
mockDoc.internal.pageSize.getWidth.mockReturnValue(210)
mockDoc.internal.pageSize.getHeight.mockReturnValue(297)
})
describe('PDF Actual/Automatizado — mezcla portrait-landscape-portrait-portrait', () => {
it('llama addPage exactamente 3 veces para 4 páginas', async () => {
const { exportToPdf } = await import('@/lib/export/pdf-export')
await exportToPdf({
process: mockProcess,
simulation: mockSimulation,
resources: [],
heatmapImageData: null,
tab: 'actual',
})
expect(mockDoc.addPage).toHaveBeenCalledTimes(3)
})
it('primera addPage usa landscape (página BPMN)', async () => {
const { exportToPdf } = await import('@/lib/export/pdf-export')
await exportToPdf({
process: mockProcess,
simulation: mockSimulation,
resources: [],
heatmapImageData: null,
tab: 'actual',
})
const firstCall = mockDoc.addPage.mock.calls[0]
expect(firstCall[0]).toBe('a4')
expect(firstCall[1]).toBe('landscape')
})
it('segunda addPage usa portrait (análisis + tabla)', async () => {
const { exportToPdf } = await import('@/lib/export/pdf-export')
await exportToPdf({
process: mockProcess,
simulation: mockSimulation,
resources: [],
heatmapImageData: null,
tab: 'actual',
})
const secondCall = mockDoc.addPage.mock.calls[1]
expect(secondCall[0]).toBe('a4')
expect(secondCall[1]).toBe('portrait')
})
it('tercera addPage usa portrait (nota metodológica)', async () => {
const { exportToPdf } = await import('@/lib/export/pdf-export')
await exportToPdf({
process: mockProcess,
simulation: mockSimulation,
resources: [],
heatmapImageData: null,
tab: 'actual',
})
const thirdCall = mockDoc.addPage.mock.calls[2]
expect(thirdCall[0]).toBe('a4')
expect(thirdCall[1]).toBe('portrait')
})
it('tab automatizado también usa landscape en página 2', async () => {
const { exportToPdf } = await import('@/lib/export/pdf-export')
await exportToPdf({
process: mockProcess,
simulation: mockSimulation,
resources: [],
heatmapImageData: null,
tab: 'automatizado',
})
const firstCall = mockDoc.addPage.mock.calls[0]
expect(firstCall[1]).toBe('landscape')
})
})
describe('PDF ROI — todas portrait', () => {
it('llama addPage 4 veces para 5 páginas (todas sin landscape)', async () => {
mockDoc.internal.getNumberOfPages.mockReturnValue(5)
const { exportToPdf } = await import('@/lib/export/pdf-export')
await exportToPdf({
process: mockProcess,
simulation: mockSimulation,
resources: [],
heatmapImageData: null,
tab: 'roi',
activities: [],
})
expect(mockDoc.addPage).toHaveBeenCalledTimes(4)
// Ninguna llamada debe usar landscape
const hasLandscape = mockDoc.addPage.mock.calls.some(
(c: unknown[]) => c[1] === 'landscape'
)
expect(hasLandscape).toBe(false)
})
})
describe('drawHeatmapImage — dimensiones dinámicas', () => {
it('usa getWidth() del doc, no constante hardcodeada', async () => {
const { drawHeatmapImage } = await import('@/lib/export/pdf-sections')
// Simular página landscape (297mm wide)
mockDoc.internal.pageSize.getWidth.mockReturnValue(297)
mockDoc.internal.pageSize.getHeight.mockReturnValue(210)
const fakeDataUrl = 'data:image/jpeg;base64,/9j/test'
drawHeatmapImage(mockDoc as any, fakeDataUrl, 40)
expect(mockDoc.internal.pageSize.getWidth).toHaveBeenCalled()
// La imagen debe ser más ancha que en portrait (170mm)
const imgCallArgs = mockDoc.addImage.mock.calls[0]
// args: [dataUrl, format, x, y, width, height]
const imgWidth = imgCallArgs[4]
expect(imgWidth).toBeGreaterThan(170) // más ancho que portrait (297 - 40 = 257mm)
})
})
})