106 lines
7.4 KiB
Swift
106 lines
7.4 KiB
Swift
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")
|
|
}
|
|
}
|