import Foundation import Testing @testable import OrgSwift /// LaTeX fragments and blocks, read as go-org reads them, with org's /// border rule for `$…$` so "$5 and $10" stays prose. struct MathParseTests { private func objects(_ source: String) -> [OrgObject] { guard case .paragraph(let objects) = OrgParser.parse(source).elements.first else { return [] } return objects } private func math(_ tex: String, _ display: Bool, _ source: String) -> OrgObject { .math(OrgMath(tex: tex, display: display, source: source)) } @Test func everyFragmentDelimiter() { #expect(objects(#"a $x^2$ b"#) == [.text("a "), math("x^2", false, "$x^2$"), .text(" b")]) #expect(objects(#"a \(x\) b"#) == [.text("a "), math("x", false, #"\(x\)"#), .text(" b")]) #expect(objects(#"a \[x\] b"#) == [.text("a "), math("x", true, #"\[x\]"#), .text(" b")]) #expect(objects(#"a $$x$$ b"#) == [.text("a "), math("x", true, "$$x$$"), .text(" b")]) } @Test func anInlineEnvironmentIsDisplayMathOfItsWholeSource() { let source = #"\begin{matrix} a \end{matrix}"# #expect(objects("so " + source + " ok") == [.text("so "), math(source, true, source), .text(" ok")]) } @Test func anEnvironmentClosedByAnotherNameIsText() { #expect(objects(#"\begin{a} x \end{b}"#) == [.text(#"\begin{a} x \end{b}"#)]) } @Test func dollarsWithSpaceInsideTheirBordersAreProse() { #expect(objects("$5 and $10") == [.text("$5 and $10")]) #expect(objects("$5 and $10 then $y$") == [.text("$5 and $10 then "), math("y", false, "$y$")]) } @Test func anUnclosedDelimiterIsText() { #expect(objects(#"cost $5"#) == [.text("cost $5")]) #expect(objects(#"a \( b"#) == [.text(#"a \( b"#)]) } @Test func verbatimHoldsNoMath() { #expect(objects("=$x$= and ~$y$~") == [.verbatim("$x$"), .text(" and "), .code("$y$")]) } @Test func mathInsideEmphasis() { #expect(objects("*bold $x$*") == [.bold([.text("bold "), math("x", false, "$x$")])]) } @Test func aBeginLineToItsEndLineIsALatexBlock() { let doc = OrgParser.parse("before\n\\begin{equation}\n x = 1\n\\end{equation}\nafter") #expect(doc.elements == [ .paragraph([.text("before")]), .latexBlock("\\begin{equation}\n x = 1\n\\end{equation}"), .paragraph([.text("after")]), ]) } @Test func aBeginLineWithoutItsEndIsProse() { let doc = OrgParser.parse("\\begin{equation}\nx = 1") #expect(doc.elements.count == 1) guard case .paragraph = doc.elements.first else { Issue.record("expected a paragraph, got \(doc.elements)"); return } } @Test(.timeLimit(.minutes(1))) func unmatchedBeginLinesParseInLinearTime() { let doc = OrgParser.parse(String(repeating: "\\begin{a}\n", count: 4000)) #expect(doc.elements.count == 1) } @Test(.timeLimit(.minutes(1))) func unclosedOpenersScanInLinearTime() { for opener in [#"\("#, #"\["#, "$$", "$x ", "[[", "[", "<"] { let text = String(repeating: opener, count: 40_000) let start = Date() #expect(OrgParser.plain(objects(text)) == text.trimmingCharacters(in: .whitespaces), "\(opener)") #expect(Date().timeIntervalSince(start) < 5, "\(opener) took \(Date().timeIntervalSince(start)) s") } } @Test func onlyASCIISpaceKeepsDollarsProse() { #expect(objects("$\u{00A0}x$") == [math("\u{00A0}x", false, "$\u{00A0}x$")]) } @Test func environmentNamesAreASCIIWordCharacters() { #expect(objects(#"\begin{aligné} x \end{aligné}"#) == [.text(#"\begin{aligné} x \end{aligné}"#)]) } @Test func aStrayEndLineEndsAParagraph() { #expect(OrgParser.parse("text\n\\end{equation}\nmore").elements.count == 2) } @Test func plainTextAndHTMLShowTheSource() { let doc = OrgParser.parse("a $x$ b\n\n\\begin{align}\nx &< y\n\\end{align}") #expect(OrgParser.plain(objects("a $x$ b")) == "a $x$ b") let html = OrgHTMLTreeRenderer().render(doc) #expect(html.contains("

a $x$ b

")) #expect(html.contains("
\\begin{align}\nx &< y\n\\end{align}
")) #expect(String(OrgAttributedStringRenderer().inline(objects("a $x$ b")).characters) == "a $x$ b") } }