virt2/api/soft/CodeMirror/view/test/test-domchange.ts

169 lines
5.7 KiB
TypeScript
Executable File

import {tempEditor} from "./temp-editor"
import {EditorSelection, StateField} from "@codemirror/next/state"
import {Decoration, DecorationSet, EditorView, WidgetType} from "@codemirror/next/view"
import ist from "ist"
function flush(cm: EditorView) {
cm.observer.flush()
}
describe("DOM changes", () => {
it("notices text changes", () => {
let cm = tempEditor("foo\nbar")
cm.domAtPos(1).node.nodeValue = "froo"
flush(cm)
ist(cm.state.doc.toString(), "froo\nbar")
})
it("handles browser enter behavior", () => {
let cm = tempEditor("foo\nbar"), line0 = cm.domAtPos(0).node
line0.appendChild(document.createElement("br"))
line0.appendChild(document.createElement("br"))
flush(cm)
ist(cm.state.doc.toString(), "foo\n\nbar")
})
it("supports deleting lines", () => {
let cm = tempEditor("1\n2\n3\n4\n5\n6")
for (let i = 0, lineDOM = cm.domAtPos(0).node.parentNode!; i < 4; i++) lineDOM.childNodes[1].remove()
flush(cm)
ist(cm.state.doc.toString(), "1\n6")
})
it("can deal with large insertions", () => {
let cm = tempEditor("okay")
let node = document.createElement("div")
node.textContent = "ayayayayayay"
for (let i = 0, lineDOM = cm.domAtPos(0).node.parentNode!; i < 100; i++) lineDOM.appendChild(node.cloneNode(true))
flush(cm)
ist(cm.state.doc.toString(), "okay" + "\nayayayayayay".repeat(100))
})
it("properly handles selection for ambiguous backspace", () => {
let cm = tempEditor("foo")
cm.dispatch(cm.state.t().setSelection(EditorSelection.single(2)))
cm.domAtPos(1).node.nodeValue = "fo"
cm.inputState.lastKeyCode = 8
cm.inputState.lastKeyTime = Date.now()
flush(cm)
ist(cm.state.selection.primary.anchor, 1)
})
it("notices text changes at the end of a long document", () => {
let cm = tempEditor("foo\nbar\n".repeat(15))
cm.domAtPos(8*15).node.textContent = "a"
flush(cm)
ist(cm.state.doc.toString(), "foo\nbar\n".repeat(15) + "a")
})
it("handles replacing a selection with a prefix of itself", () => {
let cm = tempEditor("foo\nbar")
cm.dispatch(cm.state.t().setSelection(EditorSelection.single(0, 7)))
cm.contentDOM.textContent = "f"
flush(cm)
ist(cm.state.doc.toString(), "f")
})
it("handles replacing a selection with a suffix of itself", () => {
let cm = tempEditor("foo\nbar")
cm.dispatch(cm.state.t().setSelection(EditorSelection.single(0, 7)))
cm.contentDOM.textContent = "r"
flush(cm)
ist(cm.state.doc.toString(), "r")
})
it("handles replacing a selection with a prefix of itself and something else", () => {
let cm = tempEditor("foo\nbar")
cm.dispatch(cm.state.t().setSelection(EditorSelection.single(0, 7)))
cm.contentDOM.textContent = "fa"
flush(cm)
ist(cm.state.doc.toString(), "fa")
})
it("handles replacing a selection with a suffix of itself and something else", () => {
let cm = tempEditor("foo\nbar")
cm.dispatch(cm.state.t().setSelection(EditorSelection.single(0, 7)))
cm.contentDOM.textContent = "br"
flush(cm)
ist(cm.state.doc.toString(), "br")
})
it("handles replacing a selection with new content that shares a prefix and a suffix", () => {
let cm = tempEditor("foo\nbar")
cm.dispatch(cm.state.t().setSelection(EditorSelection.single(1, 6)))
cm.contentDOM.textContent = "fo--ar"
flush(cm)
ist(cm.state.doc.toString(), "fo--ar")
})
it("handles appending", () => {
let cm = tempEditor("foo\nbar")
cm.dispatch(cm.state.t().setSelection(EditorSelection.single(7, 7)))
cm.contentDOM.appendChild(document.createElement("div"))
flush(cm)
ist(cm.state.doc.toString(), "foo\nbar\n")
})
it("handles deleting the first line and the newline after it", () => {
let cm = tempEditor("foo\nbar\n\nbaz")
cm.contentDOM.innerHTML = "bar<div><br></div><div>baz</div>"
flush(cm)
ist(cm.state.doc.toString(), "bar\n\nbaz")
})
it("handles deleting a line with an empty line after it", () => {
let cm = tempEditor("foo\nbar\n\nbaz")
cm.contentDOM.innerHTML = "<div>foo</div><br><div>baz</div>"
flush(cm)
ist(cm.state.doc.toString(), "foo\n\nbaz")
})
it("doesn't drop collapsed text", () => {
let field = StateField.define<DecorationSet>({
create() { return Decoration.set(Decoration.replace({}).range(1, 3)) },
update() { return Decoration.none },
provide: [EditorView.decorations]
})
let cm = tempEditor("abcd", [field])
cm.domAtPos(0).node.firstChild!.textContent = "x"
flush(cm)
ist(cm.state.doc.toString(), "xbcd")
})
it("preserves text nodes when edited in the middle", () => {
let cm = tempEditor("abcd"), text = cm.domAtPos(1).node
text.textContent = "axxd"
flush(cm)
ist(cm.domAtPos(1).node, text)
})
it("preserves text nodes when edited at the start", () => {
let cm = tempEditor("abcd"), text = cm.domAtPos(1).node
text.textContent = "xxcd"
flush(cm)
ist(cm.domAtPos(1).node, text)
})
it("preserves text nodes when edited at the end", () => {
let cm = tempEditor("abcd"), text = cm.domAtPos(1).node
text.textContent = "abxx"
flush(cm)
ist(cm.domAtPos(1).node, text)
})
it("doesn't insert newlines for block widgets", () => {
class Widget extends WidgetType<any> {
toDOM() { return document.createElement("div") }
}
let field = StateField.define<DecorationSet>({
create() { return Decoration.set(Decoration.widget({widget: new Widget(null) }).range(4)) },
update(v) { return v },
provide: [EditorView.decorations]
})
let cm = tempEditor("abcd", [field])
cm.domAtPos(0).node.appendChild(document.createTextNode("x"))
flush(cm)
ist(cm.state.doc.toString(), "abcdx")
})
})