import AppKit import ConjureKit import Testing @testable import Conjure @Test @MainActor func headingShortcutsConsumeSyntaxAndPersistAllSixLevels() throws { _ = NSApplication.shared let suite = "NotesHeadings.\(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: 600, height: 500), 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) for level in 1...6 { editor.setDocumentContents(NSAttributedString(string: "Title")) editor.setSelectedRange(NSRange(location: 0, length: 0)) editor.beginInsertionSession() for character in String(repeating: "#", count: level) + " " { editor.insertText(String(character), replacementRange: editor.selectedRange()) } editor.endInsertionSession() #expect(editor.string == "Title") #expect(editor.selectedRange() == NSRange(location: 0, length: 0)) #expect(editor.documentContents.attribute(NoteAttributes.block, at: 0, effectiveRange: nil) as? String == "heading") #expect(editor.documentContents.attribute(NoteAttributes.headingLevel, at: 0, effectiveRange: nil) as? Int == level) editor.undoManager?.undo() #expect(editor.string == "Title") #expect(editor.documentContents.attribute(NoteAttributes.block, at: 0, effectiveRange: nil) == nil) editor.undoManager?.redo() #expect(editor.documentContents.attribute(NoteAttributes.headingLevel, at: 0, effectiveRange: nil) as? Int == level) #expect(controller.save()) let reloaded = try #require(NotesStore(directory: directory).notes.first) #expect(reloaded.contents.string == "Title") #expect(reloaded.contents.attribute(NoteAttributes.headingLevel, at: 0, effectiveRange: nil) as? Int == level) } } @Test @MainActor func headingReturnSplitsVisibleParagraphAndUndoRestoresItsSemantics() throws { _ = NSApplication.shared let editor = NotesTextView(frame: NSRect(x: 0, y: 0, width: 600, height: 500)) editor.allowsUndo = true let window = NSWindow(contentRect: editor.frame, styleMask: .borderless, backing: .buffered, defer: false) window.contentView = editor window.makeFirstResponder(editor) defer { window.orderOut(nil) } for position in [2, 5] { editor.setDocumentContents(NoteDocument.importMarkdown(NSAttributedString(string: "## Title"))) editor.setSelectedRange(NSRange(location: position, length: 0)) editor.undoManager?.removeAllActions() editor.insertNewline(nil) #expect(editor.string == (position == 2 ? "Ti\ntle" : "Title\n")) #expect(editor.selectedRange() == NSRange(location: position + 1, length: 0)) #expect(editor.documentContents.attribute(NoteAttributes.headingLevel, at: 0, effectiveRange: nil) as? Int == 2) editor.insertText("body", replacementRange: editor.selectedRange()) #expect(editor.documentContents.attribute(NoteAttributes.block, at: position + 1, effectiveRange: nil) == nil) editor.undoManager?.undo() editor.undoManager?.undo() #expect(editor.string == "Title") #expect(editor.documentContents.attribute(NoteAttributes.headingLevel, at: 0, effectiveRange: nil) as? Int == 2) } editor.setDocumentContents(NoteDocument.importMarkdown(NSAttributedString(string: "```text\n## Title\n```"))) editor.setSelectedRange(NSRange(location: 0, length: 0)) for character in "# " { editor.insertText(String(character), replacementRange: editor.selectedRange()) } #expect(editor.string == "# ## Title") #expect(editor.documentContents.attribute(NoteAttributes.block, at: 0, effectiveRange: nil) as? String == "code") #expect(editor.documentContents.attribute(NoteAttributes.headingLevel, at: 0, effectiveRange: nil) == nil) } @Test @MainActor func headingCommandsKeepCaretOnTheVisibleBodyLine() throws { _ = NSApplication.shared let editor = NotesTextView(frame: NSRect(x: 0, y: 0, width: 600, height: 500)) let window = NSWindow(contentRect: editor.frame, styleMask: .borderless, backing: .buffered, defer: false) window.contentView = editor window.makeFirstResponder(editor) defer { window.orderOut(nil) } for source in ["\n# Title", "Text\n\n## Title", "Text\n\n\n###### Title"] { editor.setDocumentContents(NoteDocument.importMarkdown(NSAttributedString(string: source))) let before = editor.string editor.setSelectedRange(NSRange(location: (editor.string as NSString).range(of: "Title").location, length: 0)) for command in [#selector(NSResponder.deleteBackward(_:)), #selector(NSResponder.insertNewline(_:))] { editor.doCommand(by: command) let title = (editor.string as NSString).range(of: "Title") #expect(editor.selectedRange() == NSRange(location: title.location, length: 0)) let caretRect = editor.firstRect(forCharacterRange: editor.selectedRange(), actualRange: nil) let titleRect = editor.firstRect(forCharacterRange: NSRange(location: title.location, length: 1), actualRange: nil) try #require(titleRect.height > 0) #expect(caretRect.origin == titleRect.origin) #expect(caretRect.height == titleRect.height) } #expect(editor.string == before) } }