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
+105
View File
@@ -0,0 +1,105 @@
import Foundation
@main
struct WorkspacePresentationTests {
static func material(_ id: String, amount: Double, date: String, merchant: String = "测试商户") -> Material {
var ocr = OCRData()
ocr.status = "success"
ocr.amounts = [String(format: "%.2f", amount)]
ocr.merchants = [merchant]
return Material(id: id, name: id + ".pdf", path: "", type: "invoice", size: 0, matched: false,
previewPath: "", ocr: ocr, displayAmount: amount, sortDate: date, issueDate: date)
}
static func main() throws {
let a = material("a", amount: 10, date: "2026-09-02", merchant: "铁路")
let b = material("b", amount: 100, date: "2026-09-01")
let c = material("c", amount: 20, date: "2026-09-03")
let items = [a, b, c]
precondition(WorkspaceFilter.materials(items, query: "", order: .amount).map(\.id) == ["a", "c", "b"])
precondition(WorkspaceFilter.materials(items, query: "", order: .date).map(\.id) == ["b", "a", "c"])
precondition(WorkspaceFilter.materials(items, query: "铁路 10.00", order: .name).map(\.id) == ["a"])
precondition(WorkspaceFilter.materials(items, query: "B.PDF", order: .name).map(\.id) == ["b"])
precondition(WorkspaceFilter.materials(items, query: "2026-09-03", order: .name).map(\.id) == ["c"])
precondition(WorkspaceFilter.materials(items, query: "", order: .amount, descending: true).map(\.id) == ["b", "c", "a"])
var automatic = MatchGroup(id: "auto", invoices: [a], payments: [], category: "交通", matchType: "auto", score: 100, reasons: [])
let manual = MatchGroup(id: "manual", invoices: [b], payments: [], category: "其他", matchType: "manual", score: 100, reasons: [])
precondition(automatic.needsAttention && !manual.needsAttention)
automatic.explanation = try JSONDecoder().decode(MatchExplanation.self, from: Data("""
{"version":2,"title":"test","rawScore":55,"checks":[{"title":"amount","points":55,"maximum":100,"detail":"","issue":""}],"note":""}
""".utf8))
precondition(automatic.needsAttention)
var failed = manual
failed.id = "failed"
failed.invoices[0].ocr.status = "failed"
precondition(failed.needsAttention)
let matches = [automatic, manual, failed]
precondition(WorkspaceFilter.matches(matches, query: "", category: "全部", attentionOnly: true, order: .date).count == 2)
let visible = WorkspaceFilter.matches(matches, query: "铁路", category: "交通", attentionOnly: false, order: .date)
precondition(visible.map(\.id) == ["auto"])
var selection = MatchedPPTSelection(ids: ["manual"])
selection.setVisible(visible, selected: true)
precondition(selection.ids == ["manual", "auto"])
selection.setVisible(visible, selected: false)
precondition(selection.ids == ["manual"])
var workspace = Workspace()
workspace.matches = matches
let presentation = WorkspacePresentation(workspace)
precondition(presentation.matchNumbers.count == 3)
precondition(presentation.categoryCounts["其他"] == 2 && presentation.attentionCount == 2)
precondition(workspace.matches.map(\.id) == ["auto", "manual", "failed"])
let columns = presentation.categoryColumns(for: matches)
precondition(columns.map(\.category) == ["交通", "其他"])
precondition(columns.allSatisfy { !$0.matches.isEmpty })
let traffic = columns.first(where: { $0.category == "交通" })!
let other = columns.first(where: { $0.category == "其他" })!
precondition(traffic.matches.map(\.id) == ["auto"] && traffic.invoiceTotal == 10)
precondition(other.matches.map(\.id) == ["manual", "failed"] && other.invoiceTotal == 200)
precondition(columns.flatMap(\.matches).count == matches.count)
selection.setVisible(traffic.matches, selected: true)
selection.setVisible(other.matches, selected: true)
selection.setVisible(traffic.matches, selected: false)
precondition(selection.ids == ["manual", "failed"])
let filteredColumns = presentation.categoryColumns(for: visible)
precondition(filteredColumns.map(\.id) == ["交通"])
precondition(selection.ids == ["manual", "failed"])
precondition(presentation.categoryColumns(for: matches).map(\.id) == ["交通", "其他"])
let sorted = WorkspaceFilter.matches(matches, query: "", category: "全部", attentionOnly: false, order: .date)
precondition(presentation.categoryColumns(for: sorted).first(where: { $0.category == "其他" })!.matches.map(\.id) == ["manual", "failed"])
workspace.matches[1].category = "住宿"
var updated = WorkspacePresentation(workspace)
precondition(updated.categoryColumns(for: workspace.matches).first(where: { $0.category == "住宿" })!.matches.map(\.id) == ["manual"])
precondition(selection.ids == ["manual", "failed"])
let exported = try selection.exportWorkspace(from: workspace)
precondition(exported.matches.map(\.id) == ["manual", "failed"])
workspace.matches[2].category = "住宿"
updated = WorkspacePresentation(workspace)
precondition(updated.categoryColumns(for: workspace.matches).map(\.id) == ["住宿", "交通"])
workspace.matches.removeAll { $0.category == "住宿" }
precondition(WorkspacePresentation(workspace).categoryColumns(for: workspace.matches).map(\.id) == ["交通"])
workspace.matches = matches
workspace.matches[1].category = "住宿"
workspace.matches[0].category = "历史类型"
workspace.matches[2].category = ""
updated = WorkspacePresentation(workspace)
let extra = updated.categoryColumns(for: workspace.matches)
precondition(extra.map(\.id) == ["住宿", "", "历史类型"])
precondition(extra.first(where: { $0.category == "" })!.title == "未分类")
precondition(extra.first(where: { $0.category == "历史类型" })!.matches.map(\.id) == ["auto"])
precondition(updated.categoryColumns(for: []).isEmpty)
precondition(WorkspacePresentation().categoryColumns(for: []).isEmpty)
var noisy = manual
noisy.invoices[0].ocr.merchants = ["载次数", "名称:测试购买方有限公司", "全称", "一站式企业出行与商旅平台"]
let originalCandidates = noisy.invoices[0].ocr.merchants
precondition(WorkspaceFilter.matches([noisy], query: "测试购买方", category: "全部",
attentionOnly: false, order: .date).map(\.id) == [manual.id])
precondition(noisy.invoices[0].ocr.merchants == originalCandidates)
precondition(MatchedBoardLayout.columnWidth(available: 900, count: 7) == 132)
let desktopWidth = MatchedBoardLayout.columnWidth(available: 1100, count: 7)
precondition(abs(desktopWidth * 7 + MatchedBoardLayout.gap * 6 - 1100) < 0.001)
precondition(MatchedBoardLayout.columnWidth(available: 700, count: 1) == 700)
precondition(MatchedBoardLayout.columnWidth(available: 0, count: 0).isFinite)
print("Original OCR search preservation and compact category layout sizing passed")
print("Search, numeric sorting, attention filtering, cached summaries and hidden selection retention passed")
print("Automatic empty-column removal, category reappearance, filtering, selection and unchanged export order passed")
}
}