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, _ selectedPayments: Set, _ 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") } }