krz/orgstar

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

Tests/OrgCoreTests/ToggleCommandTests.swift

191ddb506f3a91b93c755ba6e8123a4e8ac4f90d
orgstar/Tests/OrgCoreTests/ToggleCommandTests.swift history · blame · raw

101 lines · 4828 bytes

  1import Foundation
  2import Testing
  3@testable import OrgCore
  4
  5struct ToggleCommandTests {
  6    static let oracle = ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil
  7
  8    static let text = """
  9        Intro line
 10        # a comment
 11        * TODO [#A] Task :tag:
 12        SCHEDULED: <2026-10-05 Mon>
 13        :PROPERTIES:
 14        :X: 1
 15        :END:
 16        Body text
 17          indented body
 18        - plain item
 19        - [ ] open box
 20        - [X] done box
 21          - nested item
 22        ** COMMENT Commented
 23        *** Deep
 24        1. first
 25        2) second
 26        * DONE Closed one
 27        last
 28
 29        """
 30
 31    /// Runs `command` with `selection` (a caret when empty) and returns the text and caret.
 32    static func ours(_ command: any OrgCommand, _ text: String, _ selection: Range<Int>) -> (String, Int) {
 33        let context = EditContext(revision: 0, text: text, tree: OrgParser.parse(text), selection: [selection], options: .org)
 34        guard case .commit(let result) = command.run(in: context) else { return (text, selection.upperBound) }
 35        var new = text
 36        for edit in result.edits.sorted(by: { $0.range.lowerBound > $1.range.lowerBound }) { new = edit.apply(to: new) }
 37        return (new, result.selection?.first?.lowerBound ?? selection.upperBound)
 38    }
 39
 40    static func compare(_ command: any OrgCommand, _ form: String, regions: [(String, String)] = []) throws {
 41        let text = Self.text
 42        let ns = text as NSString
 43        var cases: [EmacsOracle.Case] = []
 44        var mine: [(String, Int)] = []
 45        var labels: [String] = []
 46        // Each line's start and a point inside it.
 47        var lineStart = 0
 48        while lineStart < ns.length {
 49            let line = ns.lineRange(for: NSRange(location: lineStart, length: 0))
 50            for offset in Set([line.location, min(line.location + 3, NSMaxRange(line) - 1)]).sorted() {
 51                cases.append(EmacsOracle.Case(text: text, point: EmacsOracle.point(offset, in: text), form: form))
 52                mine.append(ours(command, text, offset..<offset))
 53                labels.append("at \(offset)")
 54            }
 55            lineStart = NSMaxRange(line)
 56        }
 57        for (from, to) in regions {
 58            let start = ns.range(of: from).location
 59            let end = NSMaxRange(ns.range(of: to))
 60            cases.append(EmacsOracle.Case(text: text, point: EmacsOracle.point(end, in: text), form: "(progn (transient-mark-mode 1) (set-mark \(EmacsOracle.point(start, in: text))) (activate-mark) \(form))"))
 61            mine.append(ours(command, text, start..<end))
 62            labels.append("region \(from.debugDescription)…\(to.debugDescription)")
 63        }
 64        let emacs = try EmacsOracle.run(cases)
 65        for (i, (ours, theirs)) in zip(mine, emacs).enumerated() {
 66            let expectedText = theirs.error.isEmpty ? theirs.text : text
 67            #expect(ours.0 == expectedText, "\(form) \(labels[i])\nours:  \(ours.0.debugDescription)\nemacs: \(theirs.text.debugDescription) \(theirs.error)")
 68            if ours.0 == expectedText, theirs.error.isEmpty {
 69                #expect(ours.1 == EmacsOracle.offset(theirs.point, in: theirs.text), "\(form) \(labels[i]) caret")
 70            }
 71        }
 72    }
 73
 74    @Test(.enabled(if: oracle))
 75    func commentsLikeOrg() throws {
 76        try Self.compare(ToggleComment(), "(org-toggle-comment)")
 77    }
 78
 79    @Test(.enabled(if: oracle))
 80    func togglesHeadingsLikeOrg() throws {
 81        try Self.compare(ToggleHeading(), "(org-toggle-heading)", regions: [("Body text", "indented body"), ("* TODO", "Body"), ("1. first", "2) second"), ("Intro", "comment")])
 82    }
 83
 84    @Test(.enabled(if: oracle))
 85    func togglesItemsLikeOrgCtrlCMinus() throws {
 86        try Self.compare(ToggleItem(), "(org-ctrl-c-minus)", regions: [("Body text", "indented body"), ("- plain", "open box"), ("** COMMENT", "Deep"), ("* DONE", "last")])
 87    }
 88
 89    @Test(.enabled(if: oracle))
 90    func marksSubtreesLikeOrg() throws {
 91        let text = Self.text
 92        let offsets = [(text as NSString).range(of: "Body").location, (text as NSString).range(of: "*** Deep").location + 2]
 93        let emacs = try EmacsOracle.evaluate(text, "(mapcar (lambda (p) (goto-char p) (transient-mark-mode 1) (org-mark-subtree) (format \"%d %d\" (1- (region-beginning)) (1- (region-end)))) '(\(offsets.map { String(EmacsOracle.point($0, in: text)) }.joined(separator: " "))))")
 94        let ours = offsets.map { offset -> String in
 95            let context = EditContext(revision: 0, text: text, tree: OrgParser.parse(text), selection: [offset..<offset])
 96            guard case .commit(let result) = MarkSubtree().run(in: context), let range = result.selection?.first else { return "none" }
 97            return "\(range.lowerBound) \(range.upperBound)"
 98        }
 99        #expect(ours == emacs)
100    }
101}