import AppKit import ConjureKit import Testing @testable import Conjure @Test @MainActor func noteFormattingHidesSyntaxButPreservesMarkdownAndLiteralCode() throws { _ = NSApplication.shared let editor = NotesTextView(frame: NSRect(x: 0, y: 0, width: 600, height: 800)) let source = "# Heading\n## Subheading\n*italic* **bold** _emphasis_ __strong__ ~~gone~~ `*literal*`\n> Quote\n[Label](https://example.com)\n```text\n# Code *literal*\n```\nUnmatched * and \\*escaped*" editor.setMarkdownContents(NSAttributedString(string: source)) MarkdownHighlighting.apply(to: editor) editor.refreshTasks() let layout = try #require(editor.layoutManager) let container = try #require(editor.textContainer) layout.ensureLayout(for: container) func visibleText() -> String { var result = "" let text = editor.string as NSString for index in 0.. [NSView] { [view] + view.subviews.flatMap(descendants) } let editor = try #require(descendants(controller.view).compactMap { $0 as? NotesTextView }.first) window.makeFirstResponder(editor) editor.insertText("# Title\n*word*", replacementRange: NSRange(location: 0, length: 0)) editor.setSelectedRange(NSRange(location: 0, length: 0)) editor.insertText("A", replacementRange: editor.selectedRange()) #expect(editor.markdownString == "# ATitle\n*word*") editor.breakUndoCoalescing() editor.undoManager?.removeAllActions() editor.undoManager?.beginUndoGrouping() editor.setSelectedRange(NSRange(location: editor.string.utf16.count, length: 0)) editor.deleteBackward(nil) editor.undoManager?.endUndoGrouping() #expect(editor.markdownString == "# ATitle\n*wor*") editor.undoManager?.undo() #expect(editor.markdownString == "# ATitle\n*word*") let word = (editor.string as NSString).range(of: "word") editor.setSelectedRange(NSRange(location: word.location, length: 0)) editor.moveRight(nil) #expect(editor.selectedRange().location == word.location + 1) editor.setSelectedRange(NSRange(location: NSMaxRange(word), length: 0)) editor.moveRight(nil) #expect(editor.selectedRange().location == editor.string.utf16.count) editor.setSelectedRange(NSRange(location: word.location, length: 0)) let italic = try #require(descendants(controller.view).compactMap { $0 as? NSButton }.first { $0.accessibilityLabel() == "Italic" }) // Selecting the body and toggling formatting must remove its hidden delimiters. editor.setSelectedRange(word) italic.performClick(nil) #expect(editor.markdownString == "# ATitle\nword") #expect(controller.save()) #expect(try NotesStore(directory: directory).notes.first?.contents.string == "# ATitle\nword") } @Test @MainActor func notesFormattingShortcutsToggleCombinedStylesAndRespectPopupFocus() throws { _ = NSApplication.shared let suite = "NotesFormattingKeys.\(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 launcher = LauncherWindowController( history: LaunchHistory(defaults: defaults, now: { Date(timeIntervalSinceReferenceDate: 12_000) }), defaults: defaults, notesDirectory: directory) let window = try #require(launcher.window) defer { launcher.dismiss(restoringFocus: false) } launcher.present(command: .notes) let content = try #require(window.contentView) func descendants(_ view: NSView) -> [NSView] { [view] + view.subviews.flatMap(descendants) } let editor = try #require(descendants(content).compactMap { $0 as? NotesTextView }.first) let controller = try #require(editor.delegate as? NotesController) func shortcut(_ key: String, shift: Bool = false, option: Bool = false) throws -> Bool { var flags: NSEvent.ModifierFlags = .command if shift { flags.insert(.shift) } if option { flags.insert(.option) } let event = try #require(NSEvent.keyEvent(with: .keyDown, location: .zero, modifierFlags: flags, timestamp: 1, windowNumber: window.windowNumber, context: nil, characters: shift ? key.uppercased() : key, charactersIgnoringModifiers: shift ? key.uppercased() : key, isARepeat: false, keyCode: 0)) return window.performKeyEquivalent(with: event) } editor.insertText("fox 🦊", replacementRange: editor.selectedRange()) editor.setSelectedRange(NSRange(location: 0, length: editor.string.utf16.count)) #expect(try shortcut("b")) #expect(editor.markdownString == "**fox 🦊**") #expect(try shortcut("i")) #expect(editor.markdownString == "***fox 🦊***") let font = try #require(editor.textStorage?.attribute(.font, at: editor.selectedRange().location, effectiveRange: nil) as? NSFont) let traits = NSFontManager.shared.traits(of: font) #expect(traits.contains(.boldFontMask) && traits.contains(.italicFontMask)) #expect(try shortcut("i")) #expect(editor.markdownString == "**fox 🦊**") #expect(try shortcut("b")) #expect(editor.markdownString == "fox 🦊") let hide = try #require(descendants(content).compactMap { $0 as? NSButton }.first { $0.toolTip == "Hide formatting" }) hide.performClick(nil) #expect(try shortcut("x", shift: true)) #expect(editor.markdownString == "~~fox 🦊~~") #expect(try shortcut("x", shift: true)) #expect(editor.markdownString == "fox 🦊") #expect(!(try shortcut("b", option: true))) #expect(editor.markdownString == "fox 🦊") editor.breakUndoCoalescing() editor.undoManager?.removeAllActions() editor.undoManager?.beginUndoGrouping() #expect(try shortcut("e")) editor.undoManager?.endUndoGrouping() #expect(editor.markdownString == "`fox 🦊`") editor.undoManager?.undo() #expect(editor.markdownString == "fox 🦊") #expect(try shortcut("k")) let search = try #require(descendants(content).compactMap { $0 as? NSSearchField }.first { $0.accessibilityLabel() == "Search notes actions" }) #expect(window.firstResponder !== editor) _ = try shortcut("b") #expect(editor.markdownString == "fox 🦊") #expect(search.stringValue.isEmpty) #expect(controller.handle(#selector(NSResponder.cancelOperation(_:)))) #expect(controller.save()) #expect(try NotesStore(directory: directory).notes.first?.contents.string == "fox 🦊") }