Files
reimburse/tests/SpreadsheetLayoutTests.swift
T
2026-09-18 10:35:38 +08:00

123 lines
7.6 KiB
Swift

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
}
}