123 lines
7.6 KiB
Swift
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
|
|
}
|
|
}
|