import XCTest @testable import SaplingEditor final class EditorCursorRegressionTests: XCTestCase { func testCursorMovementUpdatesActiveLineWithoutChangingSelectionLength() { let source = "Alpha\nBeta\nGamma" var state = EditorState(document: document(source: source)) state.updateSelection(EditorSelection(location: location(of: "Beta", in: source), length: 0)) XCTAssertEqual(state.selection, EditorSelection(location: 6, length: 0)) XCTAssertEqual(state.activeLineIndex, 1) XCTAssertEqual(state.activeColumnNumber, 1) } func testSelectionChangeWithinLinePreservesActiveLine() { let source = "Alpha\nBeta\nGamma" var state = EditorState(document: document(source: source)) state.updateSelection(EditorSelection(location: location(of: "eta", in: source), length: 2)) XCTAssertEqual(state.selection.length, 2) XCTAssertEqual(state.activeLineIndex, 1) XCTAssertEqual(state.activeColumnNumber, 2) } func testMultiLineSelectionUsesSelectionAnchorForActiveLine() { let source = "Alpha\nBeta\nGamma" var state = EditorState(document: document(source: source)) let anchor = location(of: "Beta", in: source) let end = location(of: "Gamma", in: source) + "Gamma".utf16.count state.updateSelection(EditorSelection(location: anchor, length: end - anchor)) XCTAssertEqual(state.selection.location, anchor) XCTAssertEqual(state.selection.length, 10) XCTAssertEqual(state.activeLineIndex, 1) } func testInsertionKeepsCursorAndActiveLineOnEditedLine() { let source = "Alpha\nBeta\nGamma" let updated = "Alpha\nBeta updated\nGamma" var state = EditorState(document: document(source: source)) state.updateSelection(EditorSelection(location: location(of: "Beta", in: source) + 4, length: 0)) state.updateSource(updated) state.updateSelection(EditorSelection(location: location(of: "updated", in: updated) + 7, length: 0)) XCTAssertEqual(state.activeLineIndex, 1) XCTAssertEqual(state.activeColumnNumber, 13) } func testDeletionClampsSelectionAndActiveLine() { let source = "Alpha\nBeta\nGamma" var state = EditorState(document: document(source: source)) state.updateSelection(EditorSelection(location: source.utf16.count, length: 0)) state.updateSource("Alpha") XCTAssertEqual(state.selection.location, 5) XCTAssertEqual(state.selection.length, 0) XCTAssertEqual(state.activeLineIndex, 0) } func testLineBreakMovesActiveLineAfterNativeSelectionUpdate() { let source = "Alpha Beta" let updated = "Alpha\n Beta" var state = EditorState(document: document(source: source)) state.updateSelection(EditorSelection(location: 5, length: 0)) state.updateSource(updated) state.updateSelection(EditorSelection(location: 6, length: 0)) XCTAssertEqual(state.activeLineIndex, 1) XCTAssertEqual(state.activeColumnNumber, 1) } func testActiveLineTransitionsAcrossDocument() { let source = "One\nTwo\nThree\nFour" var state = EditorState(document: document(source: source)) state.updateSelection(EditorSelection(location: location(of: "Four", in: source), length: 0)) XCTAssertEqual(state.activeLineIndex, 3) state.updateSelection(EditorSelection(location: location(of: "One", in: source), length: 0)) XCTAssertEqual(state.activeLineIndex, 0) } private func document(source: String) -> EditorDocument { EditorDocument( url: URL(fileURLWithPath: "/tmp/EditorCursorRegressionTests.md"), title: "EditorCursorRegressionTests", source: source ) } private func location(of needle: String, in source: String) -> Int { (source as NSString).range(of: needle).location } }