aboutsummaryrefslogtreecommitdiff
path: root/Tests/NorgEditorTests/NorgEditorTests.swift
diff options
context:
space:
mode:
authorRuben Beltran del Rio <jj@r.bdr.sh>2026-06-19 19:16:25 +0200
committerRuben Beltran del Rio <jj@r.bdr.sh>2026-06-19 22:32:15 +0200
commit25fc48fb17b53fe1c155c4f53f673ed91fdd8f74 (patch)
treede96add2a7ae8c5cc79a354fbffa26e80349cc15 /Tests/NorgEditorTests/NorgEditorTests.swift
Initial extraction from Norganize
Diffstat (limited to 'Tests/NorgEditorTests/NorgEditorTests.swift')
-rw-r--r--Tests/NorgEditorTests/NorgEditorTests.swift100
1 files changed, 100 insertions, 0 deletions
diff --git a/Tests/NorgEditorTests/NorgEditorTests.swift b/Tests/NorgEditorTests/NorgEditorTests.swift
new file mode 100644
index 0000000..6a6caf7
--- /dev/null
+++ b/Tests/NorgEditorTests/NorgEditorTests.swift
@@ -0,0 +1,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") == " ")
+}