krz/orgstar

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

Tests/OrgPresentationTests/PresentationTests.swift

b98a6509c5ae75e5172dd333d1b6105fd6ceee0e
orgstar/Tests/OrgPresentationTests/PresentationTests.swift history · blame · raw

164 lines · 7111 bytes

21 symbols in this file
  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}