import Foundation import Testing @testable import OrgSocialKit /// Helper: find a substring in the AttributedString and assert the attribute /// closure returns true for every run overlapping it. private func hasAttribute( _ attr: AttributedString, over substring: String, matches: (AttributeContainer) -> Bool ) -> Bool { let full = String(attr.characters) guard let range = full.range(of: substring) else { return false } let startOffset = full.distance(from: full.startIndex, to: range.lowerBound) let endOffset = full.distance(from: full.startIndex, to: range.upperBound) let startIdx = attr.characters.index(attr.characters.startIndex, offsetBy: startOffset) let endIdx = attr.characters.index(attr.characters.startIndex, offsetBy: endOffset) for run in attr[startIdx.. not a valid opening context per our rules. #expect(String(r.inline.characters) == "foo*bar*baz") } @Test("code protects the asterisk inside") func codeWrapsAsterisk() { let r = OrgBodyRenderer.render("literal =*not bold*= here") #expect(String(r.inline.characters) == "literal *not bold* here") #expect(hasAttribute(r.inline, over: "*not bold*") { c in c.inlinePresentationIntent?.contains(.code) == true }) // And the inner asterisks did not produce bold on "not bold". #expect(hasAttribute(r.inline, over: "not bold") { c in c.inlinePresentationIntent?.contains(.stronglyEmphasized) != true }) } } @Suite("OrgBodyRenderer – links and URLs") struct OrgBodyLinksTests { @Test("bracket link with label renders label as link") func bracketLinkLabel() { let r = OrgBodyRenderer.render("See [[https://example.com][the docs]] please") #expect(String(r.inline.characters) == "See the docs please") #expect(hasAttribute(r.inline, over: "the docs") { $0.link == URL(string: "https://example.com") }) } @Test("bracket link without label uses the URL as text") func bracketLinkBare() { let r = OrgBodyRenderer.render("Docs: [[https://example.com/x]]") #expect(String(r.inline.characters).contains("https://example.com/x")) #expect(hasAttribute(r.inline, over: "https://example.com/x") { $0.link == URL(string: "https://example.com/x") }) } @Test("bare URL in plain text becomes a link") func bareURL() { let r = OrgBodyRenderer.render("Visit https://example.com for more.") #expect(hasAttribute(r.inline, over: "https://example.com") { $0.link == URL(string: "https://example.com") }) } @Test("trailing punctuation stays outside the URL link") func bareURLTrailingPunct() { let r = OrgBodyRenderer.render("see https://example.com/foo.") #expect(hasAttribute(r.inline, over: "https://example.com/foo") { $0.link == URL(string: "https://example.com/foo") }) // The trailing dot is NOT part of the link. let full = String(r.inline.characters) guard let dotRange = full.range(of: ".", options: .backwards) else { return } let offset = full.distance(from: full.startIndex, to: dotRange.lowerBound) let idx = r.inline.characters.index(r.inline.characters.startIndex, offsetBy: offset) let afterIdx = r.inline.characters.index(after: idx) let dotRun = r.inline[idx..