Sapling/Tests/SaplingEditorTests/EditorLargeDocumentValidationTests.swift

107 lines
4.4 KiB
Swift
Raw Normal View History

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")
}
}