krz/orgstar

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

Tests/OrgCoreTests/StructureCommandTests.swift

322a87d47526d505d903e05d14e65a5464e1468f
orgstar/Tests/OrgCoreTests/StructureCommandTests.swift history · blame · raw

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}