Tests/OrgCoreTests/SetupFileTests.swift
96 lines · 5406 bytes
1import Foundation
2import Testing
3@testable import OrgCore
4
5/// `#+SETUPFILE` keywords, nested setup files included, as Org reads them.
6struct SetupFileTests {
7 static let oracle = ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil
8
9 static let common = """
10 #+TODO: NEXT WAIT | DONE CANCELLED
11 #+PRIORITIES: A E C
12 #+TAGS: work home
13 #+FILETAGS: :shared:
14 #+PROPERTY: owner alice
15 #+PROPERTY: header-args :results silent
16 #+TITLE: Setup Title
17 #+OPTIONS: toc:nil num:nil
18 #+MACRO: who Alice
19 #+SETUPFILE: ../deep.org
20 #+SETUPFILE: ../loop.org
21 """ + "\n"
22 static let deep = "#+TYP_TODO: BUG | FIXED\n#+SETUPFILE: setup/common.org\n"
23 static let main = """
24 #+SETUPFILE: setup/common.org
25
26 * NEXT [#E] Task :local:
27 {{{who}}}
28 * BUG Other
29 """ + "\n"
30
31 @Test func collectsNestedKeywordLines() {
32 let files = ["/w/setup/common.org": Self.common, "/w/deep.org": Self.deep]
33 let lines = SetupFile.keywordLines(in: Self.main, directory: "/w") { files[$0] }
34 #expect(lines.hasPrefix("#+TODO: NEXT WAIT | DONE CANCELLED\n"))
35 #expect(lines.contains("#+TYP_TODO: BUG | FIXED\n#+TITLE") == false)
36 #expect(lines.contains("#+TYP_TODO: BUG | FIXED\n"))
37 #expect(lines.components(separatedBy: "#+TODO:").count == 2)
38 #expect(SetupFile.keywordLines(in: "* x\n", directory: "/w") { files[$0] } == "")
39 // A file naming itself adds nothing; a setup file naming it back adds only its own.
40 let own = "#+SETUPFILE: a.org\n#+TODO: A B | C\n"
41 #expect(SetupFile.keywordLines(in: own, directory: "/w", path: "/w/a.org") { $0 == "/w/a.org" ? own : nil } == "")
42 let back = ["/w/a.org": "#+SETUPFILE: b.org\n#+TODO: A | B\n", "/w/b.org": "#+SETUPFILE: a.org\n#+TODO: X | Y\n"]
43 #expect(SetupFile.keywordLines(in: back["/w/a.org"]!, directory: "/w", path: "/w/a.org") { back[$0] } == "#+TODO: X | Y\n")
44 #expect(SetupFile.keywordLines(in: "#+SETUPFILE: https://example.com/a.org\n", directory: "/w") { _ in "#+TODO: X\n" } == "")
45 }
46
47 @Test(.enabled(if: oracle))
48 func matchesOrg() throws {
49 let folder = FileManager.default.temporaryDirectory.appendingPathComponent("orgstar-setup-\(UUID().uuidString)")
50 try FileManager.default.createDirectory(at: folder.appendingPathComponent("setup"), withIntermediateDirectories: true)
51 defer { try? FileManager.default.removeItem(at: folder) }
52 try Self.common.write(to: folder.appendingPathComponent("setup/common.org"), atomically: true, encoding: .utf8)
53 try Self.deep.write(to: folder.appendingPathComponent("deep.org"), atomically: true, encoding: .utf8)
54 let path = folder.appendingPathComponent("main.org").path
55 try Self.main.write(toFile: path, atomically: true, encoding: .utf8)
56
57 let emacs = try EmacsOracle.evaluate("", """
58 (with-current-buffer (find-file-noselect \(lispString(path)))
59 (require 'ox)
60 (goto-char (point-min))
61 (re-search-forward "^\\\\* NEXT")
62 (let ((env (org-export-get-environment)))
63 (list (mapconcat #'identity org-todo-keywords-1 " ")
64 (format "%c %c %c" org-priority-highest org-priority-lowest org-priority-default)
65 (mapconcat #'identity (org-get-tags) " ")
66 (or (cdr (assoc "owner" org-keyword-properties)) "")
67 (mapconcat #'identity (delq nil (mapcar (lambda (x) (and (stringp (car x)) (car x))) org-current-tag-alist)) " ")
68 (org-element-interpret-data (plist-get env :title))
69 (format "%s" (plist-get env :with-toc))
70 (progn (org-macro-initialize-templates) (or (cdr (assoc "who" org-macro-templates)) "")))))
71 """)
72
73 let settings = OrgSettings.default.resolvingSetupFiles(in: Self.main, path: path, read: OrgSettings.readFile)
74 let tree = OrgParser.parse(Self.main, defaults: settings)
75 let model = DocumentModel(tree: tree)
76 let task = try #require(model.headings.firstIndex { $0.title.contains("Task") })
77 let priorities = tree.settings.priorities
78 let tagNames = (FastSelection.tagTable(settings.setupKeywords + Self.main) ?? []).compactMap { item -> String? in
79 if case .option(_, let name) = item { name } else { nil }
80 }
81 let options = ExportOptions.parse(settings.setupKeywords + Self.main)
82 let page = HTMLExport.page(Self.main, fallbackTitle: "main", settings: settings, path: path)
83 let ours = [
84 tree.settings.todoSequences.flatMap { ($0.active + $0.done).map(\.name) }.joined(separator: " "),
85 "\(priorities.highest) \(priorities.lowest) \(priorities.default)",
86 model.tags(of: task).map(\.value).joined(separator: " "),
87 model.property("owner", of: nil)?.value ?? "",
88 tagNames.joined(separator: " "),
89 HTMLExport.metadata(Self.main, settings: settings).title ?? "",
90 options.toc == nil ? "nil" : "\(options.toc!)",
91 page.contains("Alice") ? "Alice" : "",
92 ]
93 #expect(ours == emacs)
94 #expect(page.contains("<h1 class=\"title\">Setup Title</h1>") && !page.contains("id=\"table-of-contents\""))
95 }
96}