import { describe, it, expect } from "vitest"; import { buildProjectLogsHtml, buildUserSectionHtml, buildPrintHtml, } from "../admin/hooks/useAttendanceAdmin"; /** * Characterization test for the attendance print-HTML builders. * * These builders had `any`-typed params; this pins their CURRENT output so the * follow-up retyping (precise AttendanceRecord/UserTotal/PrintData types) is * provably behavior-preserving. The only runtime touch in that retype is * `parseInt(log.hours)` → `parseInt(String(log.hours))` to satisfy the typed * `string | number | null` shape — the cases below exercise both string and * numeric hours/minutes precisely so that equivalence is locked in. * * Fixtures are cast through `Parameters` so the test compiles both * before and after the param types tighten. */ type RecordArg = Parameters[0]; type UserDataArg = Parameters[1]; type PrintDataArg = Parameters[2]; const asRecord = (o: unknown) => o as unknown as RecordArg; const asUserData = (o: unknown) => o as unknown as UserDataArg; const asPrintData = (o: unknown) => o as unknown as PrintDataArg; describe("buildProjectLogsHtml", () => { it("formats hours/minutes given as strings", () => { expect( buildProjectLogsHtml( asRecord({ project_logs: [ { project_id: 1, project_name: "Alpha", hours: "2", minutes: "30" }, ], }), ), ).toBe("
Alpha (2:30h)
"); }); it("formats hours/minutes given as numbers (parseInt-of-number path)", () => { expect( buildProjectLogsHtml( asRecord({ project_logs: [ { project_id: 2, project_name: "Beta", hours: 1, minutes: 5 }, ], }), ), ).toBe("
Beta (1:05h)
"); }); it("derives duration from started_at/ended_at when hours are absent", () => { expect( buildProjectLogsHtml( asRecord({ project_logs: [ { project_id: 3, project_name: "Gamma", started_at: "2098-01-05T08:00:00", ended_at: "2098-01-05T10:30:00", }, ], }), ), ).toBe("
Gamma (2:30h)
"); }); it("falls back to '#id' and 0:00 when name and times are missing", () => { expect( buildProjectLogsHtml(asRecord({ project_logs: [{ project_id: 7 }] })), ).toBe("
#7 (0:00h)
"); }); it("renders the bare project name when there are no logs", () => { expect( buildProjectLogsHtml( asRecord({ project_name: "Solo & ", project_logs: [] }), ), ).toBe("Solo & <Co>"); }); }); // Deterministic fixture: a fixed far-future month, one work day (with a numeric // project log so the snapshot also guards the parseInt path in context) and one // vacation day. const workRecord = { id: 1, user_id: 5, shift_date: "2098-03-04", leave_type: "work", arrival_time: "2098-03-04T08:00:00", departure_time: "2098-03-04T16:30:00", break_start: "2098-03-04T12:00:00", break_end: "2098-03-04T12:30:00", notes: "Poznámka ", project_logs: [ { project_id: 1, project_name: "Alpha", hours: 8, minutes: 0 }, ], }; const leaveRecord = { id: 2, user_id: 5, shift_date: "2098-03-05", leave_type: "vacation", leave_hours: 8, }; const userData = { name: "Jan Novák", minutes: 480, records: [workRecord, leaveRecord], }; const printData = { month: "2098-03", month_name: "Březen 2098", selected_user_name: null, user_totals: { "5": userData }, }; describe("buildUserSectionHtml", () => { it("produces a stable per-user section (full output pinned)", () => { const html = buildUserSectionHtml( "5", asUserData(userData), asPrintData(printData), ); expect(html).toMatchSnapshot(); }); }); describe("buildPrintHtml", () => { // NB: the document footer embeds `new Date()`, so assert stable fragments // rather than snapshotting the whole (non-deterministic) output. it("wraps the user sections in a stable document shell", () => { const section = buildUserSectionHtml( "5", asUserData(userData), asPrintData(printData), ); const html = buildPrintHtml( asPrintData(printData), section, "", "", "Firma s.r.o.", ); expect(html).toContain("Docházka - Březen 2098"); expect(html).toContain("EVIDENCE DOCHÁZKY"); expect(html).toContain('
Březen 2098
'); expect(html).toContain("Firma s.r.o."); expect(html).toContain(section); }); });