fix: skip invisible list separator caret stops
This commit is contained in:
@@ -514,6 +514,129 @@ func atomicListMarkersExpandAtDeletionBoundariesAndNormalizeLineJoinsInOneUndo()
|
||||
}
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func listNavigationSkipsHiddenSeparatorsAndKeepsSelectionAnchorsAndDeletionUndo() throws {
|
||||
_ = NSApplication.shared
|
||||
let suite = "NotesVisualCaret.\(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 board = NSPasteboard.withUniqueName()
|
||||
defer { board.releaseGlobally() }
|
||||
let body = "🦊e\u{301} z"
|
||||
// Prefix source, rendered prefix, marker, separator, backward/forward expansions.
|
||||
let fixtures = [
|
||||
("", "", "-", " \t", "", ""),
|
||||
("\t", "\t", "+", "\t ", "", ""),
|
||||
(" ", " ", "009)", "\t \t", "009", "09)"),
|
||||
("- parent\n ", "\u{fffc} parent\n ", "- [X]", " \t ", "- [X", " [X]"),
|
||||
("\t", "\t", "12)\t[]", "\t \t", "12)\t[", "2)\t[]"),
|
||||
]
|
||||
for (prefix, renderedPrefix, marker, separator, backwardExpansion, forwardExpansion) in fixtures {
|
||||
let source = prefix + marker + separator + body
|
||||
editor.insertText(source, replacementRange: NSRange(location: 0, length: editor.string.utf16.count))
|
||||
let rendered = renderedPrefix + "\u{fffc}" + separator + body
|
||||
#expect(editor.string == rendered)
|
||||
let before = renderedPrefix.utf16.count
|
||||
let start = before + 1 + separator.utf16.count
|
||||
func caret(_ index: Int) { editor.setSelectedRange(NSRange(location: index, length: 0)) }
|
||||
func expectSelection(_ location: Int, _ length: Int = 0) {
|
||||
#expect(editor.selectedRange() == NSRange(location: location, length: length))
|
||||
}
|
||||
caret(before)
|
||||
editor.moveRight(nil)
|
||||
expectSelection(start)
|
||||
editor.moveRight(nil)
|
||||
expectSelection(start + 2)
|
||||
editor.moveRight(nil)
|
||||
expectSelection(start + 4)
|
||||
editor.moveRight(nil)
|
||||
expectSelection(start + 5) // Visible body spaces still have individual stops.
|
||||
editor.moveLeft(nil)
|
||||
expectSelection(start + 4)
|
||||
editor.moveLeft(nil)
|
||||
expectSelection(start + 2)
|
||||
editor.moveLeft(nil)
|
||||
expectSelection(start)
|
||||
editor.moveLeft(nil)
|
||||
expectSelection(before)
|
||||
if before > 0 {
|
||||
editor.moveLeft(nil)
|
||||
expectSelection(before - 1)
|
||||
editor.moveRight(nil)
|
||||
expectSelection(before)
|
||||
}
|
||||
editor.moveRightAndModifySelection(nil)
|
||||
expectSelection(before, start - before)
|
||||
#expect(editor.writeSelection(to: board, types: editor.writablePasteboardTypes))
|
||||
#expect(board.string(forType: .string) == marker + separator)
|
||||
editor.moveRightAndModifySelection(nil)
|
||||
expectSelection(before, start + 2 - before)
|
||||
editor.moveRightAndModifySelection(nil)
|
||||
expectSelection(before, start + 4 - before)
|
||||
editor.moveLeftAndModifySelection(nil)
|
||||
expectSelection(before, start + 2 - before)
|
||||
editor.moveLeftAndModifySelection(nil)
|
||||
expectSelection(before, start - before)
|
||||
editor.moveLeftAndModifySelection(nil)
|
||||
expectSelection(before)
|
||||
if before > 0 {
|
||||
editor.moveLeftAndModifySelection(nil)
|
||||
expectSelection(before - 1, 1)
|
||||
editor.moveRightAndModifySelection(nil)
|
||||
expectSelection(before)
|
||||
}
|
||||
caret(start)
|
||||
editor.moveLeftAndModifySelection(nil)
|
||||
expectSelection(before, start - before)
|
||||
editor.moveRightAndModifySelection(nil)
|
||||
expectSelection(start)
|
||||
editor.moveRightAndModifySelection(nil)
|
||||
expectSelection(start, 2)
|
||||
editor.moveLeftAndModifySelection(nil)
|
||||
expectSelection(start)
|
||||
editor.moveLeftAndModifySelection(nil)
|
||||
expectSelection(before, start - before)
|
||||
editor.moveRightAndModifySelection(nil)
|
||||
expectSelection(start)
|
||||
editor.setSelectedRange(NSRange(location: 0, length: rendered.utf16.count))
|
||||
board.clearContents()
|
||||
#expect(editor.writeSelection(to: board, types: editor.writablePasteboardTypes))
|
||||
#expect(board.string(forType: .string) == source)
|
||||
#expect(controller.save())
|
||||
let persisted = try NotesStore(directory: directory)
|
||||
#expect(persisted.notes.contains { $0.contents.string == source })
|
||||
for backward in [true, false] {
|
||||
editor.setMarkdownContents(NSAttributedString(string: source))
|
||||
editor.breakUndoCoalescing()
|
||||
editor.undoManager?.removeAllActions()
|
||||
caret(before)
|
||||
if backward { editor.moveRight(nil); editor.deleteBackward(nil) }
|
||||
else { editor.deleteForward(nil) }
|
||||
editor.breakUndoCoalescing()
|
||||
let expansion = backward ? backwardExpansion : forwardExpansion
|
||||
#expect(editor.markdownString == prefix + expansion + separator + body)
|
||||
#expect(editor.selectedRange() == NSRange(
|
||||
location: before + (backward ? expansion.utf16.count : 0), length: 0))
|
||||
editor.undoManager?.undo()
|
||||
#expect(editor.string == rendered)
|
||||
#expect(editor.markdownString == source)
|
||||
#expect(editor.undoManager?.canUndo == false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func atomicTasksCopySelectAndPersistMarkdownAlongsideUnicodeAndImages() throws {
|
||||
_ = NSApplication.shared
|
||||
@@ -558,11 +681,11 @@ func atomicTasksCopySelectAndPersistMarkdownAlongsideUnicodeAndImages() throws {
|
||||
#expect(editor.renderedRange(for: (source.string as NSString).range(of: "🦊")) == (rendered as NSString).range(of: "🦊"))
|
||||
editor.setSelectedRange(NSRange(location: token.location, length: 0))
|
||||
editor.moveRight(nil)
|
||||
#expect(editor.selectedRange() == NSRange(location: NSMaxRange(token), length: 0))
|
||||
#expect(editor.selectedRange() == NSRange(location: NSMaxRange(token) + 1, length: 0))
|
||||
editor.moveLeft(nil)
|
||||
#expect(editor.selectedRange() == NSRange(location: token.location, length: 0))
|
||||
editor.moveRightAndModifySelection(nil)
|
||||
#expect(editor.selectedRange() == token)
|
||||
#expect(editor.selectedRange() == NSRange(location: token.location, length: 2))
|
||||
let board = NSPasteboard.withUniqueName()
|
||||
defer { board.releaseGlobally() }
|
||||
editor.setSelectedRange(token)
|
||||
|
||||
Reference in New Issue
Block a user