krz/orgstar

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

Tests/OrgCoreTests/ReadDateTests.swift

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

79 lines · 4049 bytes

 1import Foundation
 2import Testing
 3@testable import OrgCore
 4
 5/// Emacs Lisp that runs `body` with the clock stopped at `seconds` (UTC in the oracle).
 6func frozenClock(_ seconds: Int, _ body: String) -> String {
 7    """
 8    (cl-letf* ((frozen (seconds-to-time \(seconds)))
 9               (odt (symbol-function 'decode-time))
10               (oft (symbol-function 'format-time-string))
11               (otl (symbol-function 'time-less-p))
12               (ots (symbol-function 'time-subtract))
13               (ota (symbol-function 'time-add))
14               (oflt (symbol-function 'float-time))
15               ((symbol-function 'current-time) (lambda () frozen))
16               ((symbol-function 'org-today) (lambda () (time-to-days frozen)))
17               ((symbol-function 'time-less-p) (lambda (a b) (funcall otl (or a frozen) (or b frozen))))
18               ((symbol-function 'time-subtract) (lambda (a b) (funcall ots (or a frozen) (or b frozen))))
19               ((symbol-function 'time-add) (lambda (a b) (funcall ota (or a frozen) (or b frozen))))
20               ((symbol-function 'float-time) (lambda (&optional time) (funcall oflt (or time frozen))))
21               ((symbol-function 'decode-time) (lambda (&optional time zone form) (funcall odt (or time frozen) zone form)))
22               ((symbol-function 'format-time-string) (lambda (f &optional time zone) (funcall oft f (or time frozen) zone))))
23      \(body))
24    """
25}
26
27struct ReadDateTests {
28    /// Thu 2026-09-10 00:26 UTC.
29    static let now = Date(timeIntervalSince1970: 1_789_000_000)
30    static var utc: Calendar {
31        var calendar = Calendar(identifier: .gregorian)
32        calendar.timeZone = TimeZone(identifier: "UTC")!
33        return calendar
34    }
35
36    static func format(_ answer: ReadDate.Answer) -> String {
37        let c = answer.components(utc)
38        let date = String(format: "%04d-%02d-%02d", c.year!, c.month!, c.day!)
39        return (answer.timeGiven ? date + String(format: " %02d:%02d", c.hour!, c.minute!) : date) + "|" + (answer.endTime ?? "nil")
40    }
41
42    static let inputs = [
43        "", ".", "+0", "+1", "+2d", "-3d", "+1w", "+2m", "-1y", "++3", "fri", "+fri", "-fri", "+2fri", "mon",
44        "2026-10-07", "2026-10-7", "10-7", "7.10.", "7.10.2027", "10/7", "10/7/27", "12/31",
45        "3", "15", "sep 15", "15 sep", "jan", "dec 25", "2027",
46        "10:00", "9:30", "10am", "3pm", "12am", "12:30pm", "10h", "10h30", "h45",
47        "10:00-11:30", "10:00+1:30", "fri 10:00", "2026-10-07 14:15", "+2d 9:00", "+3h",
48        "w42", "2027-w01", "w3-5", "tomorrow", "nonsense",
49    ]
50
51    @Test func examples() {
52        let answer = ReadDate.analyze("fri 10:00", default: Self.now, now: Self.now, calendar: Self.utc)
53        #expect(Self.format(answer) == "2026-09-11 10:00|nil")
54        #expect(Self.format(ReadDate.analyze("10:00-11:30", default: Self.now, now: Self.now, calendar: Self.utc)) == "2026-09-10 10:00|11:30")
55    }
56
57    @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil))
58    func readDateMatchesEmacs() throws {
59        try #require(EmacsOracle.isAvailable)
60        let cases = Self.inputs.map { input in
61            EmacsOracle.Case(text: "", point: 1, form: frozenClock(1_789_000_000, """
62                (eval '(let ((org-time-was-given nil) (org-end-time-was-given nil))
63                         (let ((d (org-read-date nil nil "\(input)")))
64                           (insert d "|" (or org-end-time-was-given "nil"))))
65                      nil)
66                """))
67        }
68        var mismatches = 0
69        for (input, emacs) in zip(Self.inputs, try EmacsOracle.run(cases)) {
70            let ours = Self.format(ReadDate.analyze(input, default: Self.now, now: Self.now, calendar: Self.utc))
71            let theirs = emacs.error.isEmpty ? emacs.text : "error: \(emacs.error)"
72            if ours != theirs {
73                mismatches += 1
74                Issue.record("\(input.debugDescription): ours \(ours), emacs \(theirs)")
75            }
76        }
77        #expect(mismatches == 0)
78    }
79}