aboutsummaryrefslogtreecommitdiff
path: root/Tests/NorgKitTests/Parsers/TaskScannerTests.swift
diff options
context:
space:
mode:
Diffstat (limited to 'Tests/NorgKitTests/Parsers/TaskScannerTests.swift')
-rw-r--r--Tests/NorgKitTests/Parsers/TaskScannerTests.swift110
1 files changed, 110 insertions, 0 deletions
diff --git a/Tests/NorgKitTests/Parsers/TaskScannerTests.swift b/Tests/NorgKitTests/Parsers/TaskScannerTests.swift
new file mode 100644
index 0000000..58226fd
--- /dev/null
+++ b/Tests/NorgKitTests/Parsers/TaskScannerTests.swift
@@ -0,0 +1,110 @@
+import Foundation
+import Testing
+
+@testable import NorgKit
+
+struct TaskScannerTests {
+
+ private let file = URL(filePath: "/tmp/notes.norg")
+
+ @Test func scansTasksWithLineNumbers() {
+ let content = """
+ * Project
+ - ( ) first task
+ - (x) done task
+ plain text
+ ~ (-) ordered pending
+ """
+ let tasks = TaskScanner.scan(content: content, fileURL: file)
+ #expect(tasks.count == 3)
+ #expect(tasks[0].line == 1)
+ #expect(tasks[0].status == .undone)
+ #expect(tasks[0].text == "first task")
+ #expect(tasks[1].status == .done)
+ #expect(tasks[2].status == .pending)
+ }
+
+ @Test func ignoresNonTaskLines() {
+ let tasks = TaskScanner.scan(content: "- just a list item\n* heading", fileURL: file)
+ #expect(tasks.isEmpty)
+ }
+
+ @Test func parsesSingleLineIntoTask() {
+ let task = TaskScanner.task(in: "- (!) urgent thing", fileURL: file, line: 9)
+ #expect(task?.line == 9)
+ #expect(task?.status == .urgent)
+ #expect(task?.text == "urgent thing")
+ #expect(task?.fileURL == file)
+ }
+
+ @Test func parsesSingleLineReturnsNilForNonTask() {
+ #expect(TaskScanner.task(in: "- plain item", fileURL: file, line: 0) == nil)
+ }
+
+ @Test func scansEveryStatusBearingModifier() {
+ // The Norg spec allows any detached modifier to carry a TODO status. We
+ // model the structural, nestable, and range-able modifiers (`%`
+ // attributes are intentionally not treated as tasks).
+ let content = """
+ * (x) heading task
+ - ( ) list task
+ ~ (-) ordered task
+ > (!) quote task
+ $ (?) definition task
+ ^ (=) footnote task
+ : (+) table cell task
+ """
+ let tasks = TaskScanner.scan(content: content, fileURL: file)
+ #expect(tasks.count == 7)
+ #expect(
+ tasks.map(\.status) == [
+ .done, .undone, .pending, .urgent, .needsInput, .onHold, .recurring,
+ ])
+ #expect(tasks[3].text == "quote task")
+ #expect(tasks[4].text == "definition task")
+ }
+
+ @Test func stripsInlineMarkupFromTaskText() {
+ let tasks = TaskScanner.scan(content: "- ( ) buy *milk*", fileURL: file)
+ #expect(tasks.first?.text == "buy milk")
+ }
+
+ @Test func updatesStatusMarker() {
+ let content = "- ( ) toggle me"
+ let updated = TaskScanner.updatedContent(content, line: 0, to: .done)
+ #expect(updated == "- (x) toggle me")
+ }
+
+ @Test func updatesStatusPreservingIndentation() {
+ let content = "* Heading\n -- (x) nested"
+ let updated = TaskScanner.updatedContent(content, line: 1, to: .cancelled)
+ #expect(updated == "* Heading\n -- (_) nested")
+ }
+
+ @Test func updateReturnsNilWhenNoMarker() {
+ #expect(TaskScanner.updatedContent("plain line", line: 0, to: .done) == nil)
+ }
+
+ @Test func updateReturnsNilForOutOfRangeLine() {
+ #expect(TaskScanner.updatedContent("- ( ) a", line: 5, to: .done) == nil)
+ }
+
+ @Test func updatesStatusOnLaterLineLeavingRestIntact() {
+ let content = "* heading\n> ( ) quote task\n- plain item"
+ let updated = TaskScanner.updatedContent(content, line: 1, to: .urgent)
+ #expect(updated == "* heading\n> (!) quote task\n- plain item")
+ }
+
+ @Test func scansCrlfContent() {
+ let tasks = TaskScanner.scan(content: "- ( ) first\r\n- (x) second\r\n", fileURL: file)
+ #expect(tasks.count == 2)
+ #expect(tasks[0].text == "first")
+ #expect(tasks[1].status == .done)
+ }
+
+ @Test func updatePreservesCrlfLineEndings() {
+ let content = "- ( ) a\r\n- ( ) b"
+ let updated = TaskScanner.updatedContent(content, line: 1, to: .done)
+ #expect(updated == "- ( ) a\r\n- (x) b")
+ }
+}