Tests/OrgCoreTests/ParserGapsTests.swift
123 lines · 5753 bytes
5 symbols in this file
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}