import Foundation import RastrilloApple import Testing @testable import SheetsKit // B12 and its review (#35). The server says how many versions a write // consumed (`stamps`); accept() refetches only when the version moved // further than that, so a multi-cell batch applies in place. Stampless // responses (format, size, snapshot) allow one. The server-side proof, // against real responses, is TestNativeVersionContractAgainstRealResponses. // // The client points at a closed port, so an attempted refetch shows as a // connection error and leaves the incremental cells unapplied. @MainActor private func gapModel(version: UInt64) -> SheetModel { let model = SheetModel(client: NativeHTTP(origin: URL(string: "http://127.0.0.1:1")!)) model.page = SheetPage( Sheet: SheetSummary(ID: 1, Title: "Fixture", TabID: 1, TabName: "Sheet 1"), Rows: 10, Cols: 5, Truncated: false, MayWrite: true, MayComment: true, MayShare: false, FillSwatches: [], InkSwatches: [], NumberFormats: [], Warned: nil) model.version = version model.error = "" return model } @MainActor @Test func stampedBatchAppliesWithoutRefetch() async { let model = gapModel(version: 5) // Three typed cells (versions 6, 7, 8) plus one formula they changed. await model.received( SheetEvent( name: "cells", 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}]}"# ), source: model.grid) #expect(model.error == "", "a stamped batch attempted a refetch: \(model.error)") #expect(model.version == 8) #expect(model.cells[Coordinate(r: 2, c: 0)]?.raw == "3") #expect(model.cells[Coordinate(r: 3, c: 0)]?.display == "6") } @MainActor @Test func versionGapBeyondTheStampsStillRefetches() async { let model = gapModel(version: 5) // Two stamps ending at 9, so versions 6 and 7 were missed. await model.received( SheetEvent( name: "cells", data: #"{"version":9,"stamps":2,"cells":[{"r":0,"c":0,"raw":"1","display":"1"},{"r":1,"c":0,"raw":"2","display":"2"}]}"# ), source: model.grid) #expect(model.error != "", "a real gap was applied incrementally instead of refetched") #expect(model.version == 5) #expect(model.cells[Coordinate(r: 0, c: 0)] == nil) } @MainActor @Test func formatCellsDoNotWidenTheAcceptedJump() async { let model = gapModel(version: 5) // A format over four cells consumes no stamp; version 7 means a missed // edit, however many cells the format response lists. let cells = (0..<4).map { Cell(r: $0, c: 0, raw: "", display: "") } await model.accept(GridResponse(version: 7, cells: cells)) #expect(model.error != "") #expect(model.version == 5) } @MainActor @Test func singleStepsAndStamplessResponsesStillApply() async { let model = gapModel(version: 5) await model.accept(GridResponse(version: 6, cells: [Cell(r: 0, c: 0, raw: "x", display: "x")], stamps: 1)) // A format right after reports the next stamp: one ahead, no stamps. await model.accept(GridResponse(version: 7, cells: [Cell(r: 0, c: 0, raw: "x", display: "x")])) #expect(model.error == "") #expect(model.version == 7) #expect(model.cells[Coordinate(r: 0, c: 0)]?.raw == "x") }