import XCTest @testable import SaplingEditor #if os(macOS) import AppKit @MainActor final class HybridMarkdownLiveEditorHarnessTests: XCTestCase { func testLiveInitialFirstResponderDoesNotShowHeadingSourceBeforeUserInteraction() { let harness = HybridMarkdownLiveEditorHarness(source: "# Heading\nParagraph") harness.simulateLaunchFirstResponder() XCTAssertTrue(harness.headingMarkerIsHidden()) } func testLiveParagraphGeometryReturnsAfterClickAndFocusAway() throws { let source = """ # Heading Paragraph with **bold**, *italic*, `code`, and [Link](https://example.com) markers. Outro """ let harness = HybridMarkdownLiveEditorHarness(source: source) harness.simulateLaunchFirstResponder() let initialPoint = try XCTUnwrap(harness.point(for: "Paragraph")) let paragraphLocation = (source as NSString).range(of: "bold").location harness.setSelection(NSRange(location: paragraphLocation, length: 0)) harness.simulateFocusAway() let finalPoint = try XCTUnwrap(harness.point(for: "Paragraph")) XCTAssertEqual(initialPoint.x, finalPoint.x, accuracy: 0.001) XCTAssertEqual(initialPoint.y, finalPoint.y, accuracy: 0.001) } func testLiveCheckboxClickPreservesSelectionActiveLineAndGeometry() throws { let source = "Editing here\n* [ ] Move with arrow keys." let editingRange = (source as NSString).range(of: "Editing") let harness = HybridMarkdownLiveEditorHarness( source: source, selectedRange: NSRange(location: editingRange.location, length: 0) ) harness.simulateLaunchFirstResponder() harness.setSelection(NSRange(location: editingRange.location, length: 0)) let selectionBefore = harness.selectedRange() let activeLineBefore = harness.effectiveActiveLineIndex() let labelPointBefore = try XCTUnwrap(harness.point(for: "Move")) let checkboxFrameBefore = try XCTUnwrap(harness.checklistButtonFrame(lineIndex: 1)) harness.clickRenderedCheckbox(lineIndex: 1) let labelPointAfter = try XCTUnwrap(harness.point(for: "Move")) let checkboxFrameAfter = try XCTUnwrap(harness.checklistButtonFrame(lineIndex: 1)) XCTAssertEqual(harness.selectedRange(), selectionBefore) XCTAssertEqual(harness.effectiveActiveLineIndex(), activeLineBefore) XCTAssertTrue(harness.source().contains("* [x] Move with arrow keys.")) XCTAssertEqual(labelPointBefore.x, labelPointAfter.x, accuracy: 0.001) XCTAssertEqual(labelPointBefore.y, labelPointAfter.y, accuracy: 0.001) XCTAssertEqual(checkboxFrameBefore.origin.x, checkboxFrameAfter.origin.x, accuracy: 0.001) XCTAssertEqual(checkboxFrameBefore.origin.y, checkboxFrameAfter.origin.y, accuracy: 0.001) XCTAssertEqual(checkboxFrameBefore.size.width, checkboxFrameAfter.size.width, accuracy: 0.001) XCTAssertEqual(checkboxFrameBefore.size.height, checkboxFrameAfter.size.height, accuracy: 0.001) } func testInitialChecklistOverlayTracksFirstLiveLayoutPass() throws { let source = """ ## Navigation Checklist Use this section for quick keyboard testing. * [ ] Move with arrow keys. * [ ] Jump by word with Option + Arrow. * [ ] Extend selection with Shift + Arrow. * [ ] Select across multiple lines. * [ ] Use Home, End, Page Up, and Page Down. * [ ] Type into the active line after moving quickly. """ let harness = HybridMarkdownLiveEditorHarness(source: source, initialWidth: 640) let initialGaps = try Dictionary(uniqueKeysWithValues: (4...9).map { lineIndex in (lineIndex, try XCTUnwrap(harness.checklistLabelGap(lineIndex: lineIndex))) }) harness.simulateLayout(width: 900) for lineIndex in 4...9 { let alignmentDelta = try XCTUnwrap(harness.checklistAlignmentDelta(lineIndex: lineIndex)) let labelGap = try XCTUnwrap(harness.checklistLabelGap(lineIndex: lineIndex)) XCTAssertLessThan(alignmentDelta, 8, "Checklist overlay for line \(lineIndex) is not aligned with its label") XCTAssertEqual( labelGap, initialGaps[lineIndex] ?? labelGap, accuracy: 0.001, "Checklist overlay for line \(lineIndex) did not track the label through the first layout pass" ) } } func testLiveMultiLineSelectionUsesEditableRegion() throws { let source = "# Heading\nParagraph\n* [ ] Task\nOutro" let nsSource = source as NSString let selectionStart = nsSource.range(of: "#").location let selectionEnd = nsSource.range(of: "Task").upperBound let taskLineIndex = 2 let harness = HybridMarkdownLiveEditorHarness(source: source) harness.simulateLaunchFirstResponder() harness.setSelection(NSRange(location: selectionStart, length: selectionEnd - selectionStart)) XCTAssertFalse(harness.characterIsHidden(at: nsSource.range(of: "#").location)) XCTAssertNil(harness.checklistButtonFrame(lineIndex: taskLineIndex)) harness.setSelection(NSRange(location: nsSource.range(of: "Outro").location, length: 0)) XCTAssertTrue(harness.characterIsHidden(at: nsSource.range(of: "#").location)) XCTAssertNotNil(harness.checklistButtonFrame(lineIndex: taskLineIndex)) } func testLiveCodeBlockSelectionUsesWholeBlockEditableRegion() { let source = "Intro\n```swift\nlet value = 42\n```" let nsSource = source as NSString let harness = HybridMarkdownLiveEditorHarness(source: source) let cursorLocation = nsSource.range(of: "value").location harness.simulateLaunchFirstResponder() harness.setSelection(NSRange(location: cursorLocation, length: 0)) XCTAssertFalse(harness.characterIsHidden(at: nsSource.range(of: "```swift").location)) XCTAssertFalse(harness.characterIsHidden(at: nsSource.range(of: "```", options: .backwards).location)) harness.setSelection(NSRange(location: nsSource.range(of: "Intro").location, length: 0)) XCTAssertTrue(harness.characterIsHidden(at: nsSource.range(of: "```swift").location)) XCTAssertTrue(harness.characterIsHidden(at: nsSource.range(of: "```", options: .backwards).location)) } func testLiveRenderedCodeBlockUsesSingleContainerPresentation() throws { let source = """ Intro ```swift struct Example { let value = 42 } ``` Outro """ let nsSource = source as NSString let harness = HybridMarkdownLiveEditorHarness(source: source, initialWidth: 700) harness.simulateLaunchFirstResponder() XCTAssertEqual(harness.codeBlockContainerCount(), 1) XCTAssertEqual(harness.codeBlockContainerLabel(containing: 2), "Swift") let initialFrame = try XCTUnwrap(harness.codeBlockContainerFrame(containing: 2)) XCTAssertGreaterThan(initialFrame.height, 80) XCTAssertGreaterThan(initialFrame.width, 500) XCTAssertTrue(harness.characterIsHidden(at: nsSource.range(of: "```swift").location)) XCTAssertTrue(harness.characterIsHidden(at: nsSource.range(of: "```", options: .backwards).location)) harness.setSelection(NSRange(location: nsSource.range(of: "value").location, length: 0)) XCTAssertEqual(harness.codeBlockContainerCount(), 0) XCTAssertFalse(harness.characterIsHidden(at: nsSource.range(of: "```swift").location)) harness.setSelection(NSRange(location: nsSource.range(of: "Intro").location, length: 0)) XCTAssertEqual(harness.codeBlockContainerCount(), 1) let restoredFrame = try XCTUnwrap(harness.codeBlockContainerFrame(containing: 2)) XCTAssertEqual(initialFrame.origin.x, restoredFrame.origin.x, accuracy: 0.001) XCTAssertEqual(initialFrame.size.width, restoredFrame.size.width, accuracy: 0.001) } } #endif private extension NSRange { var upperBound: Int { location + length } }