Tests/OrgCoreTests/ElectricPairTests.swift
97 lines · 5008 bytes
4 symbols in this file
1import Foundation
2import Testing
3@testable import OrgCore
4
5/// Keys typed with `electric-pair-mode` in an Org buffer, against Emacs.
6struct ElectricPairTests {
7 struct Typing {
8 let text: String
9 let point: Int
10 var mark: Int?
11 let keys: [String]
12 }
13
14 static let cases: [Typing] = [
15 Typing(text: "", point: 0, keys: ["("]),
16 Typing(text: "foo", point: 3, keys: ["(", "x", ")"]),
17 Typing(text: ")", point: 0, keys: ["("]),
18 Typing(text: "(", point: 1, keys: [")"]),
19 Typing(text: "", point: 0, keys: ["\"", "a", "\""]),
20 Typing(text: "a \"b", point: 4, keys: ["\""]),
21 Typing(text: "(foo )", point: 4, keys: [")"]),
22 Typing(text: "x", point: 1, keys: ["<", ">"]),
23 Typing(text: "", point: 0, keys: ["[", "[", "a", "]", "]"]),
24 Typing(text: "{}", point: 1, keys: ["}"]),
25 Typing(text: "(())", point: 2, keys: [")", ")"]),
26 Typing(text: "[(])", point: 2, keys: [")"]),
27 Typing(text: "()", point: 1, keys: ["DEL"]),
28 Typing(text: "(x)", point: 2, keys: ["DEL"]),
29 Typing(text: "\"\"", point: 1, keys: ["DEL"]),
30 Typing(text: "()", point: 1, keys: ["RET"]),
31 Typing(text: "word", point: 4, mark: 0, keys: ["("]),
32 Typing(text: "word", point: 4, mark: 0, keys: [")"]),
33 Typing(text: "word", point: 4, mark: 0, keys: ["\""]),
34 Typing(text: "say ", point: 4, keys: ["“", "hi", "”"]),
35 Typing(text: "\"a b\"", point: 2, keys: ["("]),
36 Typing(text: "\"a b\"", point: 2, keys: ["<"]),
37 Typing(text: "* Heading\nit", point: 12, keys: ["'", "s"]),
38 Typing(text: "a (b", point: 4, keys: [")"]),
39 Typing(text: "a b)", point: 2, keys: ["("]),
40 Typing(text: "(a\n )", point: 2, keys: [")"]),
41 Typing(text: "word", point: 0, keys: ["\""]),
42 Typing(text: "say \"x\" and ", point: 12, keys: ["\"", "y", "\""]),
43 Typing(text: "- item (see [[x]]", point: 17, keys: [")"]),
44 Typing(text: "<2026-10-06 Tue>", point: 16, keys: ["DEL"]),
45 Typing(text: "f(\"(\")", point: 4, keys: [")"]),
46 ]
47
48 /// The keys typed into our side: `ElectricPairKey` where it applies, otherwise as the
49 /// text view types them.
50 static func ours(_ c: Typing) -> (text: String, point: Int) {
51 var text = c.text
52 var selection = (c.mark ?? c.point)..<c.point
53 if let mark = c.mark, mark > c.point { selection = c.point..<mark }
54 var options = EditingOptions.org
55 options.electricPair = true
56 let units = c.keys.flatMap { key in key == "DEL" || key == "RET" ? [key] : key.map { String($0) } }
57 for key in units {
58 let context = EditContext(revision: 0, text: text, tree: OrgParser.parse(text), selection: [selection], options: options)
59 let command = ElectricPairKey(key: key)
60 if command.applies(in: context), case .commit(let result) = command.run(in: context) {
61 for edit in result.edits.sorted(by: { $0.range.lowerBound > $1.range.lowerBound }) { text = edit.apply(to: text) }
62 selection = result.selection!.first!
63 continue
64 }
65 let ns = text as NSString
66 let caret = selection.upperBound
67 switch key {
68 case "DEL":
69 text = ns.replacingCharacters(in: NSRange(location: caret - 1, length: 1), with: "")
70 selection = caret - 1..<caret - 1
71 default:
72 let typed = key == "RET" ? "\n" : key
73 text = ns.replacingCharacters(in: NSRange(location: selection.lowerBound, length: selection.count), with: typed)
74 selection = selection.lowerBound + 1..<selection.lowerBound + 1
75 }
76 }
77 return (text, selection.upperBound)
78 }
79
80 @Test func typesLikeEmacs() throws {
81 let cases = Self.cases.map { c in
82 let keys = c.keys.map { key in
83 key == "DEL" || key == "RET" ? "(execute-kbd-macro (kbd \"\(key)\"))"
84 : key.map { "(execute-kbd-macro \(lispString(String($0))))" }.joined(separator: " ")
85 }.joined(separator: " ")
86 let region = c.mark.map { "(set-mark \(EmacsOracle.point($0, in: c.text))) (activate-mark)" } ?? ""
87 return EmacsOracle.Case(text: c.text, point: EmacsOracle.point(c.point, in: c.text), form: """
88 (progn (switch-to-buffer (current-buffer)) (electric-pair-local-mode 1) (transient-mark-mode 1) \(region) \(keys))
89 """)
90 }
91 for (c, theirs) in zip(Self.cases, try EmacsOracle.run(cases)) {
92 let ours = Self.ours(c)
93 #expect(ours.text == theirs.text && EmacsOracle.point(ours.point, in: ours.text) == theirs.point,
94 "\(c.text.debugDescription) at \(c.point) keys \(c.keys)\nours: \(ours.text.debugDescription) \(ours.point)\nemacs: \(theirs.text.debugDescription) \(theirs.point) \(theirs.error)")
95 }
96 }
97}