diff options
Diffstat (limited to 'Tests/NorgKitTests/Parsers/TaskScannerTests.swift')
| -rw-r--r-- | Tests/NorgKitTests/Parsers/TaskScannerTests.swift | 110 |
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") + } +} |