Tests/OrgCoreTests/StructureCommandTests.swift
109 lines · 5475 bytes
1import Foundation
2import Testing
3@testable import OrgCore
4
5struct StructureCommandTests {
6 @Test func insertHeading() {
7 #expect(runCommand(InsertHeading(), "* a\nbody\n", caret: 3).text == "* a\n* \nbody\n")
8 #expect(runCommand(InsertHeading(), "* ab\n", caret: 3).text == "* a\n* b\n")
9 #expect(runCommand(InsertHeadingAfterSubtree(), "* a\nbody\n** b\n* c\n", caret: 1).text == "* a\nbody\n** b\n* \n* c\n")
10 #expect(runCommand(InsertTodoHeading(), "* TODO a\n", caret: 8).text == "* TODO a\n* TODO \n")
11 }
12
13 @Test func moveAndLevels() {
14 #expect(runCommand(MoveSubtreeDown(), "* a\nx\n* b\ny\n", caret: 0).text == "* b\ny\n* a\nx\n")
15 #expect(runCommand(MoveSubtreeUp(), "* a\n* b\n", caret: 0).failure != nil)
16 #expect(runCommand(DemoteSubtree(), "* a\n** b\n* c\n", caret: 0).text == "** a\n*** b\n* c\n")
17 #expect(runCommand(PromoteSubtree(), "* a\n", caret: 0).failure != nil)
18 }
19
20 @Test func motion() {
21 let text = "intro\n* a\n** b\n** c\n* d\n"
22 #expect(runCommand(NextHeading(), text, caret: 0).caret == 6)
23 #expect(runCommand(ForwardSameLevel(), text, caret: 10).caret == 15)
24 #expect(runCommand(BackwardSameLevel(), text, caret: 16).caret == 10)
25 #expect(runCommand(UpHeading(), text, caret: 16).caret == 6)
26 #expect(runCommand(UpHeading(), text, caret: 6).failure != nil)
27 }
28
29 @Test func nextHeadingSkipsHiddenOnes() {
30 let text = "* a\n** b\n* c\n"
31 let context = EditContext(revision: 0, text: text, tree: OrgParser.parse(text), selection: [0..<0], hidden: [3..<8])
32 guard case .commit(let result) = NextHeading().run(in: context) else {
33 Issue.record("expected a commit")
34 return
35 }
36 #expect(result.selection == [9..<9])
37 }
38}
39
40struct StructureOracleTests {
41 static let variants = [
42 "* a\nbody\n* b\nmore\n",
43 "* a\n\n* b\n\n* c\n",
44 "intro\n* a\n** b\ntext\n** c\n* d\n",
45 "* TODO a :x:\n** DONE b\n* c :y:\n",
46 "* a\n** b\n*** c\n** d\n",
47 "* a\n* b",
48 "text\nmore\n",
49 "\n* a\n\nbody\n\n* b\n",
50 "* TODO [#A] title here :t:\nbody\n",
51 "* a 😀 b\n** c\n",
52 "* P [0/1]\n** TODO a\n",
53 ]
54
55 static let commands: [(command: any OrgCommand, form: String, headingLineOnly: Bool)] = [
56 (InsertHeading(), "(org-insert-heading)", false),
57 (InsertHeadingAfterSubtree(), "(org-insert-heading-respect-content)", false),
58 (InsertTodoHeading(), "(org-insert-todo-heading nil)", false),
59 (MoveSubtreeUp(), "(org-move-subtree-up)", false),
60 (MoveSubtreeDown(), "(org-move-subtree-down)", false),
61 (PromoteSubtree(), "(org-promote-subtree)", false),
62 (DemoteSubtree(), "(org-demote-subtree)", false),
63 (NextHeading(), "(org-next-visible-heading 1)", false),
64 (PreviousHeading(), "(org-previous-visible-heading 1)", false),
65 (ForwardSameLevel(), "(org-forward-heading-same-level 1)", false),
66 (BackwardSameLevel(), "(org-backward-heading-same-level 1)", false),
67 (UpHeading(), "(outline-up-heading 1)", false),
68 ]
69
70 @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil))
71 func structureCommandsMatchEmacs() throws {
72 try compareWithEmacs(Self.variants.map { ($0, EmacsOracle.positions($0)) }, Self.commands)
73 }
74
75 @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil))
76 func structureCommandsMatchEmacsWithDoomOptions() throws {
77 try compareWithEmacs(Self.variants.map { ($0, EmacsOracle.positions($0)) }, Self.commands, options: doomOptions)
78 }
79
80 /// Real files: `ORGSTAR_ORACLE_CORPUS=<folder>` runs every structure command in files up to
81 /// 20 KB, with the caret at the start and end of up to eight heading lines per file and on
82 /// a body line, for up to 60 files.
83 @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_ORACLE_CORPUS"] != nil))
84 func structureCommandsMatchEmacsOnACorpus() throws {
85 let root = URL(fileURLWithPath: ProcessInfo.processInfo.environment["ORGSTAR_ORACLE_CORPUS"]!)
86 let files = FileManager.default.enumerator(at: root, includingPropertiesForKeys: nil)!
87 .compactMap { $0 as? URL }.filter { $0.pathExtension == "org" }.sorted { $0.path < $1.path }
88 var samples: [(String, [Int])] = []
89 for file in files where samples.count < 60 {
90 guard let text = try? String(contentsOf: file, encoding: .utf8), text.utf16.count <= 20_000, !text.contains("\r") else { continue }
91 let ns = text as NSString
92 var carets: [Int] = []
93 var location = 0
94 while location < ns.length, carets.count < 17 {
95 let line = ns.lineRange(for: NSRange(location: location, length: 0))
96 let content = ns.substring(with: line)
97 if content.hasPrefix("* ") || content.hasPrefix("** ") || content.hasPrefix("*** ") {
98 carets.append(line.location)
99 carets.append(NSMaxRange(line) - (content.hasSuffix("\n") ? 1 : 0))
100 } else if carets.count == 2 {
101 carets.append(line.location)
102 }
103 location = NSMaxRange(line)
104 }
105 if !carets.isEmpty { samples.append((text, carets)) }
106 }
107 try compareWithEmacs(samples, Self.commands)
108 }
109}