feat: render interactive markdown task checkboxes

This commit is contained in:
Valdemar Lorenzen
2026-10-19 11:22:38 +02:00
parent 8e1546d502
commit 655f4b4d98
3 changed files with 152 additions and 1 deletions
@@ -322,3 +322,68 @@ func markdownListReturnContinuesExitsAndUndoesAtArbitraryDepths() throws {
let id = try #require(defaults.string(forKey: "selectedNote").flatMap(UUID.init(uuidString:)))
#expect(restored.notes.first { $0.id == id }?.contents.string == "7. saved\n8. ")
}
@Test @MainActor
func taskCheckboxesToggleMarkdownWithUndoPersistenceAndListContinuation() throws {
_ = NSApplication.shared
let suite = "NotesTasks.\(UUID().uuidString)"
let defaults = try #require(UserDefaults(suiteName: suite))
let directory = FileManager.default.temporaryDirectory.appendingPathComponent(suite)
defer {
defaults.removePersistentDomain(forName: suite)
try? FileManager.default.removeItem(at: directory)
}
let controller = NotesController(defaults: defaults, directory: directory)
let window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 750, height: 390),
styleMask: .borderless, backing: .buffered, defer: false)
window.contentViewController = controller
defer { _ = controller.close(); window.orderOut(nil) }
func descendants(_ view: NSView) -> [NSView] { [view] + view.subviews.flatMap(descendants) }
let editor = try #require(descendants(controller.view).compactMap { $0 as? NotesTextView }.first)
window.makeFirstResponder(editor)
let before = "- [ ] buy 🦊\n - [X] nested\n```md\n- [ ] literal\n```\nprose - [ ] not a task"
editor.insertText(before, replacementRange: editor.selectedRange())
editor.breakUndoCoalescing()
editor.undoManager?.removeAllActions()
let boxes = editor.subviews.compactMap { $0 as? NSButton }
#expect(boxes.map(\.state) == [.off, .on])
#expect(boxes.first?.cell?.accessibilityRole() == .checkBox)
#expect(boxes.first?.accessibilityLabel() == "buy 🦊")
let selection = editor.selectedRange()
let first = try #require(boxes.first)
first.performClick(nil)
let checked = before.replacingOccurrences(of: "- [ ] buy", with: "- [x] buy")
#expect(editor.string == checked)
#expect(first.state == .on)
#expect(editor.selectedRange() == selection)
let id = try #require(defaults.string(forKey: "selectedNote").flatMap(UUID.init(uuidString:)))
let saved = try #require(NotesStore(directory: directory).notes.first { $0.id == id })
#expect(saved.contents.string == checked)
#expect(saved.contents.attribute(.foregroundColor, at: 0, effectiveRange: nil) as? NSColor != .clear)
editor.undoManager?.undo()
#expect(editor.string == before)
#expect(first.state == .off)
editor.undoManager?.redo()
#expect(editor.string == checked)
#expect(first.state == .on)
editor.setSelectedRange(NSRange(location: 0, length: editor.string.utf16.count))
let board = NSPasteboard.withUniqueName()
defer { board.releaseGlobally() }
#expect(editor.writeSelection(to: board, types: editor.writablePasteboardTypes))
#expect(board.string(forType: .string) == checked)
let reopened = NotesController(defaults: defaults, directory: directory)
defer { _ = reopened.close() }
let restoredEditor = try #require(descendants(reopened.view).compactMap { $0 as? NotesTextView }.first)
#expect(restoredEditor.string == checked)
#expect(restoredEditor.subviews.compactMap { $0 as? NSButton }.map(\.state) == [.on, .on])
editor.insertText("- [x] finished", replacementRange: NSRange(location: 0, length: editor.string.utf16.count))
let enter = try #require(NSEvent.keyEvent(with: .keyDown, location: .zero, modifierFlags: [],
timestamp: 1, windowNumber: window.windowNumber, context: nil, characters: "\r",
charactersIgnoringModifiers: "\r", isARepeat: false, keyCode: 36))
editor.keyDown(with: enter)
#expect(editor.string == "- [x] finished\n- [ ] ")
#expect(editor.subviews.compactMap { $0 as? NSButton }.map(\.state) == [.on, .off])
editor.keyDown(with: enter)
#expect(editor.string == "- [x] finished\n")
#expect(editor.subviews.compactMap { $0 as? NSButton }.map(\.state) == [.on])
}