import Foundation import RastrilloApple import Testing @testable import SheetsKit @Test func coordinatesAndReverseRanges() { #expect(Coordinate.parse("aa151") == Coordinate(r: 150, c: 26)) #expect(Coordinate.parse("A0") == nil) #expect(Coordinate.parse("A1junk") == nil) #expect(CellRange.parse("B3:A1")?.count == 6) #expect(CellRange.parse("A1:") == nil) } @Test func spreadsheetClipboardRoundTrip() { let values = [["a\tb", "line\nnext", "say \"hello\""], ["", "=A1+$B$2", ""]] #expect(TSV.decode(TSV.encode(values)) == values) #expect(TSV.decode("a\tb\r\nc\td\r\n") == [["a", "b"], ["c", "d"]]) } @MainActor @Test func fillCopiesAnchorWithFormulaOriginAndBounds() { let model = SheetModel(client: NativeHTTP(origin: URL(string: "http://localhost")!)) model.cells[Coordinate(r: 0, c: 0)] = Cell(r: 0, c: 0, raw: "=B1", display: "0") let down = model.fillWrites(down: true) #expect(down.count == 1) #expect(down.first?.r == 1 && down.first?.c == 0) #expect(down.first?.raw == "=B1") #expect(down.first?.fromR == 0 && down.first?.fromC == 0) let right = model.fillWrites(down: false) #expect(right.first?.r == 0 && right.first?.c == 1) model.selection = CellRange(Coordinate(r: 0, c: 0), Coordinate(r: 1, c: 1)) #expect(model.fillWrites(down: true).count == 3) #expect( model.fillWrites(down: true).allSatisfy { $0.raw == "=B1" && $0.fromR == 0 && $0.fromC == 0 }) let end = Coordinate(r: model.rows - 1, c: model.cols - 1) model.selection = CellRange(end, end) #expect(model.fillWrites(down: true).isEmpty) #expect(model.fillWrites(down: false).isEmpty) model.copied = [down] model.copiedText = "=B1" model.clearCopyProvenance() #expect(model.copied == nil && model.copiedText.isEmpty) } @Test func streamedEventsRespectBoundaries() async { let parser = SheetEventParser() #expect(await parser.receive("event: cells") == nil) #expect(await parser.receive("data: {\"version\":1}") == nil) let event = await parser.receive("") #expect(event?.name == "cells") #expect(event?.data == "{\"version\":1}") } @MainActor @Test func formulaDraftTracksSelectionSynchronously() { let model = SheetModel(client: NativeHTTP(origin: URL(string: "http://localhost")!)) let first = Coordinate(r: 0, c: 0) model.cells[first] = Cell(r: 0, c: 0, raw: "=2+3", display: "5") model.select(first) #expect(model.draft == "=2+3") #expect(!model.dirty) model.draft = "=7*6" model.select(Coordinate(r: 1, c: 0)) #expect(model.selection.start == first) #expect(model.draft == "=7*6") model.discard() #expect(model.draft == "=2+3") #expect(!model.dirty) model.select(Coordinate(r: 1, c: 0)) #expect(model.draft.isEmpty) #expect(!model.dirty) } @MainActor @Test func olderLiveResponseCannotReplaceCurrentCell() async { let model = SheetModel(client: NativeHTTP(origin: URL(string: "http://localhost")!)) model.version = 2 model.cells[Coordinate(r: 0, c: 0)] = Cell(r: 0, c: 0, raw: "new", display: "new") await model.accept( GridResponse(version: 1, cells: [Cell(r: 0, c: 0, raw: "old", display: "old")])) #expect(model.cells[Coordinate(r: 0, c: 0)]?.raw == "new") } @MainActor @Test func peerEditsUpdateCleanFormulaButPreserveDraft() async { let model = SheetModel(client: NativeHTTP(origin: URL(string: "http://localhost")!)) let first = Coordinate(r: 0, c: 0) model.cells[first] = Cell(r: 0, c: 0, raw: "=2+3", display: "5") model.select(first) await model.accept(GridResponse(version: 1, cells: [Cell(r: 0, c: 0, raw: "=3+4", display: "7")])) #expect(model.draft == "=3+4") #expect(!model.dirty) model.draft = "my pending edit" await model.accept(GridResponse(version: 2, cells: [Cell(r: 0, c: 0, raw: "=4+5", display: "9")])) #expect(model.cells[first]?.display == "9") #expect(model.draft == "my pending edit") #expect(model.dirty) } @MainActor @Test func pendingBatchCannotBeReplacedByFormulaCommit() async { let model = SheetModel(client: NativeHTTP(origin: URL(string: "http://localhost")!)) model.failedWrites = [ CellWrite(r: 1, c: 0, raw: "first pending"), CellWrite(r: 2, c: 0, raw: "second pending"), ] model.draft = "a new edit" await model.commit() #expect(model.failedWrites.map(\.raw) == ["first pending", "second pending"]) #expect(model.dirty) #expect(model.error.contains("Retry or discard")) model.discard() model.rangeText = "B2:C3" model.selectRange() #expect(model.selection.start == Coordinate(r: 0, c: 0)) #expect(model.failedWrites.count == 2) } @Test func workbookExportNamesUseSafeDocumentTitles() { let url = URL(fileURLWithPath: "/tmp/private-staging.xlsx") #expect(WorkbookExport(url: url, title: "Budget 2026").filename == "Budget 2026.xlsx") #expect(WorkbookExport(url: url, title: "../Budget:2026\n").filename == "-Budget-2026.xlsx") #expect(WorkbookExport(url: url, title: "...").filename == "Workbook.xlsx") #expect(WorkbookExport(url: url, title: String(repeating: "🧪", count: 100)).filename.utf8.count <= 205) }