i7t5--edmund
145 行
5.5 KiB
Swift
145 行
5.5 KiB
Swift
import Testing
|
|
import Foundation
|
|
import AppKit
|
|
@testable import EdmundCore
|
|
|
|
// MARK: - Reference link resolution (edit-mode parse)
|
|
|
|
@Suite("SyntaxHighlighter — Reference links")
|
|
struct ReferenceLinkTests {
|
|
|
|
private func linkSpans(_ text: String, defs: String = "")
|
|
-> [(range: NSRange, dest: String)] {
|
|
SyntaxHighlighter.parse(text, linkDefinitions: defs).compactMap { s in
|
|
guard case .link(let d) = s.kind else { return nil }
|
|
return (s.fullRange, d)
|
|
}
|
|
}
|
|
|
|
@Test("Full reference `[text][label]` resolves against an appended definition")
|
|
func fullReference() {
|
|
let links = linkSpans("see [text][bar] here", defs: "[bar]: https://e.com")
|
|
#expect(links.count == 1)
|
|
#expect(links[0].dest == "https://e.com")
|
|
#expect(links[0].range == NSRange(location: 4, length: 11)) // [text][bar]
|
|
}
|
|
|
|
@Test("Collapsed reference `[label][]` resolves")
|
|
func collapsedReference() {
|
|
let links = linkSpans("a [foo][] b", defs: "[foo]: https://f.com")
|
|
#expect(links.count == 1)
|
|
#expect(links[0].dest == "https://f.com")
|
|
}
|
|
|
|
@Test("Shortcut reference `[label]` resolves")
|
|
func shortcutReference() {
|
|
let links = linkSpans("a [foo] b", defs: "[foo]: https://f.com")
|
|
#expect(links.count == 1)
|
|
#expect(links[0].dest == "https://f.com")
|
|
}
|
|
|
|
@Test("Label matching is case-insensitive (CommonMark)")
|
|
func caseInsensitiveLabel() {
|
|
let links = linkSpans("[Foo]", defs: "[foo]: https://f.com")
|
|
#expect(links.count == 1)
|
|
#expect(links[0].dest == "https://f.com")
|
|
}
|
|
|
|
@Test("Unknown label produces no link span (stays plain text)")
|
|
func unknownLabel() {
|
|
#expect(linkSpans("see [text][bar]", defs: "[other]: https://e.com").isEmpty)
|
|
}
|
|
|
|
@Test("Without definitions, a bare `[foo]` is not a link (regression)")
|
|
func noDefinitionsNoLink() {
|
|
#expect(linkSpans("a [foo] b").isEmpty)
|
|
}
|
|
|
|
@Test("Inline link `[t](url)` is unaffected by the append")
|
|
func inlineLinkUnaffected() {
|
|
let links = linkSpans("[t](https://x.com)", defs: "[bar]: https://e.com")
|
|
#expect(links.count == 1)
|
|
#expect(links[0].dest == "https://x.com")
|
|
}
|
|
|
|
@Test("Appended definition never leaks a span past the block")
|
|
func noSpanLeaksIntoAppendedRegion() {
|
|
let text = "[foo]"
|
|
let spans = SyntaxHighlighter.parse(text, linkDefinitions: "[foo]: https://f.com")
|
|
#expect(spans.allSatisfy { $0.fullRange.upperBound <= (text as NSString).length })
|
|
}
|
|
}
|
|
|
|
// MARK: - LinkDefinitionState
|
|
|
|
@Suite("LinkDefinitionState")
|
|
struct LinkDefinitionStateTests {
|
|
|
|
@Test("build collects definition lines; defsText is sorted and deterministic")
|
|
func buildCollectsAndSorts() {
|
|
let s = LinkDefinitionState.build(from: "text\n[b]: u2\nmore\n[a]: u1")
|
|
#expect(s.defsText == "[a]: u1\n[b]: u2")
|
|
}
|
|
|
|
@Test("No definitions yields empty defsText (parse skips the append)")
|
|
func emptyWhenNoDefinitions() {
|
|
#expect(LinkDefinitionState.build(from: "just prose\n[not a def]").defsText == "")
|
|
}
|
|
|
|
@Test("add then remove of the same block content is exact")
|
|
func addRemoveExact() {
|
|
var s = LinkDefinitionState()
|
|
s.add("[a]: u1")
|
|
s.add("[b]: u2")
|
|
s.remove("[a]: u1")
|
|
#expect(s.defsText == "[b]: u2")
|
|
s.remove("[b]: u2")
|
|
#expect(s.defsText == "")
|
|
}
|
|
|
|
@Test("isDefinitionLine recognizes definitions and rejects non-definitions")
|
|
func definitionLineDetection() {
|
|
#expect(LinkDefinitionState.isDefinitionLine("[a]: https://e.com"))
|
|
#expect(LinkDefinitionState.isDefinitionLine(" [a]: /path")) // ≤3 spaces
|
|
#expect(LinkDefinitionState.isDefinitionLine("[a]: u \"title\""))
|
|
#expect(!LinkDefinitionState.isDefinitionLine(" [a]: u")) // 4 spaces = code
|
|
#expect(!LinkDefinitionState.isDefinitionLine("[a]:")) // empty dest
|
|
#expect(!LinkDefinitionState.isDefinitionLine("[a] not a def"))
|
|
#expect(!LinkDefinitionState.isDefinitionLine("plain text"))
|
|
}
|
|
|
|
@Test("Equatable ignores insertion order")
|
|
func equatableOrderIndependent() {
|
|
var a = LinkDefinitionState(); a.add("[x]: 1"); a.add("[y]: 2")
|
|
var b = LinkDefinitionState(); b.add("[y]: 2"); b.add("[x]: 1")
|
|
#expect(a == b)
|
|
}
|
|
|
|
@Test("Definition inside a block quote is collected (GFM ex. 187)")
|
|
func definitionInsideBlockQuote() {
|
|
#expect(LinkDefinitionState.canonicalDefinition(from: "> [foo]: /url") == "[foo]: /url")
|
|
#expect(LinkDefinitionState.canonicalDefinition(from: ">> [foo]: /url") == "[foo]: /url")
|
|
#expect(LinkDefinitionState.build(from: "> [foo]: /url").defsText == "[foo]: /url")
|
|
}
|
|
|
|
@Test("Definition on a list-marker line is collected")
|
|
func definitionInsideListItem() {
|
|
#expect(LinkDefinitionState.canonicalDefinition(from: "- [foo]: /url") == "[foo]: /url")
|
|
#expect(LinkDefinitionState.canonicalDefinition(from: "1. [foo]: /url") == "[foo]: /url")
|
|
}
|
|
|
|
@Test("A task-list checkbox is not mistaken for a definition")
|
|
func taskListNotDefinition() {
|
|
#expect(LinkDefinitionState.canonicalDefinition(from: "- [ ] todo") == nil)
|
|
#expect(LinkDefinitionState.canonicalDefinition(from: "- [x] done") == nil)
|
|
}
|
|
|
|
@Test("Container and plain forms of the same def dedupe to one line")
|
|
func containerAndPlainDedupe() {
|
|
var s = LinkDefinitionState()
|
|
s.add("> [foo]: /url")
|
|
s.add("[foo]: /url")
|
|
#expect(s.defsText == "[foo]: /url")
|
|
}
|
|
}
|