Tests/OrgDocumentTests/DocumentStateTests.swift
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}