86 lines
3.8 KiB
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<C001>&"))
|
|
XCTAssertFalse(html.contains("R001<C001>"))
|
|
}
|
|
}
|