Tests/OrgSwiftTests/MathTests.swift
105 lines · 4412 bytes
18 symbols in this file
MathParseTestsobjectsmatheveryFragmentDelimiteranInlineEnvironmentIsDisplayMathOfItsWholeSourceanEnvironmentClosedByAnotherNameIsTextdollarsWithSpaceInsideTheirBordersAreProseanUnclosedDelimiterIsTextverbatimHoldsNoMathmathInsideEmphasisaBeginLineToItsEndLineIsALatexBlockaBeginLineWithoutItsEndIsProseunmatchedBeginLinesParseInLinearTimeunclosedOpenersScanInLinearTimeonlyASCIISpaceKeepsDollarsProseenvironmentNamesAreASCIIWordCharactersaStrayEndLineEndsAParagraphplainTextAndHTMLShowTheSource
1import Foundation
2import Testing
3@testable import OrgSwift
4
5/// LaTeX fragments and blocks, read as go-org reads them, with org's
6/// border rule for `$…$` so "$5 and $10" stays prose.
7struct MathParseTests {
8
9 private func objects(_ source: String) -> [OrgObject] {
10 guard case .paragraph(let objects) = OrgParser.parse(source).elements.first else { return [] }
11 return objects
12 }
13
14 private func math(_ tex: String, _ display: Bool, _ source: String) -> OrgObject {
15 .math(OrgMath(tex: tex, display: display, source: source))
16 }
17
18 @Test func everyFragmentDelimiter() {
19 #expect(objects(#"a $x^2$ b"#) == [.text("a "), math("x^2", false, "$x^2$"), .text(" b")])
20 #expect(objects(#"a \(x\) b"#) == [.text("a "), math("x", false, #"\(x\)"#), .text(" b")])
21 #expect(objects(#"a \[x\] b"#) == [.text("a "), math("x", true, #"\[x\]"#), .text(" b")])
22 #expect(objects(#"a $$x$$ b"#) == [.text("a "), math("x", true, "$$x$$"), .text(" b")])
23 }
24
25 @Test func anInlineEnvironmentIsDisplayMathOfItsWholeSource() {
26 let source = #"\begin{matrix} a \end{matrix}"#
27 #expect(objects("so " + source + " ok") == [.text("so "), math(source, true, source), .text(" ok")])
28 }
29
30 @Test func anEnvironmentClosedByAnotherNameIsText() {
31 #expect(objects(#"\begin{a} x \end{b}"#) == [.text(#"\begin{a} x \end{b}"#)])
32 }
33
34 @Test func dollarsWithSpaceInsideTheirBordersAreProse() {
35 #expect(objects("$5 and $10") == [.text("$5 and $10")])
36 #expect(objects("$5 and $10 then $y$") == [.text("$5 and $10 then "), math("y", false, "$y$")])
37 }
38
39 @Test func anUnclosedDelimiterIsText() {
40 #expect(objects(#"cost $5"#) == [.text("cost $5")])
41 #expect(objects(#"a \( b"#) == [.text(#"a \( b"#)])
42 }
43
44 @Test func verbatimHoldsNoMath() {
45 #expect(objects("=$x$= and ~$y$~") == [.verbatim("$x$"), .text(" and "), .code("$y$")])
46 }
47
48 @Test func mathInsideEmphasis() {
49 #expect(objects("*bold $x$*") == [.bold([.text("bold "), math("x", false, "$x$")])])
50 }
51
52 @Test func aBeginLineToItsEndLineIsALatexBlock() {
53 let doc = OrgParser.parse("before\n\\begin{equation}\n x = 1\n\\end{equation}\nafter")
54 #expect(doc.elements == [
55 .paragraph([.text("before")]),
56 .latexBlock("\\begin{equation}\n x = 1\n\\end{equation}"),
57 .paragraph([.text("after")]),
58 ])
59 }
60
61 @Test func aBeginLineWithoutItsEndIsProse() {
62 let doc = OrgParser.parse("\\begin{equation}\nx = 1")
63 #expect(doc.elements.count == 1)
64 guard case .paragraph = doc.elements.first else {
65 Issue.record("expected a paragraph, got \(doc.elements)"); return
66 }
67 }
68
69 @Test(.timeLimit(.minutes(1)))
70 func unmatchedBeginLinesParseInLinearTime() {
71 let doc = OrgParser.parse(String(repeating: "\\begin{a}\n", count: 4000))
72 #expect(doc.elements.count == 1)
73 }
74
75 @Test(.timeLimit(.minutes(1)))
76 func unclosedOpenersScanInLinearTime() {
77 for opener in [#"\("#, #"\["#, "$$", "$x ", "[[", "[", "<"] {
78 let text = String(repeating: opener, count: 40_000)
79 let start = Date()
80 #expect(OrgParser.plain(objects(text)) == text.trimmingCharacters(in: .whitespaces), "\(opener)")
81 #expect(Date().timeIntervalSince(start) < 5, "\(opener) took \(Date().timeIntervalSince(start)) s")
82 }
83 }
84
85 @Test func onlyASCIISpaceKeepsDollarsProse() {
86 #expect(objects("$\u{00A0}x$") == [math("\u{00A0}x", false, "$\u{00A0}x$")])
87 }
88
89 @Test func environmentNamesAreASCIIWordCharacters() {
90 #expect(objects(#"\begin{aligné} x \end{aligné}"#) == [.text(#"\begin{aligné} x \end{aligné}"#)])
91 }
92
93 @Test func aStrayEndLineEndsAParagraph() {
94 #expect(OrgParser.parse("text\n\\end{equation}\nmore").elements.count == 2)
95 }
96
97 @Test func plainTextAndHTMLShowTheSource() {
98 let doc = OrgParser.parse("a $x$ b\n\n\\begin{align}\nx &< y\n\\end{align}")
99 #expect(OrgParser.plain(objects("a $x$ b")) == "a $x$ b")
100 let html = OrgHTMLTreeRenderer().render(doc)
101 #expect(html.contains("<p>a $x$ b</p>"))
102 #expect(html.contains("<pre>\\begin{align}\nx &< y\n\\end{align}</pre>"))
103 #expect(String(OrgAttributedStringRenderer().inline(objects("a $x$ b")).characters) == "a $x$ b")
104 }
105}