Tests/OrgCoreTests/ColumnViewTests.swift
86 lines · 3362 bytes
1import Foundation
2import Testing
3@testable import OrgCore
4
5struct ColumnViewTests {
6 static let oracle = ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil
7
8 static let text = """
9 #+COLUMNS: %25ITEM %TODO %3PRIORITY %Effort{:} %Cost{+;%.1f} %Done{X/} %TAGS
10 * Project :work:
11 :PROPERTIES:
12 :Effort: 9:00
13 :END:
14 ** TODO [#A] Design <<target>> [1/2]
15 :PROPERTIES:
16 :Effort: 2:30
17 :Cost: 10
18 :Done: [X]
19 :END:
20 ** DONE Build | it
21 DEADLINE: <2026-10-01 Thu> SCHEDULED: <2026-09-20 Sun>
22 :PROPERTIES:
23 :Effort: 1d
24 :Cost: 2.5
25 :Done: [ ]
26 :END:
27 *** Sub task :home:
28 :PROPERTIES:
29 :Effort: 0:45
30 :END:
31 ** Archived :ARCHIVE:
32 :PROPERTIES:
33 :Effort: 5:00
34 :END:
35 * COMMENT Hidden
36 * Other
37 :PROPERTIES:
38 :COLUMNS: %ITEM(Task) %SCHEDULED %DEADLINE %ALLTAGS %Cost{max} %Cost{mean}
39 :END:
40 ** One thing
41 :PROPERTIES:
42 :Cost: 4
43 :END:
44 ** Two things
45 SCHEDULED: <2026-11-01 Sun 10:00>
46 :PROPERTIES:
47 :Cost: 7
48 :END:
49 """ + "\n"
50
51 @Test(.enabled(if: oracle))
52 func writesColumnViewBlocksLikeOrg() throws {
53 let blocks = [
54 ("Project", "#+BEGIN: columnview :hlines 1 :id local\n#+END:\n"),
55 ("Other", "#+BEGIN: columnview :hlines 1 :id local\n#+END:\n"),
56 ("Project", "#+BEGIN: columnview :id global :indent t :hlines t\n#+END:\n"),
57 ("Project", "#+BEGIN: columnview :id local :maxlevel 2 :skip-empty-rows t :exclude-tags (\"home\")\n#+END:\n"),
58 ("Project", "#+BEGIN: columnview :id local :format \"%ITEM %Effort{:max} %Effort{:mean} %Cost{$}\"\n| x |\n#+TBLFM: $4=$4*2\n#+END:\n"),
59 ]
60 var cases: [EmacsOracle.Case] = []
61 var texts: [String] = []
62 for (heading, block) in blocks {
63 // The block goes under the heading's drawer.
64 let ns = Self.text as NSString
65 let start = ns.range(of: "* " + heading).location
66 let drawerEnd = ns.range(of: ":END:\n", range: NSRange(location: start, length: ns.length - start))
67 let at = NSMaxRange(drawerEnd)
68 let text = ns.replacingCharacters(in: NSRange(location: at, length: 0), with: block)
69 texts.append(text)
70 cases.append(EmacsOracle.Case(text: text, point: EmacsOracle.point(at, in: text), form: "(org-update-dblock)"))
71 }
72 let emacs = try EmacsOracle.run(cases)
73 for (i, text) in texts.enumerated() {
74 let at = Int(cases[i].point) - 1
75 let ours = runCommand(UpdateDynamicBlock(), text, caret: EmacsOracle.offset(at + 1, in: text))
76 #expect(ours.text == emacs[i].text, "\(blocks[i].1)\nours:\n\(ours.text)\n\(ours.failure ?? "")\nemacs:\n\(emacs[i].text)\n\(emacs[i].error)")
77 }
78 }
79
80 @Test func compilesFormats() {
81 let specs = ColumnView.compile("%25ITEM(Task) %Effort{:} %3PRIORITY %Cost{+;%.2f}")
82 #expect(specs.map(\.property) == ["ITEM", "EFFORT", "PRIORITY", "COST"])
83 #expect(specs[0].title == "Task" && specs[0].width == 25)
84 #expect(specs[3].summary == "+" && specs[3].format == "%.2f")
85 }
86}