import XCTest @testable import SaplingEditor final class EditorLargeDocumentValidationTests: XCTestCase { func testLargeDocumentOpenAndDirtyRenderPlanning() { for lineCount in [1_000, 5_000, 10_000] { let source = Self.prototypeDocument(lineCount: lineCount) let activeLineIndex = lineCount / 2 let openMeasurement = elapsedTime { _ = EditorState( document: document(source: source, lineCount: lineCount), activeLineIndex: activeLineIndex ) } let updatedSource = Self.replacingLine( activeLineIndex, in: source, with: "Line \(activeLineIndex + 1) edited with **bold** and `inline code`." ) let updateMeasurement = elapsedTime { var state = EditorState( document: document(source: source, lineCount: lineCount), activeLineIndex: activeLineIndex ) state.updateSource(updatedSource) _ = EditorDirtyLineInvalidator.plan( previousText: source, currentText: updatedSource, previousActiveLineIndex: activeLineIndex, currentActiveLineIndex: activeLineIndex ) } let plan = EditorDirtyLineInvalidator.plan( previousText: source, currentText: updatedSource, previousActiveLineIndex: activeLineIndex, currentActiveLineIndex: activeLineIndex ) let dirtyRenderMeasurement = elapsedTime { let lines = EditorActiveLineTracker.lines(from: updatedSource, activeLineIndex: activeLineIndex) let dirty = Set(plan.dirtyLineIndexes) let renderer = HybridMarkdownLineRenderer() _ = lines .filter { dirty.contains($0.index) } .map(renderer.renderPlan(for:)) } XCTAssertEqual(EditorActiveLineTracker.lines(from: source, activeLineIndex: activeLineIndex).count, lineCount) XCTAssertFalse(plan.isFullRender) XCTAssertLessThanOrEqual(plan.dirtyLineCount, 3) XCTAssertLessThan(openMeasurement, 0.25, "Opening \(lineCount) generated lines should remain interactive.") XCTAssertLessThan(updateMeasurement, 0.50, "State update for \(lineCount) lines should remain bounded.") XCTAssertLessThan(dirtyRenderMeasurement, 0.05, "Dirty render planning should not scale with the full document.") } } func testLargeDocumentActiveLineNavigationDoesNotScheduleFullRender() { let source = Self.prototypeDocument(lineCount: 10_000) let plan = EditorDirtyLineInvalidator.plan( previousText: source, currentText: source, previousActiveLineIndex: 250, currentActiveLineIndex: 9_750 ) XCTAssertEqual(plan.reason, .activeLineChange) XCTAssertFalse(plan.isFullRender) XCTAssertEqual(plan.dirtyLineIndexes, [250, 9_750]) } private func document(source: String, lineCount: Int) -> EditorDocument { EditorDocument( url: URL(fileURLWithPath: "/tmp/EditorLargeDocument-\(lineCount).md"), title: "EditorLargeDocument-\(lineCount)", source: source ) } private func elapsedTime(_ operation: () -> Void) -> TimeInterval { let start = Date() operation() return Date().timeIntervalSince(start) } private static func prototypeDocument(lineCount: Int) -> String { (1...lineCount).map { index in if index.isMultiple(of: 25) { return "## Section \(index / 25)" } if index.isMultiple(of: 5) { return "Line \(index) uses **bold** context and `inline code` for scanning." } return "Line \(index) is a realistic paragraph with *light emphasis* and enough text to wrap naturally." } .joined(separator: "\n") } private static func replacingLine(_ lineIndex: Int, in source: String, with replacement: String) -> String { var lines = source.components(separatedBy: "\n") lines[lineIndex] = replacement return lines.joined(separator: "\n") } }