blob: 6a6caf7251b0027fa0f08e31f48c00be4255fa4a (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
import Foundation
import NorgKit
import Testing
@testable import NorgEditor
@Test func plainTextYieldsNoHighlights() {
#expect(NorgHighlighter.highlights(in: "just some prose").isEmpty)
#expect(NorgHighlighter.highlights(in: "").isEmpty)
}
@Test func headingMarkerIsHighlighted() {
let highlights = NorgHighlighter.highlights(in: "** Title")
let heading = highlights.first
#expect(heading?.kind == .heading(level: 2))
// The marker run `**` spans the first two UTF-16 units; the title is plain.
#expect(heading?.range == NSRange(location: 0, length: 2))
}
@Test func taskStatusIsHighlightedWithMarkerAndStatus() {
let highlights = NorgHighlighter.highlights(in: "- (x) buy milk")
#expect(highlights.contains { $0.kind == .unorderedList(level: 1) })
let status = highlights.first { highlight in
if case .taskStatus = highlight.kind { return true }
return false
}
#expect(status?.kind == .taskStatus(.done))
// `(x)` — open paren, status char, close paren.
#expect(status?.range == NSRange(location: 2, length: 3))
}
@Test func inlineVerbatimReportsDelimitersAndStyledText() {
let highlights = NorgHighlighter.highlights(in: "a `code` b")
let styled = highlights.first { highlight in
if case .styledText(let style) = highlight.kind { return style.contains(.verbatim) }
return false
}
#expect(styled?.range == NSRange(location: 3, length: 4)) // "code"
#expect(highlights.contains { $0.kind == .modifierDelimiter(.verbatim) })
}
@Test func rangesAreValidForMultibyteSource() {
// An emoji is two UTF-16 units; the bold run must land on the right span.
let source = "🌟 *bold*"
let highlights = NorgHighlighter.highlights(in: source)
let nsSource = source as NSString
let styled = highlights.first { highlight in
if case .styledText(let style) = highlight.kind { return style.contains(.bold) }
return false
}
let range = styled?.range
#expect(range.map { nsSource.substring(with: $0) } == "bold")
}
@Test func linkTargetIsHighlighted() {
let highlights = NorgHighlighter.highlights(in: "see {https://example.com}")
#expect(highlights.contains { $0.kind == .linkTarget })
#expect(highlights.contains { $0.kind == .linkDelimiter })
}
// MARK: - Auto-indentation (Return key)
/// Indentation at the end of `source` (where Return would land).
private func indent(after source: String) -> String {
NorgAutoIndent.indentation(for: source, newlineAt: (source as NSString).length)
}
@Test func indentsUnderTheOpenHeading() {
// `*** ` is three markers plus a space, so the text starts at column 4.
#expect(indent(after: "*** Headline") == " ")
#expect(indent(after: "* Top") == " ")
}
@Test func plainTextWithoutAHeadingHasNoIndent() {
#expect(indent(after: "just a paragraph") == "")
#expect(indent(after: "") == "")
}
@Test func indentPersistsAcrossContentLines() {
// A paragraph under the heading keeps the heading's indent on the next line.
#expect(indent(after: "* A\nsome prose") == " ")
}
@Test func indentFollowsTheDeepestOpenHeading() {
#expect(indent(after: "* A\n** B\n*** C") == " ")
}
@Test func strongDelimiterClosesAllHeadings() {
#expect(indent(after: "* A\n** B\n===\nmore") == "")
}
@Test func weakDelimiterReturnsToTheParentHeading() {
// `---` closes `** B`, leaving `* A` open → align under its text (column 2).
#expect(indent(after: "* A\n** B\n---\nmore") == " ")
}
@Test func horizontalRuleLeavesHeadingScopeIntact() {
#expect(indent(after: "* A\n___\nmore") == " ")
}
|