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:
commit
9485ce03b5
58 changed files with 551 additions and 504 deletions
|
@ -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);
|
||||
})
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"~/*": ["./*"]
|
||||
"~/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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
|
29
frontend/text-editor/src/editor/Event.test.js
Normal file
29
frontend/text-editor/src/editor/Event.test.js
Normal 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);
|
||||
});
|
||||
});
|
|
@ -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.
|
|
@ -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();
|
|
@ -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
|
|
@ -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);
|
||||
})
|
||||
});
|
||||
});
|
|
@ -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!");
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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", () => {
|
|
@ -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");
|
||||
});
|
||||
});
|
|
@ -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";
|
|
@ -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);
|
||||
|
||||
});
|
||||
});
|
|
@ -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";
|
|
@ -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("");
|
||||
})
|
||||
});
|
||||
});
|
|
@ -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({
|
|
@ -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");
|
||||
});
|
||||
});
|
|
@ -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!"))]),
|
||||
]);
|
||||
|
|
@ -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);
|
|
@ -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.
|
||||
*
|
File diff suppressed because it is too large
Load diff
|
@ -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);
|
|
@ -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 {
|
||||
/**
|
|
@ -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: {
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue