krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Tests/OrgDocumentTests/DocumentStateTests.swift

322a87d47526d505d903e05d14e65a5464e1468f
orgstar/Tests/OrgDocumentTests/DocumentStateTests.swift history · blame · raw

139 lines · 5385 bytes

  1import OrgCore
  2import Testing
  3@testable import OrgDocument
  4
  5func state(_ text: String) -> DocumentState {
  6    DocumentState(bytes: Array(text.utf8))
  7}
  8
  9struct DocumentStateTests {
 10    @Test func applyUndoRedo() throws {
 11        var doc = state("* a\nbody\n")
 12        try doc.apply([TextEdit(range: 2..<3, replacement: "TODO b")], baseRevision: 0)
 13        #expect(doc.text == "* TODO b\nbody\n")
 14        #expect(doc.revision == 1)
 15        #expect(doc.isDirty)
 16        #expect(doc.tree.green == OrgParser.parse(doc.text).green)
 17
 18        #expect(doc.undo() != nil)
 19        #expect(doc.text == "* a\nbody\n")
 20        #expect(!doc.isDirty)
 21        #expect(doc.tree.green == OrgParser.parse(doc.text).green)
 22
 23        #expect(doc.redo() != nil)
 24        #expect(doc.text == "* TODO b\nbody\n")
 25    }
 26
 27    @Test func groupedEditsAreOneUndoStep() throws {
 28        var doc = state("ab cd ef\n")
 29        try doc.apply([TextEdit(range: 0..<2, replacement: "X"), TextEdit(range: 6..<8, replacement: "YYY")], baseRevision: 0)
 30        #expect(doc.text == "X cd YYY\n")
 31        _ = doc.undo()
 32        #expect(doc.text == "ab cd ef\n")
 33    }
 34
 35    @Test func rejectsStaleOverlappingAndReadOnly() throws {
 36        var doc = state("abc\n")
 37        #expect(throws: DocumentState.EditError.staleRevision) { try doc.apply([], baseRevision: 5) }
 38        #expect(throws: DocumentState.EditError.overlappingEdits) {
 39            try doc.apply([TextEdit(range: 0..<2, replacement: ""), TextEdit(range: 1..<3, replacement: "")], baseRevision: 0)
 40        }
 41        var invalid = DocumentState(bytes: [0x61, 0xFF])
 42        #expect(throws: DocumentState.EditError.readOnly) { try invalid.apply([], baseRevision: 0) }
 43        #expect(throws: DocumentState.EditError.readOnly) { try invalid.encodedText() }
 44    }
 45
 46    @Test func newEditClearsRedo() throws {
 47        var doc = state("a\n")
 48        try doc.apply([TextEdit(range: 0..<0, replacement: "x")], baseRevision: 0)
 49        _ = doc.undo()
 50        try doc.apply([TextEdit(range: 0..<0, replacement: "y")], baseRevision: doc.revision)
 51        #expect(!doc.canRedo)
 52    }
 53
 54    @Test func keepsBOMWhenEncoding() throws {
 55        var doc = DocumentState(bytes: [0xEF, 0xBB, 0xBF] + Array("a\n".utf8))
 56        try doc.apply([TextEdit(range: 0..<1, replacement: "b")], baseRevision: 0)
 57        #expect(try doc.encodedText() == [0xEF, 0xBB, 0xBF] + Array("b\n".utf8))
 58    }
 59
 60    @Test func externalChangeReloadsCleanBuffer() {
 61        var doc = state("a\nb\n")
 62        #expect(doc.diskChanged(to: Array("a\nb\n".utf8)) == .unchanged)
 63        guard case .reloaded = doc.diskChanged(to: Array("a\nc\n".utf8)) else {
 64            Issue.record("expected a reload")
 65            return
 66        }
 67        #expect(doc.text == "a\nc\n")
 68        #expect(!doc.isDirty)
 69    }
 70
 71    @Test func externalChangeMergesIntoEditedBuffer() throws {
 72        var doc = state("a\nb\nc\n")
 73        try doc.apply([TextEdit(range: 0..<1, replacement: "A")], baseRevision: 0)
 74        guard case .merged = doc.diskChanged(to: Array("a\nb\nC\n".utf8)) else {
 75            Issue.record("expected a merge")
 76            return
 77        }
 78        #expect(doc.text == "A\nb\nC\n")
 79        #expect(doc.mergeBase == Array("a\nb\nC\n".utf8))
 80        #expect(doc.isDirty)
 81        #expect(!doc.canUndo)
 82    }
 83
 84    @Test func conflictingExternalChangeLeavesBufferAlone() throws {
 85        var doc = state("a\n")
 86        try doc.apply([TextEdit(range: 0..<1, replacement: "X")], baseRevision: 0)
 87        guard case .conflict = doc.diskChanged(to: Array("Y\n".utf8)) else {
 88            Issue.record("expected a conflict")
 89            return
 90        }
 91        #expect(doc.text == "X\n")
 92        #expect(doc.mergeBase == Array("a\n".utf8))
 93    }
 94}
 95
 96struct ViewStateTests {
 97    @Test func mapsSelectionAndFolds() {
 98        let view = ViewState(selection: [2..<6], folds: [0, 10])
 99        let mapped = view.mapped(through: [TextEdit(range: 1..<1, replacement: "xx"), TextEdit(range: 8..<9, replacement: "")])
100        #expect(mapped.selection == [4..<8])
101        #expect(mapped.folds == [0, 11])
102    }
103
104    @Test func offsetsInsideAReplacementMoveToItsEnd() {
105        let edits = [TextEdit(range: 2..<5, replacement: "ab")]
106        #expect(mapOffset(2, edits) == 2)
107        #expect(mapOffset(3, edits) == 4)
108        #expect(mapOffset(5, edits) == 4)
109        #expect(mapOffset(6, edits) == 5)
110        #expect(mapOffset(2, [TextEdit(range: 2..<2, replacement: "ab")]) == 4)
111    }
112
113    @Test func pruneDropsFoldsThatAreNoLongerHeadings() {
114        let tree = OrgParser.parse("* a\ntext\n* b\n")
115        #expect(ViewState(folds: [0, 4, 9]).pruned(to: tree).folds == [0, 9])
116    }
117}
118
119struct CommandRunTests {
120    @Test func runAppliesEditsAsOneStep() throws {
121        var doc = state("* a\n")
122        let step = try doc.run(TodoCycle(), selection: [2..<2])
123        guard case .commit(let result) = step else {
124            Issue.record("expected a commit")
125            return
126        }
127        #expect(doc.text == "* TODO a\n")
128        #expect(result.selection == [7..<7])
129        #expect(doc.tree.green == OrgParser.parse(doc.text).green)
130        _ = doc.undo()
131        #expect(doc.text == "* a\n")
132    }
133
134    @Test func failuresChangeNothing() throws {
135        var doc = state("text\n")
136        #expect(try doc.run(TodoCycle(), selection: [0..<0]) == .failed("Before first headline"))
137        #expect(doc.revision == 0)
138    }
139}