import AppKit import SwiftUI @main struct SpreadsheetLayoutTests { @MainActor static func main() throws { _ = NSApplication.shared NSApplication.shared.appearance = NSAppearance(named: .aqua) let data = try Data(contentsOf: URL(fileURLWithPath: CommandLine.arguments[1])) let layout = try JSONDecoder().decode(SpreadsheetLayout.self, from: data).validated() let sheet = layout.sheets[0] let cells = Dictionary(uniqueKeysWithValues: sheet.cells.map { ($0.reference, $0) }) let canvas = SpreadsheetCanvasView(sheet: sheet, scale: 1) let placements = sheet.cells.filter { !$0.text.isEmpty }.map { canvas.placement(for: $0) } let overflow = placements.compactMap(\.overflow) for ref in ["A30", "D30", "A31"] { let cell = cells[ref]! precondition(canvas.verticalSegments(x: cell.rect.maxX, top: cell.rect.minY, bottom: cell.rect.maxY, overflow: overflow).isEmpty, "A vertical line still crosses \(ref)") } let boundary = cells["B30"]! precondition(!canvas.verticalSegments(x: boundary.rect.maxX, top: boundary.rect.minY, bottom: boundary.rect.maxY, overflow: overflow).isEmpty, "An unrelated border was removed") let anchor = cells["A30"]! let shortLabel = SpreadsheetCell(reference: anchor.reference, x: anchor.x, y: anchor.y, width: anchor.width, height: anchor.height, text: "签", style: anchor.style, overflowLeft: anchor.overflowLeft, overflowRight: anchor.overflowRight) precondition(canvas.placement(for: shortLabel).overflow == nil) let blockedLabel = SpreadsheetCell(reference: anchor.reference, x: anchor.x, y: anchor.y, width: anchor.width, height: anchor.height, text: anchor.text, style: anchor.style, overflowLeft: anchor.x, overflowRight: anchor.x + anchor.width) precondition(canvas.placement(for: blockedLabel).overflow == nil) for ref in ["F27", "H29"] { let placed = canvas.placement(for: cells[ref]!) let paragraph = placed.text.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as! NSParagraphStyle precondition(paragraph.alignment == .right) precondition(placed.text.string == "447.00") } let signature = canvas.placement(for: cells["H30"]!) precondition(canvas.placement(for: cells["A29"]!).text.string.contains("\nTOTAL PAYMENT (A)")) precondition(signature.text.string == "收\n款\n人\n签\n字") precondition(abs(signature.box.maxY - cells["H30"]!.rect.maxY) < 2) precondition(cells["H30"]!.style.borders["right"]?.style == "medium") for size in [CGSize(width: 800, height: 500), CGSize(width: 1200, height: 760)] { let scale = SpreadsheetZoom.width.scale(sheet: sheet, viewport: size) precondition(scale * sheet.width <= size.width - 48) let page = SpreadsheetZoom.page.scale(sheet: sheet, viewport: size) precondition(page * sheet.height <= size.height - 48) } let expanded = SpreadsheetPreviewWindow() expanded.show(layout: layout, title: "Test") let first = expanded.window! precondition(first.styleMask.contains(.resizable)) expanded.show(layout: layout, title: "Template") precondition(expanded.window === first) expanded.close() precondition(expanded.window == nil) print("Overflow borders, accounting alignment, stacked signature, merge edges, zoom and window cleanup passed") let folder = URL(fileURLWithPath: CommandLine.arguments[2], isDirectory: true) try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true) let bitmap = try capture(canvas, to: folder.appendingPathComponent("sheet.png")) let ratio = CGFloat(bitmap.pixelsWide) / canvas.bounds.width func luminance(x: CGFloat, y: CGFloat) -> CGFloat { let color = bitmap.colorAt(x: Int(x * ratio), y: Int(y * ratio))!.usingColorSpace(.deviceRGB)! return (color.redComponent + color.greenComponent + color.blueComponent) / 3 } // Pixel checks away from glyphs catch border regressions in actual drawing, // not just matching JSON models or two instances of the same renderer. for ref in ["A30", "D30", "A31"] { let cell = cells[ref]! precondition(luminance(x: cell.rect.maxX, y: cell.rect.minY + 15) > 0.97) } precondition(luminance(x: boundary.rect.maxX, y: boundary.rect.minY + 15) < 0.9) for width in [620, 820, 1200] { let host = NSHostingView(rootView: ExpenseDocumentPreview(layout: layout)) let window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: width, height: 700), styleMask: [.borderless], backing: .buffered, defer: false) window.contentView = host host.layoutSubtreeIfNeeded() RunLoop.current.run(until: Date().addingTimeInterval(0.3)) _ = try capture(host, to: folder.appendingPathComponent("preview-\(width).png")) window.orderOut(nil) } if CommandLine.arguments.count > 3 { let alternateData = try Data(contentsOf: URL(fileURLWithPath: CommandLine.arguments[3])) let alternate = try JSONDecoder().decode(SpreadsheetLayout.self, from: alternateData).validated() let host = NSHostingView(rootView: ExpenseDocumentPreview(layout: alternate, title: "报销单模板", preferredSheet: "说明页")) let window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 620, height: 500), styleMask: [.borderless], backing: .buffered, defer: false) window.contentView = host host.layoutSubtreeIfNeeded() RunLoop.current.run(until: Date().addingTimeInterval(0.3)) _ = try capture(host, to: folder.appendingPathComponent("template-selected-sheet.png")) precondition(containsCanvas(host, named: "说明页"), "Template sheet selection was ignored") host.rootView = ExpenseDocumentPreview(layout: alternate, title: "报销单模板", preferredSheet: "横向费用单") host.layoutSubtreeIfNeeded() RunLoop.current.run(until: Date().addingTimeInterval(0.3)) _ = try capture(host, to: folder.appendingPathComponent("template-alternate.png")) precondition(containsCanvas(host, named: "横向费用单"), "Changing the mapping sheet did not change the preview") window.orderOut(nil) } print("Rendered pixel checks passed; screenshots written to \(folder.path)") } @MainActor static func containsCanvas(_ view: NSView, named: String) -> Bool { if let canvas = view as? SpreadsheetCanvasView, canvas.sheet.name == named { return true } return view.subviews.contains { containsCanvas($0, named: named) } } @MainActor static func capture(_ view: NSView, to url: URL) throws -> NSBitmapImageRep { let bitmap = view.bitmapImageRepForCachingDisplay(in: view.bounds)! view.cacheDisplay(in: view.bounds, to: bitmap) try bitmap.representation(using: .png, properties: [:])!.write(to: url) return bitmap } }