krz/orgstar

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

Tests/OrgCoreTests/AgendaSearchTests.swift

191ddb506f3a91b93c755ba6e8123a4e8ac4f90d
orgstar/Tests/OrgCoreTests/AgendaSearchTests.swift history · blame · raw

119 lines · 5372 bytes

  1import Foundation
  2import Testing
  3@testable import OrgCore
  4
  5/// `org-search-view` against Emacs.
  6struct AgendaSearchTests {
  7    static let first = """
  8    #+TODO: TODO NEXT | DONE
  9    Rent before the first heading.
 10    * TODO [#A] Pay rent :home:
 11    SCHEDULED: <2026-10-01 Thu>
 12    Ask the landlord about the boiler.
 13    * Meeting 10:00 with Bob
 14    Notes about the end of
 15    the line, and Bob's plan.
 16    * DONE Paid the rent
 17    * NEXT Report :work:
 18    ** Section with alpha and beta
 19    Alpha only here, and a boilerplate.
 20    ** Section with alpha
 21    Gamma.
 22    * todo list of chores
 23    Water the plants, alpha.
 24    * Archived :ARCHIVE:
 25    The rent is here too.
 26    ** TODO Under archived rent
 27    * COMMENT Commented rent
 28    * Two words
 29    The phrase two   words
 30    spans here; foo_bar and foo-bar.
 31    Costs $5 or 50% (don't) for the café.
 32    * The last
 33    word ends this entry with last
 34    * word starts the next heading
 35    * Ünïcode tïtle :tag:
 36    Straße and STRASSE.
 37    """
 38
 39    static let second = """
 40    #+CATEGORY: other
 41    * WAIT Second file rent
 42    * Plans
 43    alpha, beta, gamma in the body.
 44    ** HOLD Nested beta
 45    * Headline alpha
 46    """
 47
 48    static let strings = [
 49        // Phrases.
 50        "rent", "RENT", "pay rent", "end of the line", "end of\nthe", "two words", "last word",
 51        // A match ending in the next heading's line selects that entry.
 52        "last * word","Bob's", "boiler",
 53        "straße", "foo_bar", "10:00", "nothing at all", "alpha, beta",
 54        // Boolean.
 55        "+alpha", "+alpha -beta", "+alpha +gamma", "-alpha", "+beta -nested", "{alph?a}", "+{^Gam}", "-{b[eo]}",
 56        "+{\\<rent\\>}", "+\"two words\"", "+\"two   words\"", "+\"the line\"", "{boiler\\b}", "+alpha +\"in the body\"",
 57        "+{foo\\s-bar}", "+boiler -plate", "+{a b}", "+{alpha} -{x y}",
 58        // Emacs word syntax in Org buffers: `_`, `-` and `'` are not word characters; `$` and `%` are.
 59        "+{\\<bar\\>}", "+{foo\\b}", "+{o\\B_}", "+{\\<5\\>}", "+{\\b50\\b}", "+{don\\>}", "+{\\w+_\\w+}", "+{\\W5}",
 60        "+{caf\\w\\>}", "+{\\<\\$5}",
 61        // Headlines only.
 62        "*rent", "*alpha", "*+alpha -beta", "*+section", "*-alpha", "*pay rent", "*with alpha and",
 63        // TODO only and whole words.
 64        "!rent", "!+alpha", "*!section", ":+boiler", ":+alpha", "+:boiler", ":boiler", "*!:+rent",
 65    ]
 66
 67    static func lisp(_ s: String) -> String {
 68        "\"" + s.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"").replacingOccurrences(of: "\n", with: "\\n") + "\""
 69    }
 70
 71    static let files = [("a.org", first), ("b.org", second)]
 72    static let today = Days.absolute(year: 2026, month: 10, day: 5)
 73
 74    @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil))
 75    func searchMatchesEmacs() throws {
 76        for string in Self.strings {
 77            for todoOnly in [false, true] {
 78                try AgendaOracle.compareList(files: Self.files, form: "(org-search-view \(todoOnly ? "t" : "nil") \(Self.lisp(string)))", today: Self.today) {
 79                    Agenda.textSearch($0, string: string, todoOnly: todoOnly)
 80                }
 81            }
 82        }
 83    }
 84
 85    @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil))
 86    func searchOptionsMatchEmacs() throws {
 87        for string in ["boiler", "+boiler", "alpha beta", "+alpha -beta", "{^Gam}", "two words", "*rent", "rent -paid", "+\"two words\""] {
 88            for (boolean, full) in [(true, false), (false, true), (true, true)] {
 89                var options = AgendaOptions()
 90                options.searchAlwaysBoolean = boolean
 91                options.searchForceFullWords = full
 92                let form = "(let ((org-agenda-search-view-always-boolean \(boolean ? "t" : "nil")) (org-agenda-search-view-force-full-words \(full ? "t" : "nil"))) (org-search-view nil \(Self.lisp(string))))"
 93                try AgendaOracle.compareList(files: Self.files, form: form, today: Self.today) {
 94                    Agenda.textSearch($0, string: string, options: options)
 95                }
 96            }
 97        }
 98    }
 99
100    @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil))
101    func searchPrefixMatchesEmacs() throws {
102        let formats = ["search": "%c %?-5:l %b", "agenda": " %i %-12:c%?-12t% s", "todo": " %i %-12:c", "tags": " %i %-12:c"]
103        var options = AgendaOptions()
104        options.prefixFormat = formats
105        let form = "(let ((org-agenda-prefix-format \(AgendaOracle.prefixLisp(formats)))) (org-search-view nil \"alpha\"))"
106        try AgendaOracle.compareList(files: Self.files, form: form, today: Self.today) {
107            Agenda.textSearch($0, string: "alpha", options: options)
108        }
109    }
110
111    @Test func parsesSnippets() {
112        let query = Agenda.SearchQuery("*!:+alpha -\"two words\" {a b} back\\ slash", todoOnly: false, options: AgendaOptions())
113        #expect(query.headlinesOnly && query.todoOnly && query.boolean)
114        #expect(query.positive.map(\.emacs) == ["\\<back slash\\>","\\<alpha\\>", "a b"])
115        #expect(query.negative.map(\.emacs) == ["\\<two words\\>"])
116        let phrase = Agenda.SearchQuery("pay  rent.", todoOnly: false, options: AgendaOptions())
117        #expect(!phrase.boolean && phrase.positive.map(\.emacs) == ["pay\\s-+rent\\."])
118    }
119}