| 1 | // Test-only executable. Receives an isolated fixture credential over stdin. |
| 2 | import AppKit |
| 3 | import SwiftUI |
| 4 | import RastrilloApple |
| 5 | import CalendarKit |
| 6 | struct Input: Decodable { let origin: URL; let credential: NativeSession; let output: String } |
| 7 | @main struct Review { |
| 8 | @MainActor static func main() async throws { |
| 9 | let input = try JSONDecoder().decode(Input.self, from: FileHandle.standardInput.readDataToEndOfFile()) |
| 10 | guard input.origin.host == "127.0.0.1" else { fatalError("Review accepts only an isolated loopback fixture") } |
| 11 | let app = NSApplication.shared |
| 12 | app.setActivationPolicy(.accessory) |
| 13 | let client = NativeHTTP(origin: input.origin, credential: input.credential) |
| 14 | let model = CalendarModel() |
| 15 | model.date = ISO8601DateFormatter().date(from: "2026-10-24T12:00:00Z")!; await model.load(client) |
| 16 | guard model.error.isEmpty else { fatalError("Fixture failed: " + model.error) } |
| 17 | let menu = ProcessInfo.processInfo.environment["NATIVE_REVIEW_MENU"] == "1" |
| 18 | let view = NSHostingView(rootView: Group { |
| 19 | if menu { CalendarMenuView(client: client, model: model) } |
| 20 | else { CalendarWorkspace(client: client, model: model, signOut: {}) } |
| 21 | }.preferredColorScheme(.light).tint(Color(red: 0.76, green: 0.2, blue: 0.3))) |
| 22 | let window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: menu ? 380 : 1100, height: menu ? 640 : 800), styleMask: [.titled, .closable, .resizable], backing: .buffered, defer: false) |
| 23 | window.appearance = NSAppearance(named: .aqua) |
| 24 | window.contentView = view |
| 25 | window.center() |
| 26 | window.orderFrontRegardless() |
| 27 | try await Task.sleep(for: .seconds(2)) |
| 28 | func capture(_ output: String) throws { |
| 29 | let capture = Process() |
| 30 | capture.executableURL = URL(fileURLWithPath: "/usr/sbin/screencapture") |
| 31 | capture.arguments = ["-x", "-o", "-l", String(window.windowNumber), output] |
| 32 | try capture.run() |
| 33 | capture.waitUntilExit() |
| 34 | guard capture.terminationStatus == 0 else { fatalError("Window capture failed") } |
| 35 | } |
| 36 | try capture(input.output) |
| 37 | if menu && ProcessInfo.processInfo.environment["NATIVE_REVIEW_DAY_SEQUENCE"] == "1" { |
| 38 | // Exercise the same published selection updated by the month buttons, |
| 39 | // without reloading: timed day -> all-day event -> empty day. |
| 40 | guard model.dayEvents(model.date).contains(where: { !$0.all_day }) else { fatalError("Missing timed fixture") } |
| 41 | model.date = model.calendar.date(byAdding: .day, value: 1, to: model.date)! |
| 42 | try await Task.sleep(for: .seconds(1)) |
| 43 | guard model.dayEvents(model.date).contains(where: { $0.all_day }) else { fatalError("Missing all-day fixture") } |
| 44 | try capture(input.output + ".all-day.png") |
| 45 | model.date = model.calendar.date(byAdding: .day, value: 1, to: model.date)! |
| 46 | try await Task.sleep(for: .seconds(1)) |
| 47 | guard model.dayEvents(model.date).isEmpty else { fatalError("Empty day retained events") } |
| 48 | try capture(input.output + ".empty.png") |
| 49 | } |
| 50 | } |
| 51 | } |
| 52 | |