rainbow-dragon/Tests/ForgeExportTests/ShotReportTests.swift

86 lines
3.8 KiB
Swift

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("<!DOCTYPE html>"))
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<C001>&"
let html = ShotReport.html(shots: shots, projectName: "P", dayLabel: "D", date: "2026-07-11")
XCTAssertTrue(html.contains("R001&lt;C001&gt;&amp;"))
XCTAssertFalse(html.contains("R001<C001>"))
}
}