9月20日

This commit is contained in:
csj
2026-09-20 16:31:24 +08:00
parent 967eaaec8a
commit 68bf79ec3f
37 changed files with 3581 additions and 1627 deletions
+82
View File
@@ -0,0 +1,82 @@
import Foundation
import CoreGraphics
@main
struct PairConnectionLayoutTests {
static func main() {
let left = CGRect(x: 0, y: 80, width: 300, height: 500)
let right = CGRect(x: 340, y: 80, width: 300, height: 500)
let invoices = (0..<4).map { "i\($0)" }, payments = (0..<4).map { "p\($0)" }
var frames: [String: CGRect] = [:]
for index in 0..<4 {
frames[invoices[index]] = CGRect(x: 3, y: 90 + index * 110, width: 290, height: 100)
frames[payments[index]] = CGRect(x: 343, y: 90 + index * 110, width: 290, height: 100)
}
func layout(_ selectedInvoices: Set<String>, _ selectedPayments: Set<String>,
_ currentFrames: [String: CGRect]) -> PairConnectionLayout {
PairConnectionLayout(invoiceIDs: invoices, paymentIDs: payments,
selectedInvoices: selectedInvoices, selectedPayments: selectedPayments,
frames: currentFrames, invoiceViewport: left, paymentViewport: right)
}
for (invoiceCount, paymentCount) in [(1, 1), (1, 4), (4, 1), (4, 4)] {
let result = layout(Set(invoices.prefix(invoiceCount)), Set(payments.prefix(paymentCount)), frames)
precondition(result.representedPairCount == invoiceCount * paymentCount)
precondition(result.invoices.count == invoiceCount && result.payments.count == paymentCount)
precondition(result.invoices.allSatisfy { $0.overflow == nil })
precondition(result.payments.allSatisfy { $0.overflow == nil })
precondition(result.lines.allSatisfy { result.clipRect.contains($0.start) && result.clipRect.contains($0.end) })
}
precondition(layout([], Set(payments), frames).lines.isEmpty)
precondition(layout(Set(invoices), [], frames).lines.isEmpty)
let initial = layout(["i0"], Set(payments), frames)
var scrolled = frames
for id in payments { scrolled[id] = frames[id]!.offsetBy(dx: 0, dy: -160) }
let afterScroll = layout(["i0"], Set(payments), scrolled)
precondition(afterScroll.invoices.map(\.point) == initial.invoices.map(\.point))
precondition(afterScroll.payments.map(\.point) != initial.payments.map(\.point))
precondition(afterScroll.payments.reduce(0) { $0 + $1.count } == 4)
precondition(afterScroll.payments.first(where: { $0.overflow == .above })?.count == 1)
precondition(afterScroll.representedPairCount == 4)
// Offscreen anchors can disappear from LazyVGrid without removing their selected relationships.
scrolled.removeValue(forKey: "p0")
let recycled = layout(["i0"], Set(payments), scrolled)
precondition(recycled.lines == afterScroll.lines)
scrolled.removeValue(forKey: "p3")
let bothEdges = layout(Set(invoices), Set(payments), scrolled)
precondition(bothEdges.payments.first(where: { $0.overflow == .above })?.count == 1)
precondition(bothEdges.payments.first(where: { $0.overflow == .below })?.count == 1)
precondition(bothEdges.invoices.reduce(0) { $0 + $1.count } * bothEdges.payments.reduce(0) { $0 + $1.count } == 16)
var partial = frames
partial["p0"] = CGRect(x: 343, y: 20, width: 290, height: 100)
let clipped = layout(["i0"], ["p0"], partial)
precondition(clipped.payments[0].point.y == 100)
precondition(clipped.payments[0].overflow == nil)
let allRecycled = layout(Set(invoices), Set(payments), [:])
precondition(allRecycled.invoices[0].count == 4 && allRecycled.payments[0].count == 4)
precondition(allRecycled.representedPairCount == 16 && allRecycled.lines.count <= 3)
let filtered = PairConnectionLayout(invoiceIDs: ["i0"], paymentIDs: ["p1"], selectedInvoices: Set(invoices),
selectedPayments: Set(payments), frames: frames, invoiceViewport: left, paymentViewport: right)
precondition(filtered.representedPairCount == 1)
let empty = PairConnectionLayout(invoiceIDs: invoices, paymentIDs: payments, selectedInvoices: Set(invoices),
selectedPayments: Set(payments), frames: frames, invoiceViewport: .zero, paymentViewport: right)
precondition(empty.lines.isEmpty)
let grid: [String: CGRect] = [
"i0": CGRect(x: 3, y: 90, width: 140, height: 150),
"i1": CGRect(x: 155, y: 90, width: 140, height: 150),
"p0": CGRect(x: 343, y: 90, width: 140, height: 150),
"p1": CGRect(x: 495, y: 90, width: 140, height: 150)
]
let routed = layout(["i0", "i1"], ["p0", "p1"], grid)
precondition(routed.representedPairCount == 4)
for line in routed.lines {
let stroke = CGRect(x: min(line.start.x, line.end.x) - 0.5, y: min(line.start.y, line.end.y) - 0.5,
width: abs(line.end.x - line.start.x) + 1, height: abs(line.end.y - line.start.y) + 1)
precondition(grid.values.allSatisfy { !stroke.intersects($0.insetBy(dx: 1, dy: 1)) },
"Connection crosses a card's contents")
}
print("Pair connections: 1:1, 1:N, N:1, N:M, independent scrolling, clipping, recycled cells and filtering passed")
}
}