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// B12 and its review (#35). The server says how many versions a write
8// consumed (`stamps`); accept() refetches only when the version moved
9// further than that, so a multi-cell batch applies in place. Stampless
10// responses (format, size, snapshot) allow one. The server-side proof,
11// against real responses, is TestNativeVersionContractAgainstRealResponses.
12//
13// The client points at a closed port, so an attempted refetch shows as a
14// connection error and leaves the incremental cells unapplied.
15@MainActor private func gapModel(version: UInt64) -> SheetModel {
16 let model = SheetModel(client: NativeHTTP(origin: URL(string: "http://127.0.0.1:1")!))
17 model.page = SheetPage(
18 Sheet: SheetSummary(ID: 1, Title: "Fixture", TabID: 1, TabName: "Sheet 1"), Rows: 10, Cols: 5,
19 Truncated: false, MayWrite: true, MayComment: true, MayShare: false, FillSwatches: [],
20 InkSwatches: [], NumberFormats: [], Warned: nil)
21 model.version = version
22 model.error = ""
23 return model
24}
25
26@MainActor @Test func stampedBatchAppliesWithoutRefetch() async {
27 let model = gapModel(version: 5)
28 // Three typed cells (versions 6, 7, 8) plus one formula they changed.
29 await model.received(
30 SheetEvent(
31 name: "cells",
32 data: #"{"version":8,"stamps":3,"cells":[{"r":0,"c":0,"raw":"1","display":"1"},{"r":1,"c":0,"raw":"2","display":"2"},{"r":2,"c":0,"raw":"3","display":"3"},{"r":3,"c":0,"raw":"=SUM(A1:A3)","display":"6","derived":true}]}"#
33 ), source: model.grid)
34 #expect(model.error == "", "a stamped batch attempted a refetch: \(model.error)")
35 #expect(model.version == 8)
36 #expect(model.cells[Coordinate(r: 2, c: 0)]?.raw == "3")
37 #expect(model.cells[Coordinate(r: 3, c: 0)]?.display == "6")
38}
39
40@MainActor @Test func versionGapBeyondTheStampsStillRefetches() async {
41 let model = gapModel(version: 5)
42 // Two stamps ending at 9, so versions 6 and 7 were missed.
43 await model.received(
44 SheetEvent(
45 name: "cells",
46 data: #"{"version":9,"stamps":2,"cells":[{"r":0,"c":0,"raw":"1","display":"1"},{"r":1,"c":0,"raw":"2","display":"2"}]}"#
47 ), source: model.grid)
48 #expect(model.error != "", "a real gap was applied incrementally instead of refetched")
49 #expect(model.version == 5)
50 #expect(model.cells[Coordinate(r: 0, c: 0)] == nil)
51}
52
53@MainActor @Test func formatCellsDoNotWidenTheAcceptedJump() async {
54 let model = gapModel(version: 5)
55 // A format over four cells consumes no stamp; version 7 means a missed
56 // edit, however many cells the format response lists.
57 let cells = (0..<4).map { Cell(r: $0, c: 0, raw: "", display: "") }
58 await model.accept(GridResponse(version: 7, cells: cells))
59 #expect(model.error != "")
60 #expect(model.version == 5)
61}
62
63@MainActor @Test func singleStepsAndStamplessResponsesStillApply() async {
64 let model = gapModel(version: 5)
65 await model.accept(GridResponse(version: 6, cells: [Cell(r: 0, c: 0, raw: "x", display: "x")], stamps: 1))
66 // A format right after reports the next stamp: one ahead, no stamps.
67 await model.accept(GridResponse(version: 7, cells: [Cell(r: 0, c: 0, raw: "x", display: "x")]))
68 #expect(model.error == "")
69 #expect(model.version == 7)
70 #expect(model.cells[Coordinate(r: 0, c: 0)]?.raw == "x")
71}
72