import XCTest import Foundation import ForgeColor import ForgeGrade import ForgeLibrary @testable import ForgeExport final class ShotReportTests: XCTestCase { func makeShots(stillDir: String? = nil) throws -> [Shot] { let dayID = UUID() let cdl = CDL(slope: SIMD3(1.2, 1.0, 0.85), offset: SIMD3(0.01, -0.02, 0), power: SIMD3(0.95, 1, 1.1), saturation: 1.25) let snapshot = try GradeSnapshot.encode(GradeStack(nodes: [GradeNode(kind: .cdl(cdl))])) var stillPath: String? if let dir = stillDir { try FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true) stillPath = dir + "/R001C001_test.png" // Tiny valid PNG via known bytes: reuse writer through raw data — simplest: // 1x1 red PNG generated with our own encoder shape is fine for embed test. let pngMagic: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] try Data(pngMagic + [0, 0, 0, 0]).write(to: URL(fileURLWithPath: stillPath!)) } return [ Shot(dayID: dayID, clipName: "R001C001", cameraSlot: "A-Cam", timecodeIn: "10:00:00:00", timecodeOut: "10:00:30:00", exposureIndex: 800, whiteBalance: 5600, tint: 0, fps: 24, gradeSnapshot: snapshot, stillPath: stillPath), Shot(dayID: dayID, clipName: "B001_C012", cameraSlot: "B-Cam", timecodeIn: "10:05:00:00", timecodeOut: "10:06:00:00", exposureIndex: 1600, whiteBalance: 3200, tint: -2, fps: 23.98, gradeSnapshot: snapshot), ] } // Report contains header, shot rows, CDL values, camera metadata. func testReportContent() throws { let shots = try makeShots() let html = ShotReport.html( shots: shots, projectName: "Nightfall", dayLabel: "Day 03", date: "2026-07-11") XCTAssertTrue(html.contains("")) XCTAssertTrue(html.contains("Nightfall")) XCTAssertTrue(html.contains("Day 03")) XCTAssertTrue(html.contains("R001C001")) XCTAssertTrue(html.contains("B001_C012")) // CDL formatted values. XCTAssertTrue(html.contains("1.200")) XCTAssertTrue(html.contains("1.250")) // sat // Metadata. XCTAssertTrue(html.contains("800")) XCTAssertTrue(html.contains("5600")) XCTAssertTrue(html.contains("10:00:00:00")) // Reel tokens present. XCTAssertTrue(html.contains("#F0A63C")) XCTAssertTrue(html.contains("#08090C")) } // Stills embedded as base64 data URIs when file exists. func testStillEmbedded() throws { let dir = NSTemporaryDirectory() + "forge-report-\(UUID().uuidString)" defer { try? FileManager.default.removeItem(atPath: dir) } let shots = try makeShots(stillDir: dir) let html = ShotReport.html(shots: shots, projectName: "P", dayLabel: "D", date: "2026-07-11") XCTAssertTrue(html.contains("data:image/png;base64,iVBORw0KGgo")) // Shot without still gets placeholder, not broken img. XCTAssertTrue(html.contains("no-still") || html.contains("No still")) } // Shot count + totals in summary. func testSummaryTotals() throws { let shots = try makeShots() let html = ShotReport.html(shots: shots, projectName: "P", dayLabel: "D", date: "2026-07-11") XCTAssertTrue(html.contains("2 shots")) } // HTML-escapes clip names. func testEscaping() throws { var shots = try makeShots() shots[0].clipName = "R001&" let html = ShotReport.html(shots: shots, projectName: "P", dayLabel: "D", date: "2026-07-11") XCTAssertTrue(html.contains("R001<C001>&")) XCTAssertFalse(html.contains("R001")) } }