Tests/OrgCoreTests/DateCommandTests.swift
84 lines · 5241 bytes
5 symbols in this file
1import Foundation
2import Testing
3@testable import OrgCore
4
5struct DateCommandTests {
6 static let oracle = ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil
7 static let seconds = 1_789_000_000
8
9 static func run(_ command: any OrgCommand, _ text: String, caret: Int, answers: [String: String], options: EditingOptions = .org, lastCommand: String? = nil) -> (text: String, message: String?) {
10 var utc = Calendar(identifier: .gregorian)
11 utc.timeZone = TimeZone(identifier: "UTC")!
12 var answers = answers
13 while true {
14 let context = EditContext(revision: 0, text: text, tree: OrgParser.parse(text), selection: [caret..<caret], now: Date(timeIntervalSince1970: Double(seconds)),
15 calendar: utc, answers: answers, options: options, lastCommand: lastCommand)
16 switch command.run(in: context) {
17 case .commit(let result):
18 var new = text
19 for edit in result.edits.sorted(by: { $0.range.lowerBound > $1.range.lowerBound }) { new = edit.apply(to: new) }
20 let message = result.effects.compactMap { if case .message(let m) = $0 { m } else { nil } }.first
21 return (new, message)
22 case .prompt(let prompt) where prompt.key == "note" && answers["note"] == nil:
23 answers["note"] = "moved"
24 case .failed(let message): return (text, "failed: \(message)")
25 default: return (text, "other")
26 }
27 }
28 }
29
30 @Test(.enabled(if: oracle))
31 func logsReschedulesLikeOrg() throws {
32 let texts = ["* Task\nSCHEDULED: <2026-10-05 Mon>\n", "* Task\nDEADLINE: <2026-10-05 Mon> SCHEDULED: <2026-10-01 Thu>\nbody\n", "* Task\n"]
33 let actions: [(any OrgCommand, String, [String: String])] = [
34 (SetPlanning(deadline: false), "(org-schedule nil \"2026-10-10\")", ["date": "2026-10-10"]),
35 (SetPlanning(deadline: true), "(org-deadline nil \"2026-10-12\")", ["date": "2026-10-12"]),
36 (RemovePlanning(deadline: false), "(org-schedule '(4))", [:]),
37 (RemovePlanning(deadline: true), "(org-deadline '(4))", [:]),
38 ]
39 for (how, drawer) in [(LogHow.time, nil as String?), (.note, "LOGBOOK")] {
40 var cases: [EmacsOracle.Case] = []
41 var ours: [String] = []
42 for text in texts {
43 for (command, form, answers) in actions {
44 let lisp = """
45 (let ((org-log-reschedule '\(how.rawValue)) (org-log-redeadline '\(how.rawValue)) (org-log-into-drawer \(drawer.map { "\"\($0)\"" } ?? "nil")) (b (current-buffer)))
46 \(form)
47 (run-hooks 'post-command-hook)
48 (when (get-buffer "*Org Note*")
49 (with-current-buffer "*Org Note*" (goto-char (point-max)) (insert "moved") (org-store-log-note)))
50 (set-buffer b))
51 """
52 cases.append(EmacsOracle.Case(text: text, point: 3, form: frozenClock(Self.seconds, lisp)))
53 let options = EditingOptions(logIntoDrawer: drawer, logReschedule: how, logRedeadline: how)
54 ours.append(Self.run(command, text, caret: 2, answers: answers, options: options).text)
55 }
56 }
57 for (i, (mine, theirs)) in zip(ours, try EmacsOracle.run(cases)).enumerated() {
58 #expect(mine == theirs.text, "\(how) \(cases[i].form.split(separator: "\n")[2]) on \(cases[i].text.debugDescription)\nours: \(mine.debugDescription)\nemacs: \(theirs.text.debugDescription) \(theirs.error)")
59 }
60 }
61 }
62
63 @Test(.enabled(if: oracle))
64 func insertsRangesLikeOrgTimestamp() throws {
65 let text = "Meet <2026-10-05 Mon> then\n"
66 let caret = (text as NSString).range(of: " then").location
67 let emacs = try EmacsOracle.run([EmacsOracle.Case(text: text, point: EmacsOracle.point(caret, in: text), form: frozenClock(Self.seconds, """
68 (let ((last-command 'org-timestamp) (this-command 'org-timestamp) (ord (symbol-function 'org-read-date)))
69 (cl-letf (((symbol-function 'org-read-date) (lambda (&optional with-time to-time _from prompt &rest _) (funcall ord with-time to-time "+2d" prompt))))
70 (org-timestamp nil)))
71 """))])
72 let ours = Self.run(InsertTimestamp(), text, caret: caret, answers: ["date": "+2d"], lastCommand: "org.timestamp.active")
73 #expect(ours.text == emacs[0].text)
74 }
75
76 @Test(.enabled(if: oracle))
77 func evaluatesRangesLikeOrg() throws {
78 let lines = ["<2026-10-05 Mon>--<2026-10-08 Thu>", "[2026-10-05 Mon 10:00]--[2026-10-07 Wed 12:30]", "x <2026-10-05 Mon 10:00>--<2026-10-05 Mon 10:45> y", "<2026-10-05 Mon>--<2026-10-05 Mon>"]
79 let forms = lines.map { "(progn (erase-buffer) (insert \(lispString($0))) (goto-char 1) (org-evaluate-time-range))" }
80 let emacs = try EmacsOracle.evaluate("", "(list \(forms.joined(separator: " ")))")
81 let ours = lines.map { Self.run(EvaluateTimeRange(), $0, caret: 0, answers: [:]).message ?? "nil" }
82 #expect(ours == emacs)
83 }
84}