krz/orgstar

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

Tests/OrgCoreTests/TodoLoggingTests.swift

2fa201330f61c801f777baa3f2943d49d8758cda
orgstar/Tests/OrgCoreTests/TodoLoggingTests.swift history · blame · raw

90 lines · 4969 bytes

 1import Foundation
 2import Testing
 3@testable import OrgCore
 4
 5struct TodoLoggingTests {
 6    static let texts = [
 7        "#+TODO: TODO(t) WAIT(w@/!) | DONE(d!) CANCELED(c@)\n* TODO Task\nBody\n",
 8        "* TODO Task\nSCHEDULED: <2026-10-05 Mon>\n:PROPERTIES:\n:X: 1\n:END:\nBody\n",
 9        "* TODO Task\n:LOGBOOK:\n- State \"TODO\"       from              [2026-01-01 Thu 10:00]\n:END:\n",
10        "* DONE Task\nCLOSED: [2026-01-01 Thu 10:00]\n",
11        "#+STARTUP: lognotedone logdrawer\n* TODO Task\n",
12        "#+begin_example\n#+STARTUP: lognotedone logdrawer\n#+end_example\n* TODO Task\n",
13        "* TODO Task\n:PROPERTIES:\n:LOGGING: DONE(!) logrepeat\n:LOG_INTO_DRAWER: NOTES\n:END:\n",
14        "* TODO Task\nSCHEDULED: <2026-10-05 Mon +1w>\n",
15        "* TODO Task\n- item one\n",
16        "* TODO Task\n** Child\n",
17        "* TODO Task",
18    ]
19
20    static let options: [(LogHow?, String?)] = [(nil, nil), (.time, nil), (.time, "LOGBOOK"), (.note, nil), (.note, "LOGBOOK")]
21
22    static let note = "Called back\nsecond line"
23
24    @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil))
25    func logsLikeOrgTodo() throws {
26        let changes: [(any OrgCommand, String)] = [
27            (TodoCycle(), "(org-todo)"),
28            (SetTodo(keyword: "DONE"), "(org-todo \"DONE\")"),
29            (SetTodo(keyword: "TODO"), "(org-todo \"TODO\")"),
30            (SetTodo(keyword: nil), "(org-todo 'none)"),
31            (SetTodo(keyword: "WAIT"), "(org-todo \"WAIT\")"),
32            (SetTodo(keyword: "CANCELED"), "(org-todo \"CANCELED\")"),
33        ]
34        var utc = Calendar(identifier: .gregorian)
35        utc.timeZone = TimeZone(identifier: "UTC")!
36        let now = Date(timeIntervalSince1970: 1_789_000_000)
37        var cases: [EmacsOracle.Case] = []
38        var ours: [String] = []
39        for text in Self.texts {
40            let heading = (text as NSString).range(of: "Task").location
41            for (done, drawer) in Self.options {
42                let options = EditingOptions(logDone: done, logIntoDrawer: drawer, fastTodoSelection: false)
43                for (command, form) in changes {
44                    let lisp = """
45                        (let ((b (current-buffer)) (org-use-fast-todo-selection nil))
46                          ;; As the user's settings, under the file's #+STARTUP.
47                          (setq-default org-log-done \(done.map { "'" + $0.rawValue } ?? "nil") org-log-into-drawer \(drawer.map { "\"\($0)\"" } ?? "nil"))
48                          (org-mode)
49                          \(form)
50                          (run-hooks 'post-command-hook)
51                          (when (get-buffer "*Org Note*")
52                            (with-current-buffer "*Org Note*" (goto-char (point-max)) (insert \(lispString(Self.note))) (org-store-log-note)))
53                          (set-buffer b))
54                        """
55                    cases.append(EmacsOracle.Case(text: text, point: EmacsOracle.point(heading, in: text), form: frozenClock(1_789_000_000, lisp)))
56                    var answers: [String: String] = [:]
57                    var result = text
58                    while true {
59                        let context = EditContext(revision: 0, text: text, tree: OrgParser.parse(text), selection: [heading..<heading], now: now, calendar: utc, answers: answers, options: options)
60                        switch command.run(in: context) {
61                        case .commit(let r):
62                            for edit in r.edits.sorted(by: { $0.range.lowerBound > $1.range.lowerBound }) { result = edit.apply(to: result) }
63                        case .prompt(let prompt) where prompt.key == "note" && answers["note"] == nil:
64                            answers["note"] = Self.note
65                            continue
66                        default: break
67                        }
68                        break
69                    }
70                    ours.append(result)
71                }
72            }
73        }
74        let emacs = try EmacsOracle.run(cases)
75        for (i, (mine, theirs)) in zip(ours, emacs).enumerated() {
76            #expect(mine == theirs.text, "\(cases[i].form.split(separator: "\n")[1...2].joined()) on \(cases[i].text.debugDescription)\nours:  \(mine.debugDescription)\nemacs: \(theirs.text.debugDescription) \(theirs.error)")
77        }
78    }
79
80    @Test func asksForTheNote() {
81        let text = "#+TODO: TODO(t) WAIT(w@/!) | DONE(d!)\n* TODO Task\n"
82        let heading = (text as NSString).range(of: "Task").location
83        let context = EditContext(revision: 0, text: text, tree: OrgParser.parse(text), selection: [heading..<heading])
84        #expect(SetTodo(keyword: "WAIT").run(in: context) == .prompt(Prompt(key: "note", message: "Note for state change from \"TODO\" to \"WAIT\":")))
85    }
86}
87
88func lispString(_ s: String) -> String {
89    "\"" + s.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"") + "\""
90}