Sapling/Tests/SaplingEditorTests/DocumentPresentationStateTests.swift

129 lines
5.7 KiB
Swift
Raw Normal View History

import XCTest
@testable import SaplingEditor
final class DocumentPresentationStateTests: XCTestCase {
func testEveryLineHasExactlyOnePresentationState() {
let source = "# Heading\n* [x] Done\nParagraph"
let lineIndex = DocumentLineIndex(source: source)
let presentation = DocumentPresentationState(lineIndex: lineIndex, activeLineIndex: 1)
XCTAssertEqual(presentation.lines.count, 3)
XCTAssertEqual(presentation.lineState(at: 0), .rendered)
XCTAssertEqual(presentation.lineState(at: 1), .source)
XCTAssertEqual(presentation.lineState(at: 2), .rendered)
XCTAssertEqual(presentation.lines.filter { $0.state == .source }.map(\.line.index), [1])
XCTAssertEqual(presentation.lines.filter { $0.state == .rendered }.map(\.line.index), [0, 2])
}
func testPresentationStateIsDeterministicForSameDocumentAndActiveLine() {
let source = "# Heading\nThis has **bold** and `code`.\n* [ ] Todo"
let lineIndex = DocumentLineIndex(source: source)
let first = DocumentPresentationState(lineIndex: lineIndex, activeLineIndex: 2)
let second = DocumentPresentationState(lineIndex: lineIndex, activeLineIndex: 2)
XCTAssertEqual(first, second)
}
func testDocumentRenderModelIsIndependentOfActiveLinePresentation() {
let source = "# Heading\nThis has **bold** and `code`.\n* [ ] Todo"
let lineIndex = DocumentLineIndex(source: source)
let renderModel = DocumentRenderModel(lineIndex: lineIndex)
let firstPresentation = DocumentPresentationState(lineIndex: lineIndex, activeLineIndex: 0)
let secondPresentation = DocumentPresentationState(lineIndex: lineIndex, activeLineIndex: 2)
XCTAssertEqual(renderModel.snapshot, DocumentRenderModel(lineIndex: lineIndex).snapshot)
XCTAssertNotEqual(firstPresentation, secondPresentation)
XCTAssertTrue(renderModel.nodes.contains {
guard case .heading(let heading) = $0.element else { return false }
return heading.lineIndex == 0
})
}
func testInitialRenderModelContainsHeadingsWithoutInteraction() {
let source = "# Title\n\n## Section\nParagraph"
let renderModel = DocumentRenderModel(lineIndex: DocumentLineIndex(source: source))
let headingLevels = renderModel.nodes.compactMap { node -> Int? in
guard case .heading(let heading) = node.element else { return nil }
return heading.level
}
XCTAssertEqual(headingLevels, [1, 2])
}
func testRenderSnapshotChangesOnlyWhenMarkdownChanges() {
let source = "# Heading\n* [ ] Move with arrow keys."
let originalModel = DocumentRenderModel(lineIndex: DocumentLineIndex(source: source))
let repeatedModel = DocumentRenderModel(lineIndex: DocumentLineIndex(source: source))
let toggledSource = (source as NSString).replacingCharacters(
in: NSRange(location: (source as NSString).range(of: "[ ]").location, length: 3),
with: "[x]"
)
let toggledModel = DocumentRenderModel(lineIndex: DocumentLineIndex(source: toggledSource))
XCTAssertEqual(originalModel.snapshot, repeatedModel.snapshot)
XCTAssertNotEqual(originalModel.snapshot, toggledModel.snapshot)
}
func testRenderedTaskToggleReplacementDoesNotMoveSourceRanges() {
let source = "Intro\n* [ ] Move with arrow keys.\nOutro"
let lineIndex = DocumentLineIndex(source: source)
let task = DocumentRenderModel(lineIndex: lineIndex).nodes.compactMap { node -> RenderedTaskElement? in
guard case .task(let task) = node.element else { return nil }
return task
}.first
XCTAssertEqual(task?.toggledMarkdownCheckbox, "[x]")
XCTAssertEqual(task?.checkboxRange.length, ((task?.toggledMarkdownCheckbox ?? "") as NSString).length)
}
func testRenderedElementsAreSemantic() {
let source = "# Heading\n* [x] Done\nSee [Docs](https://example.com)"
let lineIndex = DocumentLineIndex(source: source)
let presentation = DocumentPresentationState(lineIndex: lineIndex, activeLineIndex: 99)
XCTAssertTrue(presentation.elements.contains {
guard case .heading(let heading) = $0 else { return false }
return heading.lineIndex == 0 && heading.level == 1
})
XCTAssertEqual(presentation.renderedTasks.map(\.checked), [true])
XCTAssertTrue(presentation.elements.contains {
guard case .link(let link) = $0 else { return false }
return (source as NSString).substring(with: link.titleRange) == "Docs"
})
}
func testCodeBlockIsRepresentedAsSemanticBlock() {
let source = "Intro\n```swift\nlet value = 42\n```"
let lineIndex = DocumentLineIndex(source: source)
let presentation = DocumentPresentationState(lineIndex: lineIndex, activeLineIndex: 0)
XCTAssertEqual(presentation.renderedCodeBlocks.count, 1)
XCTAssertEqual(presentation.renderedCodeBlocks[0].lineIndexes, [1, 2, 3])
XCTAssertEqual(
(source as NSString).substring(with: presentation.renderedCodeBlocks[0].languageRange!),
"swift"
)
}
func testDirtyPresentationResolvesNearbyCodeBlockContext() {
let source = "Intro\n```swift\nlet value = 42\n```"
let lineIndex = DocumentLineIndex(source: source)
let presentation = DocumentPresentationState(
lineIndex: lineIndex,
activeLineIndex: 0,
lineIndexes: [2]
)
XCTAssertEqual(presentation.lines.count, 1)
XCTAssertEqual(presentation.lines[0].state, .rendered)
XCTAssertEqual(presentation.lines[0].renderPlan.kind, .codeBlockContent)
}
}