krz/org-swift

A dependency-free Swift library that renders org-mode to sanitized HTML. html library org-mode swift

Tests/OrgSwiftTests/MathTests.swift

105 lines · 4412 bytes

18 symbols in this file
  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 &amp;&lt; y\n\\end{align}</pre>"))
103        #expect(String(OrgAttributedStringRenderer().inline(objects("a $x$ b")).characters) == "a $x$ b")
104    }
105}