oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1import Foundation
2import RastrilloApple
3import Testing
4
5@testable import SheetsKit
6
7@Test func coordinatesAndReverseRanges() {
8 #expect(Coordinate.parse("aa151") == Coordinate(r: 150, c: 26))
9 #expect(Coordinate.parse("A0") == nil)
10 #expect(Coordinate.parse("A1junk") == nil)
11 #expect(CellRange.parse("B3:A1")?.count == 6)
12 #expect(CellRange.parse("A1:") == nil)
13}
14@Test func spreadsheetClipboardRoundTrip() {
15 let values = [["a\tb", "line\nnext", "say \"hello\""], ["", "=A1+$B$2", ""]]
16 #expect(TSV.decode(TSV.encode(values)) == values)
17 #expect(TSV.decode("a\tb\r\nc\td\r\n") == [["a", "b"], ["c", "d"]])
18}
19
20@MainActor @Test func fillCopiesAnchorWithFormulaOriginAndBounds() {
21 let model = SheetModel(client: NativeHTTP(origin: URL(string: "http://localhost")!))
22 model.cells[Coordinate(r: 0, c: 0)] = Cell(r: 0, c: 0, raw: "=B1", display: "0")
23 let down = model.fillWrites(down: true)
24 #expect(down.count == 1)
25 #expect(down.first?.r == 1 && down.first?.c == 0)
26 #expect(down.first?.raw == "=B1")
27 #expect(down.first?.fromR == 0 && down.first?.fromC == 0)
28 let right = model.fillWrites(down: false)
29 #expect(right.first?.r == 0 && right.first?.c == 1)
30 model.selection = CellRange(Coordinate(r: 0, c: 0), Coordinate(r: 1, c: 1))
31 #expect(model.fillWrites(down: true).count == 3)
32 #expect(
33 model.fillWrites(down: true).allSatisfy { $0.raw == "=B1" && $0.fromR == 0 && $0.fromC == 0 })
34 let end = Coordinate(r: model.rows - 1, c: model.cols - 1)
35 model.selection = CellRange(end, end)
36 #expect(model.fillWrites(down: true).isEmpty)
37 #expect(model.fillWrites(down: false).isEmpty)
38 model.copied = [down]
39 model.copiedText = "=B1"
40 model.clearCopyProvenance()
41 #expect(model.copied == nil && model.copiedText.isEmpty)
42}
43@Test func streamedEventsRespectBoundaries() async {
44 let parser = SheetEventParser()
45 #expect(await parser.receive("event: cells") == nil)
46 #expect(await parser.receive("data: {\"version\":1}") == nil)
47 let event = await parser.receive("")
48 #expect(event?.name == "cells")
49 #expect(event?.data == "{\"version\":1}")
50}
51
52@MainActor @Test func formulaDraftTracksSelectionSynchronously() {
53 let model = SheetModel(client: NativeHTTP(origin: URL(string: "http://localhost")!))
54 let first = Coordinate(r: 0, c: 0)
55 model.cells[first] = Cell(r: 0, c: 0, raw: "=2+3", display: "5")
56 model.select(first)
57 #expect(model.draft == "=2+3")
58 #expect(!model.dirty)
59 model.draft = "=7*6"
60 model.select(Coordinate(r: 1, c: 0))
61 #expect(model.selection.start == first)
62 #expect(model.draft == "=7*6")
63 model.discard()
64 #expect(model.draft == "=2+3")
65 #expect(!model.dirty)
66 model.select(Coordinate(r: 1, c: 0))
67 #expect(model.draft.isEmpty)
68 #expect(!model.dirty)
69}
70
71@MainActor @Test func olderLiveResponseCannotReplaceCurrentCell() async {
72 let model = SheetModel(client: NativeHTTP(origin: URL(string: "http://localhost")!))
73 model.version = 2
74 model.cells[Coordinate(r: 0, c: 0)] = Cell(r: 0, c: 0, raw: "new", display: "new")
75 await model.accept(
76 GridResponse(version: 1, cells: [Cell(r: 0, c: 0, raw: "old", display: "old")]))
77 #expect(model.cells[Coordinate(r: 0, c: 0)]?.raw == "new")
78}
79
80@MainActor @Test func peerEditsUpdateCleanFormulaButPreserveDraft() async {
81 let model = SheetModel(client: NativeHTTP(origin: URL(string: "http://localhost")!))
82 let first = Coordinate(r: 0, c: 0)
83 model.cells[first] = Cell(r: 0, c: 0, raw: "=2+3", display: "5")
84 model.select(first)
85 await model.accept(GridResponse(version: 1, cells: [Cell(r: 0, c: 0, raw: "=3+4", display: "7")]))
86 #expect(model.draft == "=3+4")
87 #expect(!model.dirty)
88 model.draft = "my pending edit"
89 await model.accept(GridResponse(version: 2, cells: [Cell(r: 0, c: 0, raw: "=4+5", display: "9")]))
90 #expect(model.cells[first]?.display == "9")
91 #expect(model.draft == "my pending edit")
92 #expect(model.dirty)
93}
94
95@MainActor @Test func pendingBatchCannotBeReplacedByFormulaCommit() async {
96 let model = SheetModel(client: NativeHTTP(origin: URL(string: "http://localhost")!))
97 model.failedWrites = [
98 CellWrite(r: 1, c: 0, raw: "first pending"), CellWrite(r: 2, c: 0, raw: "second pending"),
99 ]
100 model.draft = "a new edit"
101 await model.commit()
102 #expect(model.failedWrites.map(\.raw) == ["first pending", "second pending"])
103 #expect(model.dirty)
104 #expect(model.error.contains("Retry or discard"))
105 model.discard()
106 model.rangeText = "B2:C3"
107 model.selectRange()
108 #expect(model.selection.start == Coordinate(r: 0, c: 0))
109 #expect(model.failedWrites.count == 2)
110}
111
112@Test func workbookExportNamesUseSafeDocumentTitles() {
113 let url = URL(fileURLWithPath: "/tmp/private-staging.xlsx")
114 #expect(WorkbookExport(url: url, title: "Budget 2026").filename == "Budget 2026.xlsx")
115 #expect(WorkbookExport(url: url, title: "../Budget:2026\n").filename == "-Budget-2026.xlsx")
116 #expect(WorkbookExport(url: url, title: "...").filename == "Workbook.xlsx")
117 #expect(WorkbookExport(url: url, title: String(repeating: "đŸ§ª", count: 100)).filename.utf8.count <= 205)
118}
119