0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-08 07:50:43 -05:00

Merge pull request #5338 from penpot/azazeln28-fix-missing-text-editor-changes

📎 Fix some text editor missing changes
This commit is contained in:
Andrey Antukh 2024-11-22 12:54:42 +01:00 committed by GitHub
commit 9485ce03b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 551 additions and 504 deletions

View file

@ -1,29 +0,0 @@
import { describe, test, expect, vi } from 'vitest';
import { addEventListeners, removeEventListeners } from './Event';
/* @vitest-environment jsdom */
describe('Event', () => {
test('addEventListeners should add event listeners to an element using an object', () => {
const clickSpy = vi.fn();
const events = {
click: clickSpy
}
const element = document.createElement('div');
addEventListeners(element, events);
element.dispatchEvent(new Event('click'));
expect(clickSpy).toBeCalled();
});
test('removeEventListeners should remove event listeners to an element using an object', () => {
const clickSpy = vi.fn();
const events = {
click: clickSpy,
};
const element = document.createElement("div");
addEventListeners(element, events);
element.dispatchEvent(new Event("click"));
removeEventListeners(element, events);
element.dispatchEvent(new Event('click'))
expect(clickSpy).toBeCalledTimes(1);
})
});

View file

@ -1,7 +1,7 @@
{
"compilerOptions": {
"paths": {
"~/*": ["./*"]
"~/*": ["./src/*"]
}
}
}

View file

@ -3,11 +3,9 @@
"private": true,
"version": "0.0.0",
"type": "module",
"main": "editor/TextEditor.js",
"main": "src/editor/TextEditor.js",
"scripts": {
"dev": "vite",
"build": "./scripts/build.sh",
"preview": "vite preview",
"coverage": "vitest run --coverage",
"test": "vitest --run",
"test:watch": "vitest",
@ -23,6 +21,7 @@
"esbuild": "^0.24.0",
"jsdom": "^25.0.0",
"playwright": "^1.45.1",
"prettier": "^3.3.3",
"vite": "^5.3.1",
"vitest": "^1.6.0"
},

View file

@ -1,3 +0,0 @@
#!/usr/bin/env bash
esbuild --bundle --minify --sourcemap --target=es2021 --format=esm --platform=browser editor/TextEditor.js --outfile=dist/TextEditor.mjs

View file

@ -0,0 +1,29 @@
import { describe, test, expect, vi } from "vitest";
import { addEventListeners, removeEventListeners } from "./Event.js";
/* @vitest-environment jsdom */
describe("Event", () => {
test("addEventListeners should add event listeners to an element using an object", () => {
const clickSpy = vi.fn();
const events = {
click: clickSpy,
};
const element = document.createElement("div");
addEventListeners(element, events);
element.dispatchEvent(new Event("click"));
expect(clickSpy).toBeCalled();
});
test("removeEventListeners should remove event listeners to an element using an object", () => {
const clickSpy = vi.fn();
const events = {
click: clickSpy,
};
const element = document.createElement("div");
addEventListeners(element, events);
element.dispatchEvent(new Event("click"));
removeEventListeners(element, events);
element.dispatchEvent(new Event("click"));
expect(clickSpy).toBeCalledTimes(1);
});
});

View file

@ -8,15 +8,15 @@
import clipboard from "./clipboard/index.js";
import commands from "./commands/index.js";
import ChangeController from './controllers/ChangeController.js';
import SelectionController from './controllers/SelectionController.js';
import { createSelectionImposterFromClientRects } from './selection/Imposter.js';
import ChangeController from "./controllers/ChangeController.js";
import SelectionController from "./controllers/SelectionController.js";
import { createSelectionImposterFromClientRects } from "./selection/Imposter.js";
import { addEventListeners, removeEventListeners } from "./Event.js";
import { createRoot, createEmptyRoot } from './content/dom/Root.js';
import { createParagraph, fixParagraph, getParagraph } from './content/dom/Paragraph.js';
import { createEmptyInline, createInline } from './content/dom/Inline.js';
import { isLineBreak } from './content/dom/LineBreak.js';
import LayoutType from './layout/LayoutType.js';
import { createRoot, createEmptyRoot } from "./content/dom/Root.js";
import { createParagraph } from "./content/dom/Paragraph.js";
import { createEmptyInline, createInline } from "./content/dom/Inline.js";
import { isLineBreak } from "./content/dom/LineBreak.js";
import LayoutType from "./layout/LayoutType.js";
/**
* Text Editor.

View file

@ -1,49 +1,49 @@
import { describe, test, expect } from 'vitest'
import { TextEditor } from './TextEditor'
import { describe, test, expect } from "vitest";
import { TextEditor } from "./TextEditor.js";
/* @vitest-environment jsdom */
describe('TextEditor', () => {
test('Creating TextEditor without element should throw', () => {
expect(() => new TextEditor()).toThrowError('Invalid text editor element');
describe("TextEditor", () => {
test("Creating TextEditor without element should throw", () => {
expect(() => new TextEditor()).toThrowError("Invalid text editor element");
});
test('Creating TextEditor with element should success', () => {
expect(new TextEditor(document.createElement('div'))).toBeInstanceOf(TextEditor);
test("Creating TextEditor with element should success", () => {
expect(new TextEditor(document.createElement("div"))).toBeInstanceOf(
TextEditor,
);
});
test('isEmpty should return true when editor is empty', () => {
test("isEmpty should return true when editor is empty", () => {
const textEditor = new TextEditor(document.createElement("div"));
expect(textEditor).toBeInstanceOf(TextEditor);
expect(textEditor.isEmpty).toBe(true);
});
test('Num paragraphs should return 1 when empty', () => {
test("Num paragraphs should return 1 when empty", () => {
const textEditor = new TextEditor(document.createElement("div"));
expect(textEditor).toBeInstanceOf(TextEditor);
expect(textEditor.numParagraphs).toBe(1);
});
test('Num paragraphs should return the number of paragraphs', () => {
test("Num paragraphs should return the number of paragraphs", () => {
const textEditor = new TextEditor(document.createElement("div"));
textEditor.root = textEditor.createRoot([
textEditor.createParagraph([
textEditor.createInlineFromString('Hello, World!')
textEditor.createInlineFromString("Hello, World!"),
]),
textEditor.createParagraph([textEditor.createInlineFromString("")]),
textEditor.createParagraph([
textEditor.createInlineFromString("¡Hola, Mundo!"),
]),
textEditor.createParagraph([
textEditor.createInlineFromString('')
textEditor.createInlineFromString("Hallo, Welt!"),
]),
textEditor.createParagraph([
textEditor.createInlineFromString('¡Hola, Mundo!')
]),
textEditor.createParagraph([
textEditor.createInlineFromString('Hallo, Welt!')
])
]);
expect(textEditor).toBeInstanceOf(TextEditor);
expect(textEditor.numParagraphs).toBe(4);
});
test('Disposing a TextEditor nullifies everything', () => {
test("Disposing a TextEditor nullifies everything", () => {
const textEditor = new TextEditor(document.createElement("div"));
expect(textEditor).toBeInstanceOf(TextEditor);
textEditor.dispose();
@ -51,7 +51,7 @@ describe('TextEditor', () => {
expect(textEditor.element).toBe(null);
});
test('TextEditor focus should focus the contenteditable element', () => {
test("TextEditor focus should focus the contenteditable element", () => {
const textEditorElement = document.createElement("div");
document.body.appendChild(textEditorElement);
const textEditor = new TextEditor(textEditorElement);
@ -76,8 +76,8 @@ describe('TextEditor', () => {
const textEditor = new TextEditor(textEditorElement);
textEditor.root = textEditor.createRoot([
textEditor.createParagraph([
textEditor.createInlineFromString("Hello, World!")
])
textEditor.createInlineFromString("Hello, World!"),
]),
]);
expect(textEditor).toBeInstanceOf(TextEditor);
textEditor.focus();

View file

@ -6,7 +6,7 @@
* Copyright (c) KALEIDOS INC
*/
import { mapContentFragmentFromHTML, mapContentFragmentFromString } from '../content/dom/Content.js';
import { mapContentFragmentFromHTML, mapContentFragmentFromString } from "../content/dom/Content.js";
/**
* When the user pastes some HTML, what we do is generate

View file

@ -1,5 +1,5 @@
import { describe, test, expect } from 'vitest';
import CommandMutations from './CommandMutations';
import { describe, test, expect } from "vitest";
import CommandMutations from "./CommandMutations.js";
describe("CommandMutations", () => {
test("should create a new CommandMutations", () => {
@ -67,5 +67,5 @@ describe("CommandMutations", () => {
expect(mutations.added).toBe(null);
expect(mutations.updated).toBe(null);
expect(mutations.removed).toBe(null);
})
});
});

View file

@ -1,23 +1,26 @@
import { describe, test, expect } from 'vitest';
import { mapContentFragmentFromHTML, mapContentFragmentFromString } from './Content.js';
import { describe, test, expect } from "vitest";
import {
mapContentFragmentFromHTML,
mapContentFragmentFromString,
} from "./Content.js";
/* @vitest-environment jsdom */
describe('Content', () => {
describe("Content", () => {
test("mapContentFragmentFromHTML should return a valid content for the editor", () => {
const inertElement = document.createElement("div");
const contentFragment = mapContentFragmentFromHTML(
"<div>Hello, World!</div>",
inertElement.style
inertElement.style,
);
expect(contentFragment).toBeInstanceOf(DocumentFragment);
expect(contentFragment.children).toHaveLength(1);
expect(contentFragment.firstElementChild).toBeInstanceOf(HTMLDivElement);
expect(contentFragment.firstElementChild.firstElementChild).toBeInstanceOf(
HTMLSpanElement
);
expect(contentFragment.firstElementChild.firstElementChild.firstChild).toBeInstanceOf(
Text
HTMLSpanElement,
);
expect(
contentFragment.firstElementChild.firstElementChild.firstChild,
).toBeInstanceOf(Text);
expect(contentFragment.textContent).toBe("Hello, World!");
});
@ -25,18 +28,18 @@ describe('Content', () => {
const inertElement = document.createElement("div");
const contentFragment = mapContentFragmentFromHTML(
"<div>Hello,<br/><span> World!</span><br/></div>",
inertElement.style
inertElement.style,
);
expect(contentFragment).toBeInstanceOf(DocumentFragment);
expect(contentFragment.children).toHaveLength(1);
expect(contentFragment.firstElementChild).toBeInstanceOf(HTMLDivElement);
expect(contentFragment.firstElementChild.children).toHaveLength(2);
expect(contentFragment.firstElementChild.firstElementChild).toBeInstanceOf(
HTMLSpanElement
);
expect(contentFragment.firstElementChild.firstElementChild.firstChild).toBeInstanceOf(
Text
HTMLSpanElement,
);
expect(
contentFragment.firstElementChild.firstElementChild.firstChild,
).toBeInstanceOf(Text);
expect(contentFragment.textContent).toBe("Hello, World!");
});
@ -49,42 +52,46 @@ describe('Content', () => {
const inertElement = document.createElement("div");
const contentFragment = mapContentFragmentFromHTML(
"<div>Lorem ipsum</div><div>Dolor sit amet</div><div><br/></div><div>Sed iaculis blandit odio ornare sagittis.</div>",
inertElement.style
inertElement.style,
);
expect(contentFragment).toBeInstanceOf(DocumentFragment);
expect(contentFragment.children).toHaveLength(3);
for (let index = 0; index < contentFragment.children.length; index++) {
expect(contentFragment.children.item(index)).toBeInstanceOf(HTMLDivElement);
expect(contentFragment.children.item(index).firstElementChild).toBeInstanceOf(
HTMLSpanElement
expect(contentFragment.children.item(index)).toBeInstanceOf(
HTMLDivElement,
);
expect(
contentFragment.children.item(index).firstElementChild.firstChild
contentFragment.children.item(index).firstElementChild,
).toBeInstanceOf(HTMLSpanElement);
expect(
contentFragment.children.item(index).firstElementChild.firstChild,
).toBeInstanceOf(Text);
expect(contentFragment.children.item(index).textContent).toBe(paragraphs[index]);
expect(contentFragment.children.item(index).textContent).toBe(
paragraphs[index],
);
}
expect(contentFragment.textContent).toBe("Lorem ipsumDolor sit ametSed iaculis blandit odio ornare sagittis.");
expect(contentFragment.textContent).toBe(
"Lorem ipsumDolor sit ametSed iaculis blandit odio ornare sagittis.",
);
});
test("mapContentFragmentFromString should return a valid content for the editor", () => {
const contentFragment = mapContentFragmentFromString(
"Hello, \nWorld!"
);
const contentFragment = mapContentFragmentFromString("Hello, \nWorld!");
expect(contentFragment).toBeInstanceOf(DocumentFragment);
expect(contentFragment.children).toHaveLength(2);
expect(contentFragment.children.item(0)).toBeInstanceOf(HTMLDivElement);
expect(contentFragment.children.item(1)).toBeInstanceOf(HTMLDivElement);
expect(contentFragment.children.item(0).firstElementChild).toBeInstanceOf(
HTMLSpanElement
);
expect(contentFragment.children.item(0).firstElementChild.firstChild).toBeInstanceOf(
Text
);
expect(contentFragment.children.item(1).firstElementChild).toBeInstanceOf(
HTMLSpanElement
HTMLSpanElement,
);
expect(
contentFragment.children.item(1).firstElementChild.firstChild
contentFragment.children.item(0).firstElementChild.firstChild,
).toBeInstanceOf(Text);
expect(contentFragment.children.item(1).firstElementChild).toBeInstanceOf(
HTMLSpanElement,
);
expect(
contentFragment.children.item(1).firstElementChild.firstChild,
).toBeInstanceOf(Text);
expect(contentFragment.textContent).toBe("Hello, World!");
});

View file

@ -1,11 +1,17 @@
import { describe, test, expect } from "vitest";
import { createElement, isElement, createRandomId, isOffsetAtStart, isOffsetAtEnd } from "./Element.js";
import {
createElement,
isElement,
createRandomId,
isOffsetAtStart,
isOffsetAtEnd,
} from "./Element.js";
/* @vitest-environment jsdom */
describe("Element", () => {
test("createRandomId should create a new random id", () => {
const randomId = createRandomId();
expect(typeof randomId).toBe('string');
expect(typeof randomId).toBe("string");
expect(randomId.length).toBeGreaterThan(0);
expect(randomId.length).toBeLessThan(12);
});
@ -20,8 +26,8 @@ describe("Element", () => {
const element = createElement("div", {
attributes: {
"aria-multiline": true,
"role": "textbox"
}
role: "textbox",
},
});
expect(element.ariaMultiLine).toBe("true");
expect(element.role).toBe("textbox");
@ -30,8 +36,8 @@ describe("Element", () => {
test("createElement should create a new element with data- properties", () => {
const element = createElement("div", {
data: {
itype: "root"
}
itype: "root",
},
});
expect(element.dataset.itype).toBe("root");
});
@ -41,14 +47,14 @@ describe("Element", () => {
styles: {
"text-decoration": "underline",
},
allowedStyles: [["text-decoration"]]
allowedStyles: [["text-decoration"]],
});
expect(element.style.textDecoration).toBe("underline");
});
test("createElement should create a new element with a child", () => {
const element = createElement("div", {
children: new Text("Hello, World!")
children: new Text("Hello, World!"),
});
expect(element.textContent).toBe("Hello, World!");
});
@ -59,16 +65,18 @@ describe("Element", () => {
createElement("div", {
children: [
createElement("div", {
children: new Text("Hello, World!")
})
]
})
children: new Text("Hello, World!"),
}),
],
}),
],
});
expect(element.textContent).toBe("Hello, World!");
expect(element.firstChild.nodeType).toBe(Node.ELEMENT_NODE);
expect(element.firstChild.firstChild.nodeType).toBe(Node.ELEMENT_NODE);
expect(element.firstChild.firstChild.firstChild.nodeType).toBe(Node.TEXT_NODE);
expect(element.firstChild.firstChild.firstChild.nodeType).toBe(
Node.TEXT_NODE,
);
});
test("isElement returns true if the passed element is the expected element", () => {
@ -81,9 +89,9 @@ describe("Element", () => {
});
test("isOffsetAtStart should return true when offset is 0", () => {
const element = createElement('span', {
children: new Text("Hello")
})
const element = createElement("span", {
children: new Text("Hello"),
});
expect(isOffsetAtStart(element, 0)).toBe(true);
});

View file

@ -1,12 +1,24 @@
import { describe, test, expect } from "vitest";
import { createEmptyInline, createInline, getInline, getInlineLength, isInline, isInlineEnd, isInlineStart, isLikeInline, splitInline, TAG, TYPE } from "./Inline.js";
import {
createEmptyInline,
createInline,
getInline,
getInlineLength,
isInline,
isInlineEnd,
isInlineStart,
isLikeInline,
splitInline,
TAG,
TYPE,
} from "./Inline.js";
import { createLineBreak } from "./LineBreak.js";
/* @vitest-environment jsdom */
describe("Inline", () => {
test("createInline should throw when passed an invalid child", () => {
expect(() => createInline("Hello, World!")).toThrowError(
"Invalid inline child"
"Invalid inline child",
);
});
@ -44,7 +56,7 @@ describe("Inline", () => {
expect(isInline(a)).toBe(false);
const b = null;
expect(isInline(b)).toBe(false);
const c = document.createElement('span');
const c = document.createElement("span");
expect(isInline(c)).toBe(false);
});
@ -82,11 +94,11 @@ describe("Inline", () => {
test("getInline ", () => {
expect(getInline(null)).toBe(null);
})
});
test("getInlineLength throws when the passed node is not an inline", () => {
const inline = document.createElement('div');
expect(() => getInlineLength(inline)).toThrowError('Invalid inline');
const inline = document.createElement("div");
expect(() => getInlineLength(inline)).toThrowError("Invalid inline");
});
test("getInlineLength returns the length of the inline content", () => {

View file

@ -1,11 +1,11 @@
import { describe, expect, test } from 'vitest';
import { createLineBreak } from './LineBreak.js';
import { describe, expect, test } from "vitest";
import { createLineBreak } from "./LineBreak.js";
/* @vitest-environment jsdom */
describe('LineBreak', () => {
describe("LineBreak", () => {
test("createLineBreak should return a <br> element", () => {
const br = createLineBreak();
expect(br.nodeType).toBe(Node.ELEMENT_NODE);
expect(br.nodeName).toBe('BR');
})
expect(br.nodeName).toBe("BR");
});
});

View file

@ -7,6 +7,7 @@
*/
import {
createRandomId,
createElement,
isElement,
isOffsetAtStart,
@ -24,8 +25,6 @@ import {
} from "./Inline.js";
import { createLineBreak, isLineBreak } from "./LineBreak.js";
import { setStyles } from "./Style.js";
import { createRandomId } from "./Element.js";
import { isEmptyTextNode, isTextNode } from './TextNode.js';
export const TAG = "DIV";
export const TYPE = "paragraph";

View file

@ -18,9 +18,9 @@ import { createInline, isInline } from "./Inline.js";
/* @vitest-environment jsdom */
describe("Paragraph", () => {
test("createParagraph should throw when passed invalid children", () => {
expect(() => createParagraph([
"Whatever"
])).toThrowError("Invalid paragraph children");
expect(() => createParagraph(["Whatever"])).toThrowError(
"Invalid paragraph children",
);
});
test("createEmptyParagraph should create a new empty paragraph", () => {
@ -33,30 +33,30 @@ describe("Paragraph", () => {
test("isParagraph should return true when the passed node is a paragraph", () => {
expect(isParagraph(null)).toBe(false);
expect(isParagraph(document.createElement('div'))).toBe(false);
expect(isParagraph(document.createElement('h1'))).toBe(false);
expect(isParagraph(document.createElement("div"))).toBe(false);
expect(isParagraph(document.createElement("h1"))).toBe(false);
expect(isParagraph(createEmptyParagraph())).toBe(true);
expect(isParagraph(createParagraph([
createInline(new Text('Hello, World!'))
]))).toBe(true);
expect(
isParagraph(createParagraph([createInline(new Text("Hello, World!"))])),
).toBe(true);
});
test("isLikeParagraph should return true when node looks like a paragraph", () => {
const p = document.createElement('p');
const p = document.createElement("p");
expect(isLikeParagraph(p)).toBe(true);
const div = document.createElement('div');
const div = document.createElement("div");
expect(isLikeParagraph(div)).toBe(true);
const h1 = document.createElement('h1');
const h1 = document.createElement("h1");
expect(isLikeParagraph(h1)).toBe(true);
const h2 = document.createElement('h2');
const h2 = document.createElement("h2");
expect(isLikeParagraph(h2)).toBe(true);
const h3 = document.createElement('h3');
const h3 = document.createElement("h3");
expect(isLikeParagraph(h3)).toBe(true);
const h4 = document.createElement('h4');
const h4 = document.createElement("h4");
expect(isLikeParagraph(h4)).toBe(true);
const h5 = document.createElement('h5');
const h5 = document.createElement("h5");
expect(isLikeParagraph(h5)).toBe(true);
const h6 = document.createElement('h6');
const h6 = document.createElement("h6");
expect(isLikeParagraph(h6)).toBe(true);
});
@ -69,7 +69,7 @@ describe("Paragraph", () => {
test("getParagraph should return null if there aren't closer paragraph nodes", () => {
const text = new Text("Hello, World!");
const whatever = document.createElement('div');
const whatever = document.createElement("div");
whatever.appendChild(text);
expect(getParagraph(text)).toBe(null);
});
@ -81,7 +81,7 @@ describe("Paragraph", () => {
test("isParagraphStart should return true on a paragraph", () => {
const paragraph = createParagraph([
createInline(new Text("Hello, World!"))
createInline(new Text("Hello, World!")),
]);
expect(isParagraphStart(paragraph.firstChild.firstChild, 0)).toBe(true);
});
@ -162,11 +162,10 @@ describe("Paragraph", () => {
const nonEmptyInline = document.createElement("span");
nonEmptyInline.dataset.itype = "inline";
nonEmptyInline.appendChild(new Text('Not empty!'));
nonEmptyInline.appendChild(new Text("Not empty!"));
const nonEmptyParagraph = document.createElement("div");
nonEmptyParagraph.dataset.itype = "paragraph";
nonEmptyParagraph.appendChild(nonEmptyInline);
expect(isEmptyParagraph(nonEmptyParagraph)).toBe(false);
});
});

View file

@ -6,10 +6,9 @@
* Copyright (c) KALEIDOS INC
*/
import { createElement, isElement } from "./Element.js";
import { createRandomId, createElement, isElement } from "./Element.js";
import { createEmptyParagraph, isParagraph } from "./Paragraph.js";
import { setStyles } from "./Style.js";
import { createRandomId } from "./Element.js";
export const TAG = "DIV";
export const TYPE = "root";

View file

@ -1,11 +1,11 @@
import { describe, test, expect } from "vitest";
import { createEmptyRoot, createRoot, setRootStyles, TAG, TYPE } from './Root.js'
import { createEmptyRoot, createRoot, setRootStyles, TAG, TYPE } from "./Root.js";
/* @vitest-environment jsdom */
describe("Root", () => {
test("createRoot should throw when passed invalid children", () => {
expect(() => createRoot(["Whatever"])).toThrowError(
"Invalid root children"
"Invalid root children",
);
});
@ -16,18 +16,20 @@ describe("Root", () => {
expect(emptyRoot.dataset.itype).toBe(TYPE);
expect(emptyRoot.firstChild).toBeInstanceOf(HTMLDivElement);
expect(emptyRoot.firstChild.firstChild).toBeInstanceOf(HTMLSpanElement);
expect(emptyRoot.firstChild.firstChild.firstChild).toBeInstanceOf(HTMLBRElement);
expect(emptyRoot.firstChild.firstChild.firstChild).toBeInstanceOf(
HTMLBRElement,
);
});
test("setRootStyles should apply only the styles of root to the root", () => {
const emptyRoot = createEmptyRoot();
setRootStyles(emptyRoot, {
["--vertical-align"]: "top",
["font-size"]: "25px"
["font-size"]: "25px",
});
expect(emptyRoot.style.getPropertyValue("--vertical-align")).toBe("top");
// We expect this style to be empty because we don't apply it
// to the root.
expect(emptyRoot.style.getPropertyValue("font-size")).toBe("");
})
});
});

View file

@ -1,5 +1,11 @@
import { describe, test, expect, vi } from "vitest";
import { getStyles, isDisplayBlock, isDisplayInline, setStyle, setStyles } from "./Style.js";
import {
getStyles,
isDisplayBlock,
isDisplayInline,
setStyle,
setStyles,
} from "./Style.js";
/* @vitest-environment jsdom */
describe("Style", () => {
@ -39,16 +45,16 @@ describe("Style", () => {
test("getStyles should retrieve a list of allowed styles", () => {
const element = document.createElement("div");
element.style.display = 'block';
element.style.textDecoration = 'underline';
element.style.fontSize = '32px';
element.style.display = "block";
element.style.textDecoration = "underline";
element.style.fontSize = "32px";
const textDecorationStyles = getStyles(element, [["text-decoration"]]);
expect(textDecorationStyles).toStrictEqual({
"text-decoration": "underline"
"text-decoration": "underline",
});
const displayStyles = getStyles(element, [["display"]]);
expect(displayStyles).toStrictEqual({
"display": "block",
display: "block",
});
const fontSizeStyles = getStyles(element, [["font-size", "px"]]);
expect(fontSizeStyles).toStrictEqual({

View file

@ -1,6 +1,6 @@
import { describe, test, expect } from 'vitest';
import { isTextNode, getTextNodeLength } from './TextNode.js';
import { createLineBreak } from './LineBreak.js';
import { describe, test, expect } from "vitest";
import { isTextNode, getTextNodeLength } from "./TextNode.js";
import { createLineBreak } from "./LineBreak.js";
/* @vitest-environment jsdom */
describe("TextNode", () => {
@ -11,16 +11,18 @@ describe("TextNode", () => {
expect(isTextNode("hola")).toBe(false);
expect(isTextNode({})).toBe(false);
expect(isTextNode([])).toBe(false);
expect(() => isTextNode(undefined)).toThrowError('Invalid text node');
expect(() => isTextNode(null)).toThrowError('Invalid text node');
expect(() => isTextNode(0)).toThrowError('Invalid text node');
expect(() => isTextNode(undefined)).toThrowError("Invalid text node");
expect(() => isTextNode(null)).toThrowError("Invalid text node");
expect(() => isTextNode(0)).toThrowError("Invalid text node");
});
test("getTextNodeLength should return the length of the text node or 0 if it is a <br>", () => {
expect(getTextNodeLength(new Text("Hello, World!"))).toBe(13);
expect(getTextNodeLength(createLineBreak())).toBe(0);
expect(() => getTextNodeLength(undefined)).toThrowError('Invalid text node');
expect(() => getTextNodeLength(null)).toThrowError('Invalid text node');
expect(() => getTextNodeLength(0)).toThrowError('Invalid text node');
expect(() => getTextNodeLength(undefined)).toThrowError(
"Invalid text node",
);
expect(() => getTextNodeLength(null)).toThrowError("Invalid text node");
expect(() => getTextNodeLength(0)).toThrowError("Invalid text node");
});
});

View file

@ -10,11 +10,11 @@ describe("TextNodeIterator", () => {
test("Create a new TextNodeIterator with an invalid root should throw", () => {
expect(() => new TextNodeIterator(null)).toThrowError("Invalid root node");
expect(() => new TextNodeIterator(Infinity)).toThrowError(
"Invalid root node"
"Invalid root node",
);
expect(() => new TextNodeIterator(1)).toThrowError("Invalid root node");
expect(() => new TextNodeIterator("hola")).toThrowError(
"Invalid root node"
"Invalid root node",
);
});
@ -26,7 +26,10 @@ describe("TextNodeIterator", () => {
createInline(new Text("Whatever")),
]),
createParagraph([createInline(createLineBreak())]),
createParagraph([createInline(new Text("This is a ")), createInline(new Text("test"))]),
createParagraph([
createInline(new Text("This is a ")),
createInline(new Text("test")),
]),
createParagraph([createInline(new Text("Hi!"))]),
]);

View file

@ -1,15 +1,15 @@
import { expect, describe, test, vi } from 'vitest'
import ChangeController from './ChangeController'
import { expect, describe, test, vi } from "vitest";
import ChangeController from "./ChangeController.js";
describe("ChangeController", () => {
test("Creating a ChangeController without a valid time should throw", () => {
expect(() => new ChangeController(Infinity)).toThrowError('Invalid time')
expect(() => new ChangeController(Infinity)).toThrowError("Invalid time");
});
test("A ChangeController should dispatch an event when `notifyImmediately` is called", () => {
const changeListener = vi.fn();
const changeController = new ChangeController(10);
changeController.addEventListener("change", changeListener)
changeController.addEventListener("change", changeListener);
changeController.notifyImmediately();
expect(changeController.hasPendingChanges).toBe(false);
expect(changeListener).toBeCalled(1);

View file

@ -16,7 +16,6 @@ import {
isInlineStart,
isInlineEnd,
setInlineStyles,
mergeInlines,
splitInline,
createEmptyInline,
} from "../content/dom/Inline.js";
@ -29,7 +28,6 @@ import {
isParagraphEnd,
setParagraphStyles,
splitParagraph,
splitParagraphAtNode,
mergeParagraphs,
fixParagraph,
} from "../content/dom/Paragraph.js";
@ -48,9 +46,6 @@ import { setRootStyles } from "../content/dom/Root.js";
import { SelectionDirection } from "./SelectionDirection.js";
import SafeGuard from "./SafeGuard.js";
const SAFE_GUARD = true;
const SAFE_GUARD_TIME = true;
/**
* Supported options for the SelectionController.
*

View file

@ -6,7 +6,7 @@ test("Create selection DOM rects from client rects", () => {
const rect = new DOMRect(20, 20, 100, 50);
const clientRects = [
new DOMRect(20, 20, 100, 20),
new DOMRect(20, 50, 50, 20)
new DOMRect(20, 50, 50, 20),
];
const fragment = createSelectionImposterFromClientRects(rect, clientRects);
expect(fragment).toBeInstanceOf(DocumentFragment);

View file

@ -1,7 +1,7 @@
import { createRoot } from "~/editor/content/dom/Root";
import { createParagraph } from "~/editor/content/dom/Paragraph";
import { createEmptyInline, createInline } from "~/editor/content/dom/Inline";
import { createLineBreak } from "~/editor/content/dom/LineBreak";
import { createRoot } from "../editor/content/dom/Root.js";
import { createParagraph } from "../editor/content/dom/Paragraph.js";
import { createEmptyInline, createInline } from "../editor/content/dom/Inline.js";
import { createLineBreak } from "../editor/content/dom/LineBreak.js";
export class TextEditorMock extends EventTarget {
/**

View file

@ -1,26 +1,23 @@
import { resolve } from "node:path";
import { defineConfig } from "vite";
import { coverageConfigDefaults } from 'vitest/config'
import { coverageConfigDefaults } from "vitest/config"
export default defineConfig({
root: "./src",
resolve: {
alias: {
"~": resolve("."),
"~": resolve("./src"),
},
},
build: {
minify: false,
minify: true,
sourcemap: true,
lib: {
entry: "editor/TextEditor.js",
entry: "src/editor/TextEditor.js",
name: "TextEditor",
fileName: "TextEditor",
formats: ["es"],
},
terserOptions: {
compress: true,
mangle: true,
},
},
test: {
coverage: {

View file

@ -518,6 +518,7 @@ __metadata:
esbuild: "npm:^0.24.0"
jsdom: "npm:^25.0.0"
playwright: "npm:^1.45.1"
prettier: "npm:^3.3.3"
vite: "npm:^5.3.1"
vitest: "npm:^1.6.0"
languageName: unknown
@ -2292,6 +2293,15 @@ __metadata:
languageName: node
linkType: hard
"prettier@npm:^3.3.3":
version: 3.3.3
resolution: "prettier@npm:3.3.3"
bin:
prettier: bin/prettier.cjs
checksum: 10c0/b85828b08e7505716324e4245549b9205c0cacb25342a030ba8885aba2039a115dbcf75a0b7ca3b37bc9d101ee61fab8113fc69ca3359f2a226f1ecc07ad2e26
languageName: node
linkType: hard
"pretty-format@npm:^29.7.0":
version: 29.7.0
resolution: "pretty-format@npm:29.7.0"