Tests/OrgPresentationTests/PresentationTests.swift
164 lines · 7111 bytes
1import Foundation
2import OrgCore
3import Testing
4@testable import OrgPresentation
5
6func roles(_ text: String) -> [String] {
7 let tree = OrgParser.parse(text)
8 let utf16 = Array(text.utf16)
9 return Presentation.styles(tree, in: 0..<utf16.count).map { run in
10 "\(run.role):\(String(decoding: utf16[run.range], as: UTF16.self))"
11 }
12}
13
14struct StyleTests {
15 @Test func headingParts() {
16 #expect(roles("** DONE [#A] Ship *it* :work:\n") == [
17 "heading(level: 2):** DONE [#A] Ship *it* :work:\n",
18 "leadingStars:*", "done:DONE", "priority:[#A]", "tags::work:",
19 "bold:*it*", "markup:*", "markup:*",
20 ])
21 }
22
23 @Test func describedLinksDimTheirTarget() {
24 #expect(roles("[[https://a.b][site]]\n") == [
25 "link:[[https://a.b][site]]", "linkMarkup:[[", "linkMarkup:https://a.b", "linkMarkup:][", "linkMarkup:]]",
26 ])
27 #expect(roles("[[https://a.b]]\n") == ["link:[[https://a.b]]", "linkMarkup:[[", "linkMarkup:]]"])
28 }
29
30 @Test func blocksAndMetadata() {
31 #expect(roles("#+begin_src sh\necho\n#+end_src\n") == [
32 "block:#+begin_src sh\necho\n#+end_src\n", "blockDelimiter:#+begin_src sh", "blockDelimiter:#+end_src",
33 ])
34 #expect(roles("* a\nSCHEDULED: <2026-10-04 Sun>\n").contains("metadata:SCHEDULED: <2026-10-04 Sun>\n"))
35 }
36
37 @Test func onlyRunsOverlappingTheRange() {
38 let text = "* a\nfirst *b*\n* c\nsecond *d*\n"
39 let tree = OrgParser.parse(text)
40 let runs = Presentation.styles(tree, in: 17..<28)
41 #expect(!runs.contains { $0.range == 10..<13 })
42 #expect(runs.contains { $0.range == 25..<28 && $0.role == .bold })
43 }
44}
45
46struct IndentTests {
47 @Test func bodiesIndentPastTheirStars() {
48 let text = "pre\n* a\nbody\n** b\nmore\n"
49 let runs = Presentation.indents(OrgParser.parse(text), in: 0..<text.utf16.count)
50 #expect(runs == [
51 IndentRun(range: 4..<8, firstLine: 0, wrapped: 2),
52 IndentRun(range: 8..<13, firstLine: 2, wrapped: 2),
53 IndentRun(range: 13..<18, firstLine: 0, wrapped: 3),
54 IndentRun(range: 18..<23, firstLine: 3, wrapped: 3),
55 ])
56 }
57}
58
59struct FoldTests {
60 let text = "* a\nbody\n** b\nmore\n* c\ntext\n"
61
62 @Test func hiddenRanges() {
63 let tree = OrgParser.parse(text)
64 #expect(Presentation.hiddenRanges(tree, folds: [0]) == [4..<19])
65 #expect(Presentation.hiddenRanges(tree, folds: [9]) == [14..<19])
66 #expect(Presentation.hiddenRanges(tree, folds: [0, 9]) == [4..<19])
67 #expect(Presentation.hiddenRanges(tree, folds: [19]) == [23..<28])
68 #expect(Presentation.hiddenRanges(OrgParser.parse("* a"), folds: [0]).isEmpty)
69 }
70
71 @Test func shadedBlocks() {
72 let text = "* a\n#+begin_src sh\nls\n#+end_src\n\n- item\n #+begin_quote\n q\n #+end_quote\n: fixed\n"
73 let ns = text as NSString
74 let ranges = Presentation.shadedBlocks(OrgParser.parse(text))
75 #expect(ranges.map { ns.substring(with: NSRange($0)) } == [
76 "#+begin_src sh\nls\n#+end_src", " #+begin_quote\n q\n #+end_quote", ": fixed",
77 ])
78 }
79
80 @Test func headingContaining() {
81 let tree = OrgParser.parse(text)
82 #expect(Presentation.heading(containing: 2, in: tree) == 0)
83 #expect(Presentation.heading(containing: 3, in: tree) == 0)
84 #expect(Presentation.heading(containing: 5, in: tree) == nil)
85 #expect(Presentation.heading(containing: 10, in: tree) == 9)
86 #expect(Presentation.headings(tree) == [0, 9, 19])
87 }
88}
89
90struct CyclingTests {
91 let text = "* a\nbody\n** b\nmore\n*** c\ndeep\n* d\n"
92 var tree: OrgTree { OrgParser.parse(text) }
93
94 @Test func outline() {
95 let entries = Presentation.outline(tree)
96 #expect(entries.map(\.title) == ["a", "b", "c", "d"])
97 #expect(entries.map(\.parent) == [nil, 0, 1, nil])
98 #expect(entries.map(\.isTopLevel) == [true, false, false, true])
99 }
100
101 @Test func localCycle() {
102 var state = FoldState()
103 state = Presentation.cycle(0, in: tree, state)
104 #expect(state == FoldState(folds: [0]))
105 state = Presentation.cycle(0, in: tree, state)
106 #expect(state == FoldState(folds: [9]))
107 state = Presentation.cycle(0, in: tree, state)
108 #expect(state == FoldState())
109 #expect(Presentation.cycle(30, in: tree, FoldState(folds: [30])) == FoldState())
110 }
111
112 @Test func globalStates() {
113 #expect(Presentation.foldState(for: .overview, in: tree) == FoldState(folds: [0, 30]))
114 #expect(Presentation.foldState(for: .contents, in: tree) == FoldState(bodyFolds: [0, 9, 19, 30]))
115 #expect(Presentation.cycleGlobal(from: nil, in: tree).0 == .overview)
116 #expect(Presentation.cycleGlobal(from: .overview, in: tree).0 == .contents)
117 #expect(Presentation.cycleGlobal(from: .contents, in: tree).0 == .showAll)
118 }
119
120 @Test func bodyFoldsKeepChildHeadings() {
121 #expect(Presentation.hiddenRanges(tree, folds: [], bodyFolds: [0, 9, 19, 30]) == [4..<9, 14..<19, 25..<30])
122 }
123
124 @Test func startup() {
125 #expect(Presentation.startupFoldState(OrgParser.parse("#+STARTUP: content\n" + text)) != nil)
126 #expect(Presentation.startupFoldState(OrgParser.parse(text)) == nil)
127 let levels = OrgParser.parse("#+startup: show2levels\n" + text)
128 let state = Presentation.startupFoldState(levels)
129 #expect(state?.folds.count == 1 && state?.bodyFolds.count == 2)
130 #expect(Presentation.startupOptions(OrgParser.parse("#+begin_example\n#+STARTUP: overview\n#+end_example\n")).isEmpty)
131 #expect(Presentation.startupOptions(OrgParser.parse("#+STARTUP: content\n* a\n:LOGBOOK:\n#+startup: hideblocks\n:END:\n")) == ["content", "hideblocks"])
132 }
133}
134
135struct ThemeSpecTests {
136 @Test func readsColorsOverTheDefault() throws {
137 let tables = try TOML.parse("""
138 [theme]
139 font = "Menlo"
140 link = "#112233"
141 [theme.dark]
142 link = "#445566cc"
143 heading-1 = "blue"
144 sparkle = "#000000"
145 [theme.todo]
146 WAIT = "#bf8700"
147 """)
148 let (spec, problems) = ThemeSpec.reading(tables)
149 #expect(spec.light["link"] == RGBA(hex: "#112233"))
150 #expect(spec.dark["link"]?.hex == "#445566cc")
151 #expect(spec.dark["heading-1"] == ThemeSpec.default.dark["heading-1"])
152 #expect(spec.todo["WAIT"]?.hex == "#bf8700")
153 #expect(problems.count == 2)
154 }
155
156 @Test func defaultThemeFileReadsBackAsTheDefault() throws {
157 let (spec, problems) = ThemeSpec.reading(try TOML.parse(ThemeSpec.defaultThemeFile()), over: ThemeSpec(light: [:], dark: [:]))
158 #expect(problems.isEmpty)
159 #expect(spec.light == ThemeSpec.default.light && spec.dark == ThemeSpec.default.dark)
160 let defaulted = Set(ThemeSpec.slots.map(\.key)).subtracting(ThemeSpec.systemDefaults)
161 #expect(Set(ThemeSpec.default.light.keys) == defaulted)
162 #expect(Set(ThemeSpec.default.dark.keys) == defaulted)
163 }
164}