Tests/OrgCoreTests/PropertyTests.swift
75 lines · 3824 bytes
1import Foundation
2import Testing
3@testable import OrgCore
4
5struct PropertyTests {
6 static let texts = [
7 "* TODO Task\n",
8 "* TODO Task\nBody\n",
9 "* Task\nSCHEDULED: <2026-10-05 Mon>\nBody\n",
10 "* Task\n:PROPERTIES:\n:OWNER: alice\n:SIZE: 3\n:END:\nBody\n",
11 "* Task :tag:\n:PROPERTIES:\n:ID: x\n:END:\n** Child\n",
12 "* DONE Old\n* TODO Repeat\nSCHEDULED: <2026-10-05 Mon +1w>\n",
13 "* Task",
14 ]
15
16 @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil))
17 func matchesEmacs() throws {
18 let edits: [(any OrgCommand, String)] = [
19 (SetProperty(key: "OWNER", value: "bob"), "(org-entry-put nil \"OWNER\" \"bob\")"),
20 (SetProperty(key: "Effort", value: "1:30"), "(org-entry-put nil \"Effort\" \"1:30\")"),
21 (SetProperty(key: "LONGPROPERTYNAME", value: "v"), "(org-entry-put nil \"LONGPROPERTYNAME\" \"v\")"),
22 (SetProperty(key: "SIZE", value: ""), "(org-entry-put nil \"SIZE\" \"\")"),
23 (SetTodo(keyword: "DONE"), "(org-todo \"DONE\")"),
24 (SetTodo(keyword: "TODO"), "(org-todo \"TODO\")"),
25 (SetTodo(keyword: nil), "(org-todo 'none)"),
26 ]
27 var cases: [EmacsOracle.Case] = []
28 var ours: [(text: String, caret: Int, failure: String?)] = []
29 for text in Self.texts {
30 for heading in [(text as NSString).range(of: "Task").location, (text as NSString).range(of: "Repeat").location] where heading != NSNotFound {
31 for (command, form) in edits {
32 cases.append(EmacsOracle.Case(text: text, point: EmacsOracle.point(heading, in: text), form: frozenClock(1_789_000_000, "(progn \(form) (run-hooks 'post-command-hook))")))
33 var utc = Calendar(identifier: .gregorian)
34 utc.timeZone = TimeZone(identifier: "UTC")!
35 let context = EditContext(revision: 0, text: text, tree: OrgParser.parse(text), selection: [heading..<heading], now: Date(timeIntervalSince1970: 1_789_000_000), calendar: utc)
36 switch command.run(in: context) {
37 case .commit(let result):
38 var new = text
39 for edit in result.edits.sorted(by: { $0.range.lowerBound > $1.range.lowerBound }) { new = edit.apply(to: new) }
40 ours.append((new, heading, nil))
41 case .failed(let message): ours.append((text, heading, message))
42 default: ours.append((text, heading, "other"))
43 }
44 }
45 }
46 }
47 let emacs = try EmacsOracle.run(cases)
48 for (i, (mine, theirs)) in zip(ours, emacs).enumerated() {
49 #expect(mine.text == theirs.text, "\(cases[i].form.split(separator: "\n").last ?? "") on \(cases[i].text.debugDescription)\nours: \(mine.text.debugDescription) \(mine.failure ?? "")\nemacs: \(theirs.text.debugDescription) \(theirs.error)")
50 }
51 }
52}
53
54struct BoardTests {
55 @Test func entriesAndKeywords() {
56 let source = AgendaSource(path: "/n/a.org", text: """
57 #+TODO: TODO NEXT | DONE
58 * TODO [#A] One :work:
59 :PROPERTIES:
60 :EFFORT: 1:00
61 :END:
62 * Plain
63 * DONE Two
64 DEADLINE: <2026-10-05 Mon>
65 * ARCHIVED TODO :ARCHIVE:
66 ** TODO Hidden
67 """)
68 let all = Board.entries([source], match: "")
69 #expect(all.map(\.title) == ["One", "Two"])
70 #expect(all[0].properties["EFFORT"] == "1:00" && all[0].priority == "A" && all[0].tags == ["work"])
71 #expect(all[1].deadline == "2026-10-05 Mon")
72 #expect(Board.entries([source], match: "+work").map(\.title) == ["One"])
73 #expect(Board.keywords([source]) == ["TODO", "NEXT", "DONE"])
74 }
75}