krz/orgstar

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

Tests/OrgCoreTests/ParserGapsTests.swift

15f6b0709d88971fb62ed432c3e5b8032643670a
orgstar/Tests/OrgCoreTests/ParserGapsTests.swift history · blame · raw

123 lines · 5753 bytes

  1import Foundation
  2import Testing
  3@testable import OrgCore
  4
  5/// Citations, export snippets, inline Babel calls, diary sexps, table.el tables, and item
  6/// counters and tags, where org-element finds them.
  7struct ParserGapsTests {
  8    static let text = #"""
  9        As [cite:@doe2020] and [cite/t/b:see @a p. 2; @b;and @c-d:e] say, also [cite:pre; @x; post].
 10        Not a cite: [cite:no key], [cite/:@x], [cite:@k [nested] here], [cite:@open
 11        and on @@html:<b>bold</b>@@ or @@latex:\emph{x}@@, not @@x@@ or @@:y@@ or @@html:open.
 12        Calls: call_square(4) call_f[:results raw](x=1, y=(2))[:exports both] xcall_no(1) call_f[x] call_(1).
 13        %%(diary-float t 4 2) Thanksgiving
 14        Diary <%%(diary-float t 4 2)>, <%%(x) 10:00>, <%%()>, <%%(a]b)>, <%%(open and <%%(y)
 15          %%(indented) is text
 16
 17        +----+-----+
 18        | a  | b   |
 19        +----+-----+
 20        | c  | d   |
 21        +----+-----+
 22
 23        +--+--+
 24        Single rule is a paragraph.
 25
 26        +--+
 27        |x |
 28        not closed by a rule
 29
 30        | org | table |
 31        +---+---+
 32        | other |
 33        +-+
 34        +-+
 35
 36        - term :: definition with *bold*
 37        - a :: b :: c
 38        - [X] boxed :: yes
 39        - [@3] counted
 40        - [@start:7] [X] both
 41        - no tag::here
 42        - tag ::
 43        1. one :: two
 44        2. [@5] five
 45        - plain
 46        - *bold* term :: x
 47        """# + "\n"
 48
 49    static let kinds: [(lisp: String, ours: SyntaxKind)] = [
 50        ("citation", .citation), ("citation-reference", .citationReference), ("export-snippet", .exportSnippet),
 51        ("inline-babel-call", .inlineBabelCall), ("timestamp", .timestamp), ("diary-sexp", .diarySexp), ("paragraph", .paragraph), ("item", .item),
 52    ]
 53
 54    @Test func objectsAndElementsMatchOrgElement() throws {
 55        let tree = OrgParser.parse(Self.text)
 56        var forms = Self.kinds.map { kind in
 57            "(mapconcat (lambda (o) (format \"%d\" (1- (org-element-begin o)))) (org-element-map (org-element-parse-buffer) '\(kind.lisp) #'identity) \",\")"
 58        }
 59        forms.append("(mapconcat (lambda (o) (format \"%d:%s\" (1- (org-element-begin o)) (org-element-property :type o))) (org-element-map (org-element-parse-buffer) 'table #'identity) \",\")")
 60        forms.append("""
 61            (mapconcat (lambda (o) (format "%s|%s|%s" (let ((tag (org-element-interpret-data (org-element-property :tag o)))) (if (string-empty-p tag) "-" tag))
 62                                           (or (org-element-property :counter o) "-") (or (org-element-property :checkbox o) "-")))
 63                       (org-element-map (org-element-parse-buffer) 'item #'identity) ",")
 64            """)
 65        let emacs = try EmacsOracle.evaluate(Self.text, "(with-current-buffer (find-file-noselect \"oracle.org\") (org-mode) (list \(forms.joined(separator: " "))))")
 66        let nodes = tree.root.descendants()
 67        for (i, kind) in Self.kinds.enumerated() {
 68            let ours = nodes.filter { $0.kind == kind.ours }.map { String($0.range.lowerBound) }
 69            #expect(ours == emacs[i].split(separator: ",").map(String.init), "\(kind.lisp)")
 70        }
 71        let tables = nodes.filter { $0.kind == .table || $0.kind == .tableEl }.map { "\($0.range.lowerBound):\($0.kind == .table ? "org" : "table.el")" }
 72        #expect(tables == emacs[Self.kinds.count].split(separator: ",").map(String.init), "tables")
 73        let items = nodes.filter { $0.kind == .item }.map { item -> String in
 74            let tag = item.firstChild(.itemTag)?.text ?? "-"
 75            let counter = item.tokens.first { $0.kind == .counter }.map { $0.text.replacingOccurrences(of: "[@start:", with: "").replacingOccurrences(of: "[@", with: "").replacingOccurrences(of: "]", with: "") } ?? "-"
 76            let box = item.tokens.first { $0.kind == .checkbox }.map { ["[X]": "on", "[x]": "on", "[ ]": "off", "[-]": "trans"][$0.text]! } ?? "-"
 77            return "\(tag)|\(counter)|\(box)"
 78        }
 79        #expect(items == emacs[Self.kinds.count + 1].split(separator: ",").map(String.init), "items")
 80    }
 81
 82    @Test func citationParts() throws {
 83        let tree = OrgParser.parse("[cite/t:see @a p. 2; @b; post]\n")
 84        let cite = try #require(tree.root.descendants().first { $0.kind == .citation })
 85        #expect(cite.text == "[cite/t:see @a p. 2; @b; post]")
 86        let references = cite.children.filter { $0.kind == .citationReference }
 87        #expect(references.map(\.text) == ["see @a p. 2;", " @b;"])
 88        #expect(references.compactMap { $0.tokens.first { $0.kind == .citationKey }?.text } == ["@a", "@b"])
 89    }
 90}
 91
 92/// The new syntax through ox-html, compared as the conformance files are.
 93struct ParserGapsExportTests {
 94    static let text = """
 95        Raw @@html:<b>bold</b>@@ and @@latex:\\emph{x}@@ here.
 96
 97        %%(diary-float t 4 2) Thanksgiving
 98
 99        - term :: definition here
100        - untagged
101        - [X] boxed :: yes
102
103
104        1. one
105        2. two
106
107
108        - [@3] unordered counter
109        """ + "\n"
110
111    @Test func htmlMatchesOxHTML() throws {
112        let emacs = try EmacsOracle.evaluate(Self.text, """
113            (list (progn (require 'ox-html)
114                         (with-current-buffer (find-file-noselect "oracle.org")
115                           (org-export-as 'html nil nil t '(:with-toc nil :section-numbers nil)))))
116            """)
117        let ours = Skeleton.reduce(HTMLExport.body(Self.text))
118        #expect(ours == Skeleton.reduce(emacs[0]), "ours:\n\(ours)\nemacs:\n\(Skeleton.reduce(emacs[0]))")
119        // Org 9.8.7's ox-html fails on an ordered item's counter (`format` given a number);
120        // the value is what it means to write.
121        #expect(HTMLExport.body("1. one\n2. [@5] five\n").contains("<li value=\"5\">five</li>"))
122    }
123}