mirror of
https://github.com/penpot/penpot.git
synced 2026-01-29 16:51:41 -05:00
Compare commits
1 Commits
develop
...
azazeln28-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
021aeb50dd |
@@ -405,12 +405,8 @@ export class TextEditor extends EventTarget {
|
||||
|
||||
if (e.inputType in commands) {
|
||||
const command = commands[e.inputType];
|
||||
if (!this.#selectionController.startMutation()) {
|
||||
return;
|
||||
}
|
||||
command(e, this, this.#selectionController);
|
||||
const mutations = this.#selectionController.endMutation();
|
||||
this.#notifyLayout(LayoutType.FULL, mutations);
|
||||
this.#notifyLayout(LayoutType.FULL);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -456,19 +452,12 @@ export class TextEditor extends EventTarget {
|
||||
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === "Backspace") {
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.#selectionController.startMutation()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#selectionController.isCollapsed) {
|
||||
this.#selectionController.removeWordBackward();
|
||||
} else {
|
||||
this.#selectionController.removeSelected();
|
||||
}
|
||||
|
||||
const mutations = this.#selectionController.endMutation();
|
||||
this.#notifyLayout(LayoutType.FULL, mutations);
|
||||
this.#notifyLayout(LayoutType.FULL);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -476,14 +465,12 @@ export class TextEditor extends EventTarget {
|
||||
* Notifies that the edited texts needs layout.
|
||||
*
|
||||
* @param {'full'|'partial'} type
|
||||
* @param {CommandMutations} mutations
|
||||
*/
|
||||
#notifyLayout(type = LayoutType.FULL, mutations) {
|
||||
#notifyLayout(type = LayoutType.FULL) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("needslayout", {
|
||||
detail: {
|
||||
type: type,
|
||||
mutations: mutations,
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -630,10 +617,8 @@ export class TextEditor extends EventTarget {
|
||||
* @returns {TextEditor}
|
||||
*/
|
||||
applyStylesToSelection(styles) {
|
||||
this.#selectionController.startMutation();
|
||||
this.#selectionController.applyStyles(styles);
|
||||
const mutations = this.#selectionController.endMutation();
|
||||
this.#notifyLayout(LayoutType.FULL, mutations);
|
||||
this.#notifyLayout(LayoutType.FULL);
|
||||
this.#changeController.notifyImmediately();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright (c) KALEIDOS INC
|
||||
*/
|
||||
|
||||
/**
|
||||
* Command mutations
|
||||
*/
|
||||
export class CommandMutations {
|
||||
#added = new Set();
|
||||
#removed = new Set();
|
||||
#updated = new Set();
|
||||
|
||||
constructor(added, updated, removed) {
|
||||
if (added && Array.isArray(added)) this.#added = new Set(added);
|
||||
if (updated && Array.isArray(updated)) this.#updated = new Set(updated);
|
||||
if (removed && Array.isArray(removed)) this.#removed = new Set(removed);
|
||||
}
|
||||
|
||||
get added() {
|
||||
return this.#added;
|
||||
}
|
||||
|
||||
get removed() {
|
||||
return this.#removed;
|
||||
}
|
||||
|
||||
get updated() {
|
||||
return this.#updated;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.#added.clear();
|
||||
this.#removed.clear();
|
||||
this.#updated.clear();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.#added.clear();
|
||||
this.#added = null;
|
||||
this.#removed.clear();
|
||||
this.#removed = null;
|
||||
this.#updated.clear();
|
||||
this.#updated = null;
|
||||
}
|
||||
|
||||
add(node) {
|
||||
this.#added.add(node);
|
||||
return this;
|
||||
}
|
||||
|
||||
remove(node) {
|
||||
this.#removed.add(node);
|
||||
return this;
|
||||
}
|
||||
|
||||
update(node) {
|
||||
this.#updated.add(node);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export default CommandMutations;
|
||||
@@ -1,71 +0,0 @@
|
||||
import { describe, test, expect } from "vitest";
|
||||
import CommandMutations from "./CommandMutations.js";
|
||||
|
||||
describe("CommandMutations", () => {
|
||||
test("should create a new CommandMutations", () => {
|
||||
const mutations = new CommandMutations();
|
||||
expect(mutations).toHaveProperty("added");
|
||||
expect(mutations).toHaveProperty("updated");
|
||||
expect(mutations).toHaveProperty("removed");
|
||||
});
|
||||
|
||||
test("should create an initialized new CommandMutations", () => {
|
||||
const mutations = new CommandMutations([1], [2], [3]);
|
||||
expect(mutations.added.size).toBe(1);
|
||||
expect(mutations.updated.size).toBe(1);
|
||||
expect(mutations.removed.size).toBe(1);
|
||||
expect(mutations.added.has(1)).toBe(true);
|
||||
expect(mutations.updated.has(2)).toBe(true);
|
||||
expect(mutations.removed.has(3)).toBe(true);
|
||||
});
|
||||
|
||||
test("should add an added node to a CommandMutations", () => {
|
||||
const mutations = new CommandMutations();
|
||||
mutations.add(1);
|
||||
expect(mutations.added.has(1)).toBe(true);
|
||||
});
|
||||
|
||||
test("should add an updated node to a CommandMutations", () => {
|
||||
const mutations = new CommandMutations();
|
||||
mutations.update(1);
|
||||
expect(mutations.updated.has(1)).toBe(true);
|
||||
});
|
||||
|
||||
test("should add an removed node to a CommandMutations", () => {
|
||||
const mutations = new CommandMutations();
|
||||
mutations.remove(1);
|
||||
expect(mutations.removed.has(1)).toBe(true);
|
||||
});
|
||||
|
||||
test("should clear a CommandMutations", () => {
|
||||
const mutations = new CommandMutations();
|
||||
mutations.add(1);
|
||||
mutations.update(2);
|
||||
mutations.remove(3);
|
||||
expect(mutations.added.has(1)).toBe(true);
|
||||
expect(mutations.added.size).toBe(1);
|
||||
expect(mutations.updated.has(2)).toBe(true);
|
||||
expect(mutations.updated.size).toBe(1);
|
||||
expect(mutations.removed.has(3)).toBe(true);
|
||||
expect(mutations.removed.size).toBe(1);
|
||||
|
||||
mutations.clear();
|
||||
expect(mutations.added.size).toBe(0);
|
||||
expect(mutations.added.has(1)).toBe(false);
|
||||
expect(mutations.updated.size).toBe(0);
|
||||
expect(mutations.updated.has(1)).toBe(false);
|
||||
expect(mutations.removed.size).toBe(0);
|
||||
expect(mutations.removed.has(1)).toBe(false);
|
||||
});
|
||||
|
||||
test("should dispose a CommandMutations", () => {
|
||||
const mutations = new CommandMutations();
|
||||
mutations.add(1);
|
||||
mutations.update(2);
|
||||
mutations.remove(3);
|
||||
mutations.dispose();
|
||||
expect(mutations.added).toBe(null);
|
||||
expect(mutations.updated).toBe(null);
|
||||
expect(mutations.removed).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, test, expect } from "vitest";
|
||||
import { insertInto, removeBackward, removeForward, replaceWith } from "./Text";
|
||||
import { insertInto, removeSlice, removeBackward, removeForward, removeWordBackward, replaceWith, findPreviousWordBoundary } from "./Text";
|
||||
|
||||
describe("Text", () => {
|
||||
test("* should throw when passed wrong parameters", () => {
|
||||
@@ -51,4 +51,23 @@ describe("Text", () => {
|
||||
test("`removeForward` should remove string forward from offset 6", () => {
|
||||
expect(removeForward("Hello, World!", 6)).toBe("Hello,World!");
|
||||
});
|
||||
|
||||
test("`removeSlice` should remove a part of a text", () => {
|
||||
expect(removeSlice("Hello, World!", 7, 12)).toBe("Hello, !");
|
||||
});
|
||||
|
||||
test("`findPreviousWordBoundary` edge cases", () => {
|
||||
expect(findPreviousWordBoundary(null)).toBe(0);
|
||||
expect(findPreviousWordBoundary("Hello, World!", 0)).toBe(0);
|
||||
expect(findPreviousWordBoundary(" Hello, World!", 3)).toBe(0);
|
||||
})
|
||||
|
||||
test("`removeWordBackward` with no text should return an empty string", () => {
|
||||
expect(removeWordBackward(null, 0)).toBe("");
|
||||
});
|
||||
|
||||
test("`removeWordBackward` should remove a word backward", () => {
|
||||
expect(removeWordBackward("Hello, World!", 13)).toBe("Hello, World");
|
||||
expect(removeWordBackward("Hello, World", 12)).toBe("Hello, ");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, test, expect } from "vitest";
|
||||
import { getFills } from "./Color.js";
|
||||
|
||||
/* @vitest-environment jsdom */
|
||||
describe("Color", () => {
|
||||
describe.skip("Color", () => {
|
||||
test("getFills", () => {
|
||||
expect(getFills("#aa0000")).toBe(
|
||||
'[["^ ","~:fill-color","#aa0000","~:fill-opacity",1]]',
|
||||
|
||||
@@ -49,7 +49,6 @@ import {
|
||||
} from "../content/dom/TextNode.js";
|
||||
import TextNodeIterator from "../content/dom/TextNodeIterator.js";
|
||||
import TextEditor from "../TextEditor.js";
|
||||
import CommandMutations from "../commands/CommandMutations.js";
|
||||
import { isRoot, setRootStyles } from "../content/dom/Root.js";
|
||||
import { SelectionDirection } from "./SelectionDirection.js";
|
||||
import { SafeGuard } from "./SafeGuard.js";
|
||||
@@ -145,13 +144,6 @@ export class SelectionController extends EventTarget {
|
||||
*/
|
||||
#debug = null;
|
||||
|
||||
/**
|
||||
* Command Mutations.
|
||||
*
|
||||
* @type {CommandMutations}
|
||||
*/
|
||||
#mutations = new CommandMutations();
|
||||
|
||||
/**
|
||||
* Style defaults.
|
||||
*
|
||||
@@ -449,14 +441,14 @@ export class SelectionController extends EventTarget {
|
||||
dispose() {
|
||||
document.removeEventListener("selectionchange", this.#onSelectionChange);
|
||||
this.#textEditor = null;
|
||||
this.#currentStyle = null;
|
||||
this.#options = null;
|
||||
this.#ranges.clear();
|
||||
this.#ranges = null;
|
||||
this.#range = null;
|
||||
this.#selection = null;
|
||||
this.#focusNode = null;
|
||||
this.#anchorNode = null;
|
||||
this.#mutations.dispose();
|
||||
this.#mutations = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -522,28 +514,6 @@ export class SelectionController extends EventTarget {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the start of a mutation.
|
||||
*
|
||||
* Clears all the mutations kept in CommandMutations.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
startMutation() {
|
||||
this.#mutations.clear();
|
||||
if (!this.#focusNode) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the end of a mutation.
|
||||
*
|
||||
* @returns {CommandMutations}
|
||||
*/
|
||||
endMutation() {
|
||||
return this.#mutations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects all content.
|
||||
*
|
||||
@@ -597,11 +567,18 @@ export class SelectionController extends EventTarget {
|
||||
* @returns {SelectionController}
|
||||
*/
|
||||
cursorToEnd() {
|
||||
const root = this.#textEditor.root;
|
||||
|
||||
const range = document.createRange(); //Create a range (a range is a like the selection but invisible)
|
||||
range.selectNodeContents(this.#textEditor.element);
|
||||
range.setStart(root.lastChild.firstChild.firstChild, root.lastChild.firstChild.firstChild?.nodeValue?.length ?? 0);
|
||||
range.setEnd(root.lastChild.firstChild.firstChild, root.lastChild.firstChild.firstChild?.nodeValue?.length ?? 0);
|
||||
range.collapse(false);
|
||||
|
||||
this.#selection.removeAllRanges();
|
||||
this.#selection.addRange(range);
|
||||
|
||||
this.#updateState();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1340,7 +1317,6 @@ export class SelectionController extends EventTarget {
|
||||
|
||||
if (this.focusNode.nodeValue !== removedData) {
|
||||
this.focusNode.nodeValue = removedData;
|
||||
this.#mutations.update(this.focusTextSpan);
|
||||
}
|
||||
|
||||
const paragraph = this.focusParagraph;
|
||||
@@ -1383,7 +1359,6 @@ export class SelectionController extends EventTarget {
|
||||
this.focusOffset,
|
||||
newText,
|
||||
);
|
||||
this.#mutations.update(this.focusTextSpan);
|
||||
return this.collapse(this.focusNode, this.focusOffset + newText.length);
|
||||
}
|
||||
|
||||
@@ -1447,7 +1422,6 @@ export class SelectionController extends EventTarget {
|
||||
this.#textEditor.root.replaceChildren(newParagraph);
|
||||
return this.collapse(newTextNode, newText.length + 1);
|
||||
}
|
||||
this.#mutations.update(this.focusTextSpan);
|
||||
return this.collapse(this.focusNode, startOffset + newText.length);
|
||||
}
|
||||
|
||||
@@ -1525,8 +1499,6 @@ export class SelectionController extends EventTarget {
|
||||
const currentParagraph = this.focusParagraph;
|
||||
const newParagraph = createEmptyParagraph(this.#currentStyle);
|
||||
currentParagraph.after(newParagraph);
|
||||
this.#mutations.update(currentParagraph);
|
||||
this.#mutations.add(newParagraph);
|
||||
return this.collapse(newParagraph.firstChild.firstChild, 0);
|
||||
}
|
||||
|
||||
@@ -1537,8 +1509,6 @@ export class SelectionController extends EventTarget {
|
||||
const currentParagraph = this.focusParagraph;
|
||||
const newParagraph = createEmptyParagraph(this.#currentStyle);
|
||||
currentParagraph.before(newParagraph);
|
||||
this.#mutations.update(currentParagraph);
|
||||
this.#mutations.add(newParagraph);
|
||||
return this.collapse(currentParagraph.firstChild.firstChild, 0);
|
||||
}
|
||||
|
||||
@@ -1553,8 +1523,6 @@ export class SelectionController extends EventTarget {
|
||||
this.#focusOffset,
|
||||
);
|
||||
this.focusParagraph.after(newParagraph);
|
||||
this.#mutations.update(currentParagraph);
|
||||
this.#mutations.add(newParagraph);
|
||||
return this.collapse(newParagraph.firstChild.firstChild, 0);
|
||||
}
|
||||
|
||||
@@ -1586,10 +1554,6 @@ export class SelectionController extends EventTarget {
|
||||
this.focusOffset,
|
||||
);
|
||||
currentParagraph.after(newParagraph);
|
||||
|
||||
this.#mutations.update(currentParagraph);
|
||||
this.#mutations.add(newParagraph);
|
||||
|
||||
// FIXME: Missing collapse?
|
||||
}
|
||||
|
||||
@@ -1610,7 +1574,6 @@ export class SelectionController extends EventTarget {
|
||||
const previousOffset = isLineBreak(previousTextSpan.firstChild)
|
||||
? 0
|
||||
: previousTextSpan.firstChild.nodeValue?.length || 0;
|
||||
this.#mutations.remove(paragraphToBeRemoved);
|
||||
return this.collapse(previousTextSpan.firstChild, previousOffset);
|
||||
}
|
||||
|
||||
@@ -1632,8 +1595,6 @@ export class SelectionController extends EventTarget {
|
||||
} else {
|
||||
mergeParagraphs(previousParagraph, currentParagraph);
|
||||
}
|
||||
this.#mutations.remove(currentParagraph);
|
||||
this.#mutations.update(previousParagraph);
|
||||
return this.collapse(previousTextSpan.firstChild, previousOffset);
|
||||
}
|
||||
|
||||
@@ -1647,8 +1608,6 @@ export class SelectionController extends EventTarget {
|
||||
return;
|
||||
}
|
||||
mergeParagraphs(this.focusParagraph, nextParagraph);
|
||||
this.#mutations.update(currentParagraph);
|
||||
this.#mutations.remove(nextParagraph);
|
||||
|
||||
// FIXME: Missing collapse?
|
||||
}
|
||||
@@ -1665,7 +1624,6 @@ export class SelectionController extends EventTarget {
|
||||
paragraphToBeRemoved.remove();
|
||||
const nextTextSpan = nextParagraph.firstChild;
|
||||
const nextOffset = this.focusOffset;
|
||||
this.#mutations.remove(paragraphToBeRemoved);
|
||||
return this.collapse(nextTextSpan.firstChild, nextOffset);
|
||||
}
|
||||
|
||||
@@ -1680,7 +1638,6 @@ export class SelectionController extends EventTarget {
|
||||
for (const textSpan of affectedTextSpans) {
|
||||
if (textSpan.textContent === "") {
|
||||
textSpan.remove();
|
||||
this.#mutations.remove(textSpan);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1688,7 +1645,6 @@ export class SelectionController extends EventTarget {
|
||||
for (const paragraph of affectedParagraphs) {
|
||||
if (paragraph.children.length === 0) {
|
||||
paragraph.remove();
|
||||
this.#mutations.remove(paragraph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,6 +581,136 @@ describe("SelectionController", () => {
|
||||
expect(textEditorMock.root.textContent).toBe("");
|
||||
});
|
||||
|
||||
test("`insertParagraph` should insert a new paragraph in an empty editor", () => {
|
||||
const textEditorMock = TextEditorMock.createTextEditorMockEmpty();
|
||||
const root = textEditorMock.root;
|
||||
const selection = document.getSelection();
|
||||
const selectionController = new SelectionController(textEditorMock, selection);
|
||||
focus(
|
||||
selection,
|
||||
textEditorMock,
|
||||
root.firstChild.firstChild.firstChild,
|
||||
0,
|
||||
);
|
||||
selectionController.insertParagraph();
|
||||
expect(textEditorMock.root).toBeInstanceOf(HTMLDivElement);
|
||||
expect(textEditorMock.root.dataset.itype).toBe("root");
|
||||
expect(textEditorMock.root.children.length).toBe(2);
|
||||
expect(textEditorMock.root.children.item(0)).toBeInstanceOf(HTMLDivElement);
|
||||
expect(textEditorMock.root.children.item(0).dataset.itype).toBe("paragraph");
|
||||
expect(textEditorMock.root.children.item(0).firstChild).toBeInstanceOf(
|
||||
HTMLSpanElement,
|
||||
);
|
||||
expect(textEditorMock.root.children.item(0).firstChild.dataset.itype).toBe("span");
|
||||
expect(textEditorMock.root.children.item(1)).toBeInstanceOf(HTMLDivElement);
|
||||
expect(textEditorMock.root.children.item(1).dataset.itype).toBe("paragraph");
|
||||
expect(textEditorMock.root.children.item(1).firstChild).toBeInstanceOf(
|
||||
HTMLSpanElement,
|
||||
);
|
||||
expect(textEditorMock.root.children.item(1).firstChild.dataset.itype).toBe(
|
||||
"span",
|
||||
);
|
||||
expect(textEditorMock.root.textContent).toBe("");
|
||||
});
|
||||
|
||||
test("`insertParagraph` should insert a new paragraph after a text", () => {
|
||||
const textEditorMock = TextEditorMock.createTextEditorMockWith([
|
||||
["Hello, World!"]
|
||||
]);
|
||||
const root = textEditorMock.root;
|
||||
const selection = document.getSelection();
|
||||
const selectionController = new SelectionController(
|
||||
textEditorMock,
|
||||
selection,
|
||||
);
|
||||
focus(
|
||||
selection,
|
||||
textEditorMock,
|
||||
root.firstChild.firstChild.firstChild,
|
||||
"Hello, World!".length
|
||||
);
|
||||
selectionController.insertParagraph();
|
||||
expect(textEditorMock.root).toBeInstanceOf(HTMLDivElement);
|
||||
expect(textEditorMock.root.dataset.itype).toBe("root");
|
||||
expect(textEditorMock.root.children.length).toBe(2);
|
||||
expect(textEditorMock.root.children.item(0)).toBeInstanceOf(HTMLDivElement);
|
||||
expect(textEditorMock.root.children.item(0).dataset.itype).toBe(
|
||||
"paragraph",
|
||||
);
|
||||
expect(textEditorMock.root.children.item(0).firstChild).toBeInstanceOf(
|
||||
HTMLSpanElement,
|
||||
);
|
||||
expect(textEditorMock.root.children.item(0).firstChild.dataset.itype).toBe(
|
||||
"span",
|
||||
);
|
||||
expect(textEditorMock.root.children.item(0).firstChild.textContent).toBe(
|
||||
"Hello, World!",
|
||||
);
|
||||
expect(textEditorMock.root.children.item(1)).toBeInstanceOf(HTMLDivElement);
|
||||
expect(textEditorMock.root.children.item(1).dataset.itype).toBe(
|
||||
"paragraph",
|
||||
);
|
||||
expect(textEditorMock.root.children.item(1).firstChild).toBeInstanceOf(
|
||||
HTMLSpanElement,
|
||||
);
|
||||
expect(textEditorMock.root.children.item(1).firstChild.dataset.itype).toBe(
|
||||
"span",
|
||||
);
|
||||
expect(textEditorMock.root.children.item(1).firstChild.firstChild).toBeInstanceOf(
|
||||
HTMLBRElement,
|
||||
);
|
||||
expect(textEditorMock.root.textContent).toBe("Hello, World!");
|
||||
});
|
||||
|
||||
test("`insertParagraph` should insert a new paragraph before a text", () => {
|
||||
const textEditorMock = TextEditorMock.createTextEditorMockWith([
|
||||
["Hello, World!"],
|
||||
]);
|
||||
const root = textEditorMock.root;
|
||||
const selection = document.getSelection();
|
||||
const selectionController = new SelectionController(
|
||||
textEditorMock,
|
||||
selection,
|
||||
);
|
||||
focus(
|
||||
selection,
|
||||
textEditorMock,
|
||||
root.firstChild.firstChild.firstChild,
|
||||
0,
|
||||
);
|
||||
selectionController.insertParagraph();
|
||||
expect(textEditorMock.root).toBeInstanceOf(HTMLDivElement);
|
||||
expect(textEditorMock.root.dataset.itype).toBe("root");
|
||||
expect(textEditorMock.root.children.length).toBe(2);
|
||||
expect(textEditorMock.root.children.item(0)).toBeInstanceOf(HTMLDivElement);
|
||||
expect(textEditorMock.root.children.item(0).dataset.itype).toBe(
|
||||
"paragraph",
|
||||
);
|
||||
expect(textEditorMock.root.children.item(0).firstChild).toBeInstanceOf(
|
||||
HTMLSpanElement,
|
||||
);
|
||||
expect(textEditorMock.root.children.item(0).firstChild.dataset.itype).toBe(
|
||||
"span",
|
||||
);
|
||||
expect(textEditorMock.root.children.item(0).firstChild.firstChild).toBeInstanceOf(
|
||||
HTMLBRElement,
|
||||
);
|
||||
expect(textEditorMock.root.children.item(1)).toBeInstanceOf(HTMLDivElement);
|
||||
expect(textEditorMock.root.children.item(1).dataset.itype).toBe(
|
||||
"paragraph",
|
||||
);
|
||||
expect(textEditorMock.root.children.item(1).firstChild).toBeInstanceOf(
|
||||
HTMLSpanElement,
|
||||
);
|
||||
expect(textEditorMock.root.children.item(1).firstChild.dataset.itype).toBe(
|
||||
"span",
|
||||
);
|
||||
expect(textEditorMock.root.children.item(1).firstChild.textContent).toBe(
|
||||
"Hello, World!",
|
||||
);
|
||||
expect(textEditorMock.root.textContent).toBe("Hello, World!");
|
||||
});
|
||||
|
||||
test("`mergeBackwardParagraph` should merge two paragraphs in backward direction (backspace)", () => {
|
||||
const textEditorMock = TextEditorMock.createTextEditorMockWith([
|
||||
["Hello, "],
|
||||
@@ -1027,7 +1157,7 @@ describe("SelectionController", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test.skip("`removeSelected` multiple paragraphs", () => {
|
||||
test("`removeSelected` multiple paragraphs", () => {
|
||||
const textEditorMock = TextEditorMock.createTextEditorMockWith([
|
||||
["Hello, "],
|
||||
["\n"],
|
||||
@@ -1392,7 +1522,10 @@ describe("SelectionController", () => {
|
||||
root.firstChild.lastChild.firstChild.nodeValue.length - 3,
|
||||
);
|
||||
selectionController.applyStyles({
|
||||
"font-family": "Montserrat, sans-serif",
|
||||
"font-weight": "bold",
|
||||
"--fills":
|
||||
'[["^ ","~:fill-color","#000000","~:fill-opacity",1],["^ ","~:fill-color","#aa0000","~:fill-opacity",1]]',
|
||||
});
|
||||
expect(textEditorMock.root).toBeInstanceOf(HTMLDivElement);
|
||||
expect(textEditorMock.root.children.length).toBe(1);
|
||||
@@ -1492,4 +1625,68 @@ describe("SelectionController", () => {
|
||||
"ld!",
|
||||
);
|
||||
});
|
||||
|
||||
test("`selectAll` should select everything", () => {
|
||||
const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([
|
||||
createParagraphWith(["Hello, "], {
|
||||
"font-style": "italic",
|
||||
}),
|
||||
createParagraphWith(["World!"], {
|
||||
"font-style": "oblique",
|
||||
}),
|
||||
]);
|
||||
const root = textEditorMock.root;
|
||||
const selection = document.getSelection();
|
||||
const selectionController = new SelectionController(textEditorMock, selection);
|
||||
textEditorMock.element.focus();
|
||||
selectionController.selectAll();
|
||||
expect(selectionController.anchorNode).toBe(
|
||||
root.firstChild.firstChild.firstChild
|
||||
);
|
||||
expect(selectionController.focusNode).toBe(
|
||||
root.lastChild.firstChild.firstChild,
|
||||
);
|
||||
});
|
||||
|
||||
test("`cursorToEnd` should move cursor to the end", () => {
|
||||
const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([
|
||||
createParagraphWith(["Hello, "], {
|
||||
"font-style": "italic",
|
||||
}),
|
||||
createParagraphWith(["World!"], {
|
||||
"font-style": "oblique",
|
||||
}),
|
||||
]);
|
||||
const root = textEditorMock.root;
|
||||
const selection = document.getSelection();
|
||||
const selectionController = new SelectionController(textEditorMock, selection);
|
||||
textEditorMock.element.focus();
|
||||
selectionController.cursorToEnd();
|
||||
expect(selectionController.focusNode).toBe(root.lastChild.firstChild.firstChild);
|
||||
expect(selectionController.focusAtEnd).toBeTruthy();
|
||||
})
|
||||
|
||||
test("`dispose` should release every held reference", () => {
|
||||
const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([
|
||||
createParagraphWith(["Hello, "], {
|
||||
"font-style": "italic",
|
||||
}),
|
||||
createParagraphWith(["World!"], {
|
||||
"font-style": "oblique",
|
||||
}),
|
||||
]);
|
||||
const root = textEditorMock.root;
|
||||
const selection = document.getSelection();
|
||||
const selectionController = new SelectionController(textEditorMock, selection);
|
||||
focus(
|
||||
selection,
|
||||
textEditorMock,
|
||||
root.firstChild.firstChild.firstChild,
|
||||
0
|
||||
);
|
||||
selectionController.dispose();
|
||||
expect(selectionController.selection).toBe(null);
|
||||
expect(selectionController.currentStyle).toBe(null);
|
||||
expect(selectionController.options).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user