mirror of
https://github.com/facebook/lexical.git
synced 2025-05-17 23:26:16 +08:00
chore: allow tsc to typecheck tests, fix type issues in those tests (#5982)
This commit is contained in:
@ -178,7 +178,7 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
const code = $createCodeNode();
|
const code = $createCodeNode();
|
||||||
root.append(code);
|
root.append(code);
|
||||||
code.selectStart();
|
code.selectStart();
|
||||||
$getSelection().insertText('function');
|
$getSelection()!.insertText('function');
|
||||||
});
|
});
|
||||||
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
|
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
|
||||||
expect(testEnv.innerHTML).toBe(
|
expect(testEnv.innerHTML).toBe(
|
||||||
@ -187,12 +187,12 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
|
|
||||||
// CodeNode should only render diffs, make sure that the TabNode is not cloned when
|
// CodeNode should only render diffs, make sure that the TabNode is not cloned when
|
||||||
// appending more text
|
// appending more text
|
||||||
let tabKey;
|
let tabKey: string;
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
tabKey = $dfs()
|
tabKey = $dfs()
|
||||||
.find(({node}) => $isTabNode(node))
|
.find(({node}) => $isTabNode(node))!
|
||||||
.node.getKey();
|
.node.getKey();
|
||||||
$getSelection().insertText('foo');
|
$getSelection()!.insertText('foo');
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
editor.getEditorState().read(() => {
|
editor.getEditorState().read(() => {
|
||||||
@ -214,7 +214,7 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
const code = $createCodeNode();
|
const code = $createCodeNode();
|
||||||
root.append(code);
|
root.append(code);
|
||||||
code.selectStart();
|
code.selectStart();
|
||||||
$getSelection().insertText('function');
|
$getSelection()!.insertText('function');
|
||||||
});
|
});
|
||||||
// TODO consolidate editor.update - there's some bad logic in updateAndRetainSelection
|
// TODO consolidate editor.update - there's some bad logic in updateAndRetainSelection
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
@ -238,7 +238,7 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
const code = $createCodeNode();
|
const code = $createCodeNode();
|
||||||
root.append(code);
|
root.append(code);
|
||||||
code.selectStart();
|
code.selectStart();
|
||||||
$getSelection().insertText('function');
|
$getSelection()!.insertText('function');
|
||||||
});
|
});
|
||||||
// TODO consolidate editor.update - there's some bad logic in updateAndRetainSelection
|
// TODO consolidate editor.update - there's some bad logic in updateAndRetainSelection
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
@ -253,8 +253,8 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
const codeTab = root.getFirstDescendant();
|
const codeTab = root.getFirstDescendant()!;
|
||||||
const codeText = root.getLastDescendant();
|
const codeText = root.getLastDescendant()!;
|
||||||
const selection = $createRangeSelection();
|
const selection = $createRangeSelection();
|
||||||
selection.anchor.set(codeTab.getKey(), 0, 'text');
|
selection.anchor.set(codeTab.getKey(), 0, 'text');
|
||||||
selection.focus.set(codeText.getKey(), 'function'.length, 'text');
|
selection.focus.set(codeText.getKey(), 'function'.length, 'text');
|
||||||
@ -275,7 +275,7 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
const code = $createCodeNode();
|
const code = $createCodeNode();
|
||||||
root.append(code);
|
root.append(code);
|
||||||
code.selectStart();
|
code.selectStart();
|
||||||
$getSelection().insertText('function');
|
$getSelection()!.insertText('function');
|
||||||
});
|
});
|
||||||
// TODO consolidate editor.update - there's some bad logic in updateAndRetainSelection
|
// TODO consolidate editor.update - there's some bad logic in updateAndRetainSelection
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
@ -290,8 +290,8 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
const codeTab = root.getFirstDescendant();
|
const codeTab = root.getFirstDescendant()!;
|
||||||
const codeText = root.getLastDescendant();
|
const codeText = root.getLastDescendant()!;
|
||||||
const selection = $createRangeSelection();
|
const selection = $createRangeSelection();
|
||||||
selection.anchor.set(codeTab.getKey(), 0, 'text');
|
selection.anchor.set(codeTab.getKey(), 0, 'text');
|
||||||
selection.focus.set(codeText.getKey(), 0, 'text');
|
selection.focus.set(codeText.getKey(), 0, 'text');
|
||||||
@ -313,12 +313,12 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
const code = $createCodeNode();
|
const code = $createCodeNode();
|
||||||
root.append(code);
|
root.append(code);
|
||||||
code.selectStart();
|
code.selectStart();
|
||||||
$getSelection().insertRawText('hello\tworld\nhello\tworld');
|
$getSelection()!.insertRawText('hello\tworld\nhello\tworld');
|
||||||
});
|
});
|
||||||
// TODO consolidate editor.update - there's some bad logic in updateAndRetainSelection
|
// TODO consolidate editor.update - there's some bad logic in updateAndRetainSelection
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const firstCodeText = $getRoot().getFirstDescendant();
|
const firstCodeText = $getRoot().getFirstDescendant()!;
|
||||||
const lastCodeText = $getRoot().getLastDescendant();
|
const lastCodeText = $getRoot().getLastDescendant()!;
|
||||||
const selection = $createRangeSelection();
|
const selection = $createRangeSelection();
|
||||||
selection.anchor.set(firstCodeText.getKey(), 1, 'text');
|
selection.anchor.set(firstCodeText.getKey(), 1, 'text');
|
||||||
selection.focus.set(lastCodeText.getKey(), 1, 'text');
|
selection.focus.set(lastCodeText.getKey(), 1, 'text');
|
||||||
@ -347,7 +347,7 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
const code = $createCodeNode();
|
const code = $createCodeNode();
|
||||||
root.append(code);
|
root.append(code);
|
||||||
code.selectStart();
|
code.selectStart();
|
||||||
$getSelection().insertRawText('hello\n');
|
$getSelection()!.insertRawText('hello\n');
|
||||||
});
|
});
|
||||||
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
|
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
|
||||||
expect(testEnv.innerHTML)
|
expect(testEnv.innerHTML)
|
||||||
@ -365,7 +365,7 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
const code = $createCodeNode();
|
const code = $createCodeNode();
|
||||||
root.append(code);
|
root.append(code);
|
||||||
code.selectStart();
|
code.selectStart();
|
||||||
$getSelection().insertRawText('\thello');
|
$getSelection()!.insertRawText('\thello');
|
||||||
});
|
});
|
||||||
// TODO consolidate editor.update - there's some bad logic in updateAndRetainSelection
|
// TODO consolidate editor.update - there's some bad logic in updateAndRetainSelection
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
@ -389,7 +389,7 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
const code = $createCodeNode();
|
const code = $createCodeNode();
|
||||||
root.append(code);
|
root.append(code);
|
||||||
code.selectStart();
|
code.selectStart();
|
||||||
$getSelection().insertRawText('abc\tdef\nghi\tjkl');
|
$getSelection()!.insertRawText('abc\tdef\nghi\tjkl');
|
||||||
});
|
});
|
||||||
const keyEvent = new KeyboardEventMock();
|
const keyEvent = new KeyboardEventMock();
|
||||||
keyEvent.altKey = true;
|
keyEvent.altKey = true;
|
||||||
@ -409,16 +409,16 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
const code = $createCodeNode();
|
const code = $createCodeNode();
|
||||||
root.append(code);
|
root.append(code);
|
||||||
code.selectStart();
|
code.selectStart();
|
||||||
$getSelection().insertRawText('abc\tdef\nghi\tjkl\nmno\tpqr');
|
$getSelection()!.insertRawText('abc\tdef\nghi\tjkl\nmno\tpqr');
|
||||||
});
|
});
|
||||||
// TODO consolidate editor.update - there's some bad logic in updateAndRetainSelection
|
// TODO consolidate editor.update - there's some bad logic in updateAndRetainSelection
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const firstCodeText = $getRoot().getFirstDescendant();
|
const firstCodeText = $getRoot().getFirstDescendant()!;
|
||||||
const secondCodeText = firstCodeText
|
const secondCodeText = firstCodeText
|
||||||
.getNextSibling() // tab
|
.getNextSibling()! // tab
|
||||||
.getNextSibling() // def
|
.getNextSibling()! // def
|
||||||
.getNextSibling() // linebreak
|
.getNextSibling()! // linebreak
|
||||||
.getNextSibling(); // ghi;
|
.getNextSibling()!; // ghi;
|
||||||
const selection = $createRangeSelection();
|
const selection = $createRangeSelection();
|
||||||
selection.anchor.set(firstCodeText.getKey(), 1, 'text');
|
selection.anchor.set(firstCodeText.getKey(), 1, 'text');
|
||||||
selection.focus.set(secondCodeText.getKey(), 1, 'text');
|
selection.focus.set(secondCodeText.getKey(), 1, 'text');
|
||||||
@ -455,7 +455,7 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
const code = $createCodeNode();
|
const code = $createCodeNode();
|
||||||
root.append(code);
|
root.append(code);
|
||||||
code.selectStart();
|
code.selectStart();
|
||||||
const selection = $getSelection();
|
const selection = $getSelection()!;
|
||||||
if (tabOrSpaces === 'tab') {
|
if (tabOrSpaces === 'tab') {
|
||||||
selection.insertRawText('\t\tfunction foo\n\t\tfunction bar');
|
selection.insertRawText('\t\tfunction foo\n\t\tfunction bar');
|
||||||
} else {
|
} else {
|
||||||
@ -570,7 +570,7 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
const firstChild = code.getFirstChild();
|
const firstChild = code.getFirstChild();
|
||||||
invariant($isTextNode(firstChild));
|
invariant($isTextNode(firstChild));
|
||||||
if (tabOrSpaces === 'tab') {
|
if (tabOrSpaces === 'tab') {
|
||||||
firstChild.getNextSibling().selectNext(0, 0);
|
firstChild.getNextSibling()!.selectNext(0, 0);
|
||||||
} else {
|
} else {
|
||||||
firstChild.select(4, 4);
|
firstChild.select(4, 4);
|
||||||
}
|
}
|
||||||
@ -605,7 +605,7 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
$isLineBreakNode(dfsNode.node),
|
$isLineBreakNode(dfsNode.node),
|
||||||
)[0].node;
|
)[0].node;
|
||||||
if (tabOrSpaces === 'tab') {
|
if (tabOrSpaces === 'tab') {
|
||||||
const firstTab = linebreak.getNextSibling();
|
const firstTab = linebreak.getNextSibling()!;
|
||||||
firstTab.selectNext();
|
firstTab.selectNext();
|
||||||
} else {
|
} else {
|
||||||
linebreak.selectNext(4, 4);
|
linebreak.selectNext(4, 4);
|
||||||
@ -687,7 +687,7 @@ describe('LexicalCodeNode tests', () => {
|
|||||||
$isLineBreakNode(dfsNode.node),
|
$isLineBreakNode(dfsNode.node),
|
||||||
)[0].node;
|
)[0].node;
|
||||||
if (tabOrSpaces === 'tab') {
|
if (tabOrSpaces === 'tab') {
|
||||||
const firstTab = linebreak.getNextSibling();
|
const firstTab = linebreak.getNextSibling()!;
|
||||||
firstTab.selectNext(0, 0);
|
firstTab.selectNext(0, 0);
|
||||||
} else {
|
} else {
|
||||||
linebreak.selectNext(2, 2);
|
linebreak.selectNext(2, 2);
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {RangeSelection} from 'lexical';
|
import type {EditorState, LexicalEditor, RangeSelection} from 'lexical';
|
||||||
|
|
||||||
import {$generateHtmlFromNodes} from '@lexical/html';
|
import {$generateHtmlFromNodes} from '@lexical/html';
|
||||||
import {JSDOM} from 'jsdom';
|
import {JSDOM} from 'jsdom';
|
||||||
@ -33,14 +33,17 @@ import {
|
|||||||
import {createHeadlessEditor} from '../..';
|
import {createHeadlessEditor} from '../..';
|
||||||
|
|
||||||
describe('LexicalHeadlessEditor', () => {
|
describe('LexicalHeadlessEditor', () => {
|
||||||
let editor;
|
let editor: LexicalEditor;
|
||||||
|
|
||||||
async function update(updateFn) {
|
async function update(updateFn: () => void) {
|
||||||
editor.update(updateFn);
|
editor.update(updateFn);
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertEditorState(editorState, nodes) {
|
function assertEditorState(
|
||||||
|
editorState: EditorState,
|
||||||
|
nodes: Record<string, unknown>[],
|
||||||
|
) {
|
||||||
const nodesFromState = Array.from(editorState._nodeMap.values());
|
const nodesFromState = Array.from(editorState._nodeMap.values());
|
||||||
expect(nodesFromState).toEqual(
|
expect(nodesFromState).toEqual(
|
||||||
nodes.map((node) => expect.objectContaining(node)),
|
nodes.map((node) => expect.objectContaining(node)),
|
||||||
|
@ -34,12 +34,12 @@ import {
|
|||||||
} from 'lexical/src';
|
} from 'lexical/src';
|
||||||
import {createTestEditor, TestComposer} from 'lexical/src/__tests__/utils';
|
import {createTestEditor, TestComposer} from 'lexical/src/__tests__/utils';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {createRoot} from 'react-dom/client';
|
import {createRoot, Root} from 'react-dom/client';
|
||||||
import * as ReactTestUtils from 'react-dom/test-utils';
|
import * as ReactTestUtils from 'react-dom/test-utils';
|
||||||
|
|
||||||
describe('LexicalHistory tests', () => {
|
describe('LexicalHistory tests', () => {
|
||||||
let container: HTMLDivElement | null = null;
|
let container: HTMLDivElement | null = null;
|
||||||
let reactRoot;
|
let reactRoot: Root;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
container = document.createElement('div');
|
container = document.createElement('div');
|
||||||
|
@ -6,7 +6,12 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {$createParagraphNode, $getRoot, TextNode} from 'lexical';
|
import {
|
||||||
|
$createParagraphNode,
|
||||||
|
$createRangeSelection,
|
||||||
|
$getRoot,
|
||||||
|
TextNode,
|
||||||
|
} from 'lexical';
|
||||||
import {
|
import {
|
||||||
expectHtmlToBeEqual,
|
expectHtmlToBeEqual,
|
||||||
html,
|
html,
|
||||||
@ -147,10 +152,10 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('ListItemNode.replace()', () => {
|
describe('ListItemNode.replace()', () => {
|
||||||
let listNode;
|
let listNode: ListNode;
|
||||||
let listItemNode1;
|
let listItemNode1: ListItemNode;
|
||||||
let listItemNode2;
|
let listItemNode2: ListItemNode;
|
||||||
let listItemNode3;
|
let listItemNode3: ListItemNode;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
@ -391,7 +396,7 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
// - B
|
// - B
|
||||||
test('siblings are not nested', async () => {
|
test('siblings are not nested', async () => {
|
||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
let x;
|
let x: ListItemNode;
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
@ -459,7 +464,7 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
// - B
|
// - B
|
||||||
test('the previous sibling is nested', async () => {
|
test('the previous sibling is nested', async () => {
|
||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
let x;
|
let x: ListItemNode;
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
@ -539,7 +544,7 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
// - B
|
// - B
|
||||||
test('the next sibling is nested', async () => {
|
test('the next sibling is nested', async () => {
|
||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
let x;
|
let x: ListItemNode;
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
@ -619,7 +624,7 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
// - B
|
// - B
|
||||||
test('both siblings are nested', async () => {
|
test('both siblings are nested', async () => {
|
||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
let x;
|
let x: ListItemNode;
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
@ -708,7 +713,7 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
// - B
|
// - B
|
||||||
test('the previous sibling is nested deeper than the next sibling', async () => {
|
test('the previous sibling is nested deeper than the next sibling', async () => {
|
||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
let x;
|
let x: ListItemNode;
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
@ -818,7 +823,7 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
// - B2
|
// - B2
|
||||||
test('the next sibling is nested deeper than the previous sibling', async () => {
|
test('the next sibling is nested deeper than the previous sibling', async () => {
|
||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
let x;
|
let x: ListItemNode;
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
@ -929,7 +934,7 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
// - B2
|
// - B2
|
||||||
test('both siblings are deeply nested', async () => {
|
test('both siblings are deeply nested', async () => {
|
||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
let x;
|
let x: ListItemNode;
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
@ -1052,10 +1057,10 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('ListItemNode.insertNewAfter(): non-empty list items', () => {
|
describe('ListItemNode.insertNewAfter(): non-empty list items', () => {
|
||||||
let listNode;
|
let listNode: ListNode;
|
||||||
let listItemNode1;
|
let listItemNode1: ListItemNode;
|
||||||
let listItemNode2;
|
let listItemNode2: ListItemNode;
|
||||||
let listItemNode3;
|
let listItemNode3: ListItemNode;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
@ -1103,7 +1108,7 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
listItemNode1.insertNewAfter();
|
listItemNode1.insertNewAfter($createRangeSelection());
|
||||||
});
|
});
|
||||||
|
|
||||||
expectHtmlToBeEqual(
|
expectHtmlToBeEqual(
|
||||||
@ -1134,7 +1139,7 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
listItemNode3.insertNewAfter();
|
listItemNode3.insertNewAfter($createRangeSelection());
|
||||||
});
|
});
|
||||||
|
|
||||||
expectHtmlToBeEqual(
|
expectHtmlToBeEqual(
|
||||||
@ -1165,7 +1170,7 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
listItemNode3.insertNewAfter();
|
listItemNode3.insertNewAfter($createRangeSelection());
|
||||||
});
|
});
|
||||||
|
|
||||||
expectHtmlToBeEqual(
|
expectHtmlToBeEqual(
|
||||||
@ -1217,7 +1222,7 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
listItemNode1.insertNewAfter();
|
listItemNode1.insertNewAfter($createRangeSelection());
|
||||||
});
|
});
|
||||||
|
|
||||||
expectHtmlToBeEqual(
|
expectHtmlToBeEqual(
|
||||||
@ -1264,9 +1269,9 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('ListItemNode.setIndent()', () => {
|
describe('ListItemNode.setIndent()', () => {
|
||||||
let listNode;
|
let listNode: ListNode;
|
||||||
let listItemNode1;
|
let listItemNode1: ListItemNode;
|
||||||
let listItemNode2;
|
let listItemNode2: ListItemNode;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
@ -1296,7 +1301,7 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expectHtmlToBeEqual(
|
expectHtmlToBeEqual(
|
||||||
editor.getRootElement().innerHTML,
|
editor.getRootElement()!.innerHTML,
|
||||||
html`
|
html`
|
||||||
<ul>
|
<ul>
|
||||||
<li value="1">
|
<li value="1">
|
||||||
@ -1330,7 +1335,7 @@ describe('LexicalListItemNode tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expectHtmlToBeEqual(
|
expectHtmlToBeEqual(
|
||||||
editor.getRootElement().innerHTML,
|
editor.getRootElement()!.innerHTML,
|
||||||
html`
|
html`
|
||||||
<ul>
|
<ul>
|
||||||
<li value="1" dir="ltr">
|
<li value="1" dir="ltr">
|
||||||
|
@ -238,7 +238,7 @@ describe('LexicalListNode tests', () => {
|
|||||||
|
|
||||||
expect(listNode.append(...nodesToAppend)).toBe(listNode);
|
expect(listNode.append(...nodesToAppend)).toBe(listNode);
|
||||||
expect($isListItemNode(listNode.getFirstChild())).toBe(true);
|
expect($isListItemNode(listNode.getFirstChild())).toBe(true);
|
||||||
expect(listNode.getFirstChild<ListItemNode>().getFirstChild()).toBe(
|
expect(listNode.getFirstChild<ListItemNode>()!.getFirstChild()).toBe(
|
||||||
nestedListNode,
|
nestedListNode,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -8,10 +8,10 @@
|
|||||||
|
|
||||||
import {$createTextNode, $getRoot, ParagraphNode, TextNode} from 'lexical';
|
import {$createTextNode, $getRoot, ParagraphNode, TextNode} from 'lexical';
|
||||||
|
|
||||||
import {createTestConnection, waitForReact} from './utils';
|
import {Client, createTestConnection, waitForReact} from './utils';
|
||||||
|
|
||||||
describe('Collaboration', () => {
|
describe('Collaboration', () => {
|
||||||
let container = null;
|
let container: null | HTMLDivElement = null;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
container = document.createElement('div');
|
container = document.createElement('div');
|
||||||
@ -19,11 +19,11 @@ describe('Collaboration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
document.body.removeChild(container);
|
document.body.removeChild(container!);
|
||||||
container = null;
|
container = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function expectCorrectInitialContent(client1, client2) {
|
async function expectCorrectInitialContent(client1: Client, client2: Client) {
|
||||||
// Should be empty, as client has not yet updated
|
// Should be empty, as client has not yet updated
|
||||||
expect(client1.getHTML()).toEqual('');
|
expect(client1.getHTML()).toEqual('');
|
||||||
expect(client1.getHTML()).toEqual(client2.getHTML());
|
expect(client1.getHTML()).toEqual(client2.getHTML());
|
||||||
@ -42,8 +42,8 @@ describe('Collaboration', () => {
|
|||||||
const client1 = connector.createClient('1');
|
const client1 = connector.createClient('1');
|
||||||
const client2 = connector.createClient('2');
|
const client2 = connector.createClient('2');
|
||||||
|
|
||||||
client1.start(container);
|
client1.start(container!);
|
||||||
client2.start(container);
|
client2.start(container!);
|
||||||
|
|
||||||
await expectCorrectInitialContent(client1, client2);
|
await expectCorrectInitialContent(client1, client2);
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ describe('Collaboration', () => {
|
|||||||
|
|
||||||
const text = $createTextNode('Hello world');
|
const text = $createTextNode('Hello world');
|
||||||
|
|
||||||
paragraph.append(text);
|
paragraph!.append(text);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -71,8 +71,8 @@ describe('Collaboration', () => {
|
|||||||
client2.update(() => {
|
client2.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const paragraph = root.getFirstChild<ParagraphNode>();
|
const paragraph = root.getFirstChild<ParagraphNode>()!;
|
||||||
const text = paragraph.getFirstChild<TextNode>();
|
const text = paragraph.getFirstChild<TextNode>()!;
|
||||||
|
|
||||||
text.spliceText(6, 5, 'metaverse');
|
text.spliceText(6, 5, 'metaverse');
|
||||||
});
|
});
|
||||||
@ -97,8 +97,8 @@ describe('Collaboration', () => {
|
|||||||
const client1 = connector.createClient('1');
|
const client1 = connector.createClient('1');
|
||||||
const client2 = connector.createClient('2');
|
const client2 = connector.createClient('2');
|
||||||
|
|
||||||
client1.start(container);
|
client1.start(container!);
|
||||||
client2.start(container);
|
client2.start(container!);
|
||||||
|
|
||||||
await expectCorrectInitialContent(client1, client2);
|
await expectCorrectInitialContent(client1, client2);
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ describe('Collaboration', () => {
|
|||||||
client1.update(() => {
|
client1.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const paragraph = root.getFirstChild<ParagraphNode>();
|
const paragraph = root.getFirstChild<ParagraphNode>()!;
|
||||||
const text = $createTextNode('Hello world');
|
const text = $createTextNode('Hello world');
|
||||||
|
|
||||||
paragraph.append(text);
|
paragraph.append(text);
|
||||||
@ -125,7 +125,7 @@ describe('Collaboration', () => {
|
|||||||
client2.update(() => {
|
client2.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const paragraph = root.getFirstChild<ParagraphNode>();
|
const paragraph = root.getFirstChild<ParagraphNode>()!;
|
||||||
const text = $createTextNode('Hello world');
|
const text = $createTextNode('Hello world');
|
||||||
|
|
||||||
paragraph.append(text);
|
paragraph.append(text);
|
||||||
@ -157,8 +157,8 @@ describe('Collaboration', () => {
|
|||||||
client1.update(() => {
|
client1.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const paragraph = root.getFirstChild<ParagraphNode>();
|
const paragraph = root.getFirstChild<ParagraphNode>()!;
|
||||||
const text = paragraph.getFirstChild<TextNode>();
|
const text = paragraph.getFirstChild<TextNode>()!;
|
||||||
|
|
||||||
text.spliceText(11, 11, '');
|
text.spliceText(11, 11, '');
|
||||||
});
|
});
|
||||||
@ -175,8 +175,8 @@ describe('Collaboration', () => {
|
|||||||
client2.update(() => {
|
client2.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const paragraph = root.getFirstChild<ParagraphNode>();
|
const paragraph = root.getFirstChild<ParagraphNode>()!;
|
||||||
const text = paragraph.getFirstChild<TextNode>();
|
const text = paragraph.getFirstChild<TextNode>()!;
|
||||||
|
|
||||||
text.spliceText(11, 11, '!');
|
text.spliceText(11, 11, '!');
|
||||||
});
|
});
|
||||||
@ -203,8 +203,8 @@ describe('Collaboration', () => {
|
|||||||
const connector = createTestConnection();
|
const connector = createTestConnection();
|
||||||
const client1 = connector.createClient('1');
|
const client1 = connector.createClient('1');
|
||||||
const client2 = connector.createClient('2');
|
const client2 = connector.createClient('2');
|
||||||
client1.start(container);
|
client1.start(container!);
|
||||||
client2.start(container);
|
client2.start(container!);
|
||||||
|
|
||||||
await expectCorrectInitialContent(client1, client2);
|
await expectCorrectInitialContent(client1, client2);
|
||||||
|
|
||||||
@ -213,7 +213,7 @@ describe('Collaboration', () => {
|
|||||||
client1.update(() => {
|
client1.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const paragraph = root.getFirstChild<ParagraphNode>();
|
const paragraph = root.getFirstChild<ParagraphNode>()!;
|
||||||
const text = $createTextNode('Hello world');
|
const text = $createTextNode('Hello world');
|
||||||
paragraph.append(text);
|
paragraph.append(text);
|
||||||
});
|
});
|
||||||
@ -235,8 +235,8 @@ describe('Collaboration', () => {
|
|||||||
client1.update(() => {
|
client1.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const paragraph = root.getFirstChild<ParagraphNode>();
|
const paragraph = root.getFirstChild<ParagraphNode>()!;
|
||||||
paragraph.getFirstChild().remove();
|
paragraph.getFirstChild()!.remove();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -250,9 +250,9 @@ describe('Collaboration', () => {
|
|||||||
client2.update(() => {
|
client2.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const paragraph = root.getFirstChild<ParagraphNode>();
|
const paragraph = root.getFirstChild<ParagraphNode>()!;
|
||||||
|
|
||||||
paragraph.getFirstChild<TextNode>().spliceText(11, 0, 'Hello world');
|
paragraph.getFirstChild<TextNode>()!.spliceText(11, 0, 'Hello world');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -297,15 +297,15 @@ describe('Collaboration', () => {
|
|||||||
uuid: Math.floor(Math.random() * 10000),
|
uuid: Math.floor(Math.random() * 10000),
|
||||||
};
|
};
|
||||||
|
|
||||||
client1.start(container, awarenessData1);
|
client1.start(container!, awarenessData1);
|
||||||
client2.start(container, awarenessData2);
|
client2.start(container!, awarenessData2);
|
||||||
|
|
||||||
await expectCorrectInitialContent(client1, client2);
|
await expectCorrectInitialContent(client1, client2);
|
||||||
|
|
||||||
expect(client1.awareness.getLocalState().awarenessData).toEqual(
|
expect(client1.awareness.getLocalState()!.awarenessData).toEqual(
|
||||||
awarenessData1,
|
awarenessData1,
|
||||||
);
|
);
|
||||||
expect(client2.awareness.getLocalState().awarenessData).toEqual(
|
expect(client2.awareness.getLocalState()!.awarenessData).toEqual(
|
||||||
awarenessData2,
|
awarenessData2,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -52,11 +52,11 @@ const $createSelectionByPath = ({
|
|||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const anchorNode = anchorPath.reduce(
|
const anchorNode = anchorPath.reduce(
|
||||||
(node, index) => node.getChildAtIndex(index),
|
(node, index) => node.getChildAtIndex(index)!,
|
||||||
root,
|
root,
|
||||||
);
|
);
|
||||||
const focusNode = focusPath.reduce(
|
const focusNode = focusPath.reduce(
|
||||||
(node, index) => node.getChildAtIndex(index),
|
(node, index) => node.getChildAtIndex(index)!,
|
||||||
root,
|
root,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -95,11 +95,11 @@ const $replaceTextByPath = ({
|
|||||||
focusOffset,
|
focusOffset,
|
||||||
focusPath,
|
focusPath,
|
||||||
});
|
});
|
||||||
selection.insertText(text);
|
selection.insertText(text!);
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('CollaborationWithCollisions', () => {
|
describe('CollaborationWithCollisions', () => {
|
||||||
let container = null;
|
let container: HTMLDivElement | null = null;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
container = document.createElement('div');
|
container = document.createElement('div');
|
||||||
@ -107,7 +107,7 @@ describe('CollaborationWithCollisions', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
document.body.removeChild(container);
|
document.body.removeChild(container!);
|
||||||
container = null;
|
container = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -131,7 +131,7 @@ describe('CollaborationWithCollisions', () => {
|
|||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
// Second client deletes first paragraph
|
// Second client deletes first paragraph
|
||||||
$getRoot().getFirstChild().remove();
|
$getRoot().getFirstChild()!.remove();
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
expectedHTML: null,
|
expectedHTML: null,
|
||||||
@ -152,7 +152,7 @@ describe('CollaborationWithCollisions', () => {
|
|||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
// Second client deletes first paragraph
|
// Second client deletes first paragraph
|
||||||
$getRoot().getFirstChild().remove();
|
$getRoot().getFirstChild()!.remove();
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
expectedHTML: null,
|
expectedHTML: null,
|
||||||
@ -199,7 +199,7 @@ describe('CollaborationWithCollisions', () => {
|
|||||||
const connection = createTestConnection();
|
const connection = createTestConnection();
|
||||||
const clients = createAndStartClients(
|
const clients = createAndStartClients(
|
||||||
connection,
|
connection,
|
||||||
container,
|
container!,
|
||||||
testCase.clients.length,
|
testCase.clients.length,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -7,14 +7,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
|
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
|
||||||
import {createRoot} from 'react-dom/client';
|
import * as React from 'react';
|
||||||
|
import {createRoot, Root} from 'react-dom/client';
|
||||||
import * as ReactTestUtils from 'react-dom/test-utils';
|
import * as ReactTestUtils from 'react-dom/test-utils';
|
||||||
|
|
||||||
import {LexicalComposer} from '../../LexicalComposer';
|
import {LexicalComposer} from '../../LexicalComposer';
|
||||||
|
|
||||||
describe('LexicalNodeHelpers tests', () => {
|
describe('LexicalNodeHelpers tests', () => {
|
||||||
let container = null;
|
let container: HTMLDivElement | null = null;
|
||||||
let reactRoot;
|
let reactRoot: Root;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
container = document.createElement('div');
|
container = document.createElement('div');
|
||||||
@ -23,7 +24,7 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
document.body.removeChild(container);
|
document.body.removeChild(container!);
|
||||||
container = null;
|
container = null;
|
||||||
|
|
||||||
jest.restoreAllMocks();
|
jest.restoreAllMocks();
|
||||||
|
@ -22,8 +22,10 @@ import {
|
|||||||
$getRoot,
|
$getRoot,
|
||||||
$getSelection,
|
$getSelection,
|
||||||
$isRangeSelection,
|
$isRangeSelection,
|
||||||
|
LexicalEditor,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
import {createRoot} from 'react-dom/client';
|
import * as React from 'react';
|
||||||
|
import {createRoot, Root} from 'react-dom/client';
|
||||||
import * as ReactTestUtils from 'react-dom/test-utils';
|
import * as ReactTestUtils from 'react-dom/test-utils';
|
||||||
|
|
||||||
import {LexicalComposer} from '../../LexicalComposer';
|
import {LexicalComposer} from '../../LexicalComposer';
|
||||||
@ -48,8 +50,8 @@ const RICH_TEXT_NODES = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
describe('LexicalNodeHelpers tests', () => {
|
describe('LexicalNodeHelpers tests', () => {
|
||||||
let container = null;
|
let container: HTMLDivElement | null = null;
|
||||||
let reactRoot;
|
let reactRoot: Root;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
container = document.createElement('div');
|
container = document.createElement('div');
|
||||||
@ -58,7 +60,7 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
document.body.removeChild(container);
|
document.body.removeChild(container!);
|
||||||
container = null;
|
container = null;
|
||||||
|
|
||||||
jest.restoreAllMocks();
|
jest.restoreAllMocks();
|
||||||
@ -113,7 +115,7 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
reactRoot.render(<App />);
|
reactRoot.render(<App />);
|
||||||
});
|
});
|
||||||
|
|
||||||
const text = editor.getEditorState().read($rootTextContent);
|
const text = editor!.getEditorState().read($rootTextContent);
|
||||||
expect(text).toBe('foo');
|
expect(text).toBe('foo');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -164,9 +166,9 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
reactRoot.render(<App />);
|
reactRoot.render(<App />);
|
||||||
});
|
});
|
||||||
|
|
||||||
await editor.focus();
|
await editor!.focus();
|
||||||
|
|
||||||
await editor.getEditorState().read(() => {
|
await editor!.getEditorState().read(() => {
|
||||||
expect($rootTextContent()).toBe('foo');
|
expect($rootTextContent()).toBe('foo');
|
||||||
|
|
||||||
const selection = $getSelection();
|
const selection = $getSelection();
|
||||||
@ -183,7 +185,7 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
|
|
||||||
for (const plugin of ['PlainTextPlugin', 'RichTextPlugin']) {
|
for (const plugin of ['PlainTextPlugin', 'RichTextPlugin']) {
|
||||||
it(`${plugin} can hide placeholder when non-editable`, async () => {
|
it(`${plugin} can hide placeholder when non-editable`, async () => {
|
||||||
let editor;
|
let editor: LexicalEditor;
|
||||||
|
|
||||||
function GrabEditor() {
|
function GrabEditor() {
|
||||||
[editor] = useLexicalComposerContext();
|
[editor] = useLexicalComposerContext();
|
||||||
@ -232,7 +234,7 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function placeholderText() {
|
function placeholderText() {
|
||||||
const placeholderContainer = container.querySelector('.placeholder');
|
const placeholderContainer = container!.querySelector('.placeholder');
|
||||||
return placeholderContainer && placeholderContainer.textContent;
|
return placeholderContainer && placeholderContainer.textContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
paragraph.append(overflowRight);
|
paragraph.append(overflowRight);
|
||||||
});
|
});
|
||||||
|
|
||||||
return [overflowLeftKey, overflowRightKey];
|
return [overflowLeftKey!, overflowRightKey!];
|
||||||
}
|
}
|
||||||
|
|
||||||
it('merges an empty overflow node (left overflow selected)', async () => {
|
it('merges an empty overflow node (left overflow selected)', async () => {
|
||||||
@ -63,8 +63,9 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
await initializeEditorWithLeftRightOverflowNodes();
|
await initializeEditorWithLeftRightOverflowNodes();
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const overflowLeft = $getNodeByKey<OverflowNode>(overflowLeftKey);
|
const overflowLeft = $getNodeByKey<OverflowNode>(overflowLeftKey)!;
|
||||||
const overflowRight = $getNodeByKey<OverflowNode>(overflowRightKey);
|
const overflowRight =
|
||||||
|
$getNodeByKey<OverflowNode>(overflowRightKey)!;
|
||||||
|
|
||||||
const text1 = $createTextNode('1');
|
const text1 = $createTextNode('1');
|
||||||
const text2 = $createTextNode('2');
|
const text2 = $createTextNode('2');
|
||||||
@ -77,8 +78,9 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const paragraph = $getRoot().getFirstChild<ParagraphNode>();
|
const paragraph = $getRoot().getFirstChild<ParagraphNode>()!;
|
||||||
const overflowRight = $getNodeByKey<OverflowNode>(overflowRightKey);
|
const overflowRight =
|
||||||
|
$getNodeByKey<OverflowNode>(overflowRightKey)!;
|
||||||
|
|
||||||
mergePrevious(overflowRight);
|
mergePrevious(overflowRight);
|
||||||
|
|
||||||
@ -110,8 +112,9 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
let text1Key: NodeKey;
|
let text1Key: NodeKey;
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const overflowLeft = $getNodeByKey<OverflowNode>(overflowLeftKey);
|
const overflowLeft = $getNodeByKey<OverflowNode>(overflowLeftKey)!;
|
||||||
const overflowRight = $getNodeByKey<OverflowNode>(overflowRightKey);
|
const overflowRight =
|
||||||
|
$getNodeByKey<OverflowNode>(overflowRightKey)!;
|
||||||
|
|
||||||
const text1 = $createTextNode('1');
|
const text1 = $createTextNode('1');
|
||||||
const text2 = $createTextNode('2');
|
const text2 = $createTextNode('2');
|
||||||
@ -133,9 +136,10 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const paragraph = $getRoot().getFirstChild<ParagraphNode>();
|
const paragraph = $getRoot().getFirstChild<ParagraphNode>()!;
|
||||||
|
|
||||||
const overflowRight = $getNodeByKey<OverflowNode>(overflowRightKey);
|
const overflowRight =
|
||||||
|
$getNodeByKey<OverflowNode>(overflowRightKey)!;
|
||||||
|
|
||||||
mergePrevious(overflowRight);
|
mergePrevious(overflowRight);
|
||||||
|
|
||||||
@ -168,8 +172,9 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
let text3Key: NodeKey;
|
let text3Key: NodeKey;
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const overflowLeft = $getNodeByKey<OverflowNode>(overflowLeftKey);
|
const overflowLeft = $getNodeByKey<OverflowNode>(overflowLeftKey)!;
|
||||||
const overflowRight = $getNodeByKey<OverflowNode>(overflowRightKey);
|
const overflowRight =
|
||||||
|
$getNodeByKey<OverflowNode>(overflowRightKey)!;
|
||||||
|
|
||||||
const text1 = $createTextNode('1');
|
const text1 = $createTextNode('1');
|
||||||
const text2 = $createTextNode('2');
|
const text2 = $createTextNode('2');
|
||||||
@ -202,8 +207,9 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const paragraph = $getRoot().getFirstChild<ParagraphNode>();
|
const paragraph = $getRoot().getFirstChild<ParagraphNode>()!;
|
||||||
const overflowRight = $getNodeByKey<OverflowNode>(overflowRightKey);
|
const overflowRight =
|
||||||
|
$getNodeByKey<OverflowNode>(overflowRightKey)!;
|
||||||
|
|
||||||
mergePrevious(overflowRight);
|
mergePrevious(overflowRight);
|
||||||
|
|
||||||
|
@ -11,17 +11,19 @@ import {
|
|||||||
$createTextNode,
|
$createTextNode,
|
||||||
$getRoot,
|
$getRoot,
|
||||||
createEditor,
|
createEditor,
|
||||||
|
LexicalEditor,
|
||||||
ParagraphNode,
|
ParagraphNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {createRoot} from 'react-dom/client';
|
import {createRef} from 'react';
|
||||||
|
import {createRoot, Root} from 'react-dom/client';
|
||||||
import * as ReactTestUtils from 'react-dom/test-utils';
|
import * as ReactTestUtils from 'react-dom/test-utils';
|
||||||
|
|
||||||
import {useLexicalIsTextContentEmpty} from '../../useLexicalIsTextContentEmpty';
|
import {useLexicalIsTextContentEmpty} from '../../useLexicalIsTextContentEmpty';
|
||||||
|
|
||||||
describe('useLexicalIsTextContentEmpty', () => {
|
describe('useLexicalIsTextContentEmpty', () => {
|
||||||
let container = null;
|
let container: HTMLDivElement | null = null;
|
||||||
let reactRoot;
|
let reactRoot: Root;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
container = document.createElement('div');
|
container = document.createElement('div');
|
||||||
@ -30,13 +32,13 @@ describe('useLexicalIsTextContentEmpty', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
document.body.removeChild(container);
|
document.body.removeChild(container!);
|
||||||
container = null;
|
container = null;
|
||||||
|
|
||||||
jest.restoreAllMocks();
|
jest.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
function useLexicalEditor(rootElementRef) {
|
function useLexicalEditor(rootElementRef: React.RefObject<HTMLDivElement>) {
|
||||||
const editor = React.useMemo(
|
const editor = React.useMemo(
|
||||||
() =>
|
() =>
|
||||||
createEditor({
|
createEditor({
|
||||||
@ -58,8 +60,8 @@ describe('useLexicalIsTextContentEmpty', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test('hook works', async () => {
|
test('hook works', async () => {
|
||||||
const ref = React.createRef<HTMLDivElement>();
|
const ref = createRef<HTMLDivElement>();
|
||||||
let editor;
|
let editor: LexicalEditor;
|
||||||
let hasText = false;
|
let hasText = false;
|
||||||
|
|
||||||
function TestBase() {
|
function TestBase() {
|
||||||
|
@ -6,9 +6,10 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {UserState} from '@lexical/yjs';
|
import {Provider, UserState} from '@lexical/yjs';
|
||||||
import {LexicalEditor} from 'lexical';
|
import {LexicalEditor} from 'lexical';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import {Container} from 'react-dom';
|
||||||
import {createRoot, Root} from 'react-dom/client';
|
import {createRoot, Root} from 'react-dom/client';
|
||||||
import * as ReactTestUtils from 'react-dom/test-utils';
|
import * as ReactTestUtils from 'react-dom/test-utils';
|
||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
@ -21,7 +22,17 @@ import {ContentEditable} from '../../LexicalContentEditable';
|
|||||||
import LexicalErrorBoundary from '../../LexicalErrorBoundary';
|
import LexicalErrorBoundary from '../../LexicalErrorBoundary';
|
||||||
import {RichTextPlugin} from '../../LexicalRichTextPlugin';
|
import {RichTextPlugin} from '../../LexicalRichTextPlugin';
|
||||||
|
|
||||||
function Editor({doc, provider, setEditor, awarenessData}) {
|
function Editor({
|
||||||
|
doc,
|
||||||
|
provider,
|
||||||
|
setEditor,
|
||||||
|
awarenessData,
|
||||||
|
}: {
|
||||||
|
doc: Y.Doc;
|
||||||
|
provider: Provider;
|
||||||
|
setEditor: (editor: LexicalEditor) => void;
|
||||||
|
awarenessData?: object | undefined;
|
||||||
|
}) {
|
||||||
const context = useCollaborationContext();
|
const context = useCollaborationContext();
|
||||||
|
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
@ -49,20 +60,20 @@ function Editor({doc, provider, setEditor, awarenessData}) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Client {
|
export class Client implements Provider {
|
||||||
_id: string;
|
_id: string;
|
||||||
_reactRoot: Root;
|
_reactRoot: Root | null = null;
|
||||||
_container: HTMLDivElement;
|
_container: HTMLDivElement | null = null;
|
||||||
_editor: LexicalEditor;
|
_editor: LexicalEditor | null = null;
|
||||||
_connection: {
|
_connection: {
|
||||||
_clients: Client[];
|
_clients: Map<string, Client>;
|
||||||
};
|
};
|
||||||
_connected: boolean;
|
_connected: boolean = false;
|
||||||
_doc: Y.Doc;
|
_doc: Y.Doc = new Y.Doc();
|
||||||
_awarenessState: unknown;
|
|
||||||
|
|
||||||
_listeners: Map<string, Set<(data: unknown) => void>>;
|
_listeners = new Map<string, Set<(data: unknown) => void>>();
|
||||||
_updates: Uint8Array[];
|
_updates: Uint8Array[] = [];
|
||||||
|
_awarenessState: UserState | null = null;
|
||||||
awareness: {
|
awareness: {
|
||||||
getLocalState: () => UserState | null;
|
getLocalState: () => UserState | null;
|
||||||
getStates: () => Map<number, UserState>;
|
getStates: () => Map<number, UserState>;
|
||||||
@ -71,54 +82,37 @@ class Client {
|
|||||||
setLocalState: (state: UserState) => void;
|
setLocalState: (state: UserState) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(id, connection) {
|
constructor(id: Client['_id'], connection: Client['_connection']) {
|
||||||
this._id = id;
|
this._id = id;
|
||||||
this._reactRoot = null;
|
|
||||||
this._container = null;
|
|
||||||
this._connection = connection;
|
this._connection = connection;
|
||||||
this._connected = false;
|
|
||||||
this._doc = new Y.Doc();
|
|
||||||
this._awarenessState = {};
|
|
||||||
this._onUpdate = this._onUpdate.bind(this);
|
this._onUpdate = this._onUpdate.bind(this);
|
||||||
|
|
||||||
this._doc.on('update', this._onUpdate);
|
this._doc.on('update', this._onUpdate);
|
||||||
|
|
||||||
this._listeners = new Map();
|
|
||||||
this._updates = [];
|
|
||||||
this._editor = null;
|
|
||||||
|
|
||||||
this.awareness = {
|
this.awareness = {
|
||||||
getLocalState() {
|
getLocalState: () => this._awarenessState,
|
||||||
return this._awarenessState;
|
getStates: () => new Map([[0, this._awarenessState!]]),
|
||||||
},
|
off: () => {
|
||||||
|
|
||||||
getStates() {
|
|
||||||
const states: Map<number, UserState> = new Map();
|
|
||||||
states[0] = this._awarenessState as UserState;
|
|
||||||
return states;
|
|
||||||
},
|
|
||||||
|
|
||||||
off() {
|
|
||||||
// TODO
|
// TODO
|
||||||
},
|
},
|
||||||
|
|
||||||
on() {
|
on: () => {
|
||||||
// TODO
|
// TODO
|
||||||
},
|
},
|
||||||
|
|
||||||
setLocalState(state) {
|
setLocalState: (state) => {
|
||||||
this._awarenessState = state;
|
this._awarenessState = state;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUpdate(update, origin, transaction) {
|
_onUpdate(update: Uint8Array, origin: unknown, transaction: unknown) {
|
||||||
if (origin !== this._connection && this._connected) {
|
if (origin !== this._connection && this._connected) {
|
||||||
this._broadcastUpdate(update);
|
this._broadcastUpdate(update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_broadcastUpdate(update) {
|
_broadcastUpdate(update: Uint8Array) {
|
||||||
this._connection._clients.forEach((client) => {
|
this._connection._clients.forEach((client) => {
|
||||||
if (client !== this) {
|
if (client !== this) {
|
||||||
if (client._connected) {
|
if (client._connected) {
|
||||||
@ -154,7 +148,7 @@ class Client {
|
|||||||
this._connected = false;
|
this._connected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
start(rootContainer, awarenessData?) {
|
start(rootContainer: Container, awarenessData?: object) {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
const reactRoot = createRoot(container);
|
const reactRoot = createRoot(container);
|
||||||
this._container = container;
|
this._container = container;
|
||||||
@ -185,15 +179,16 @@ class Client {
|
|||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
ReactTestUtils.act(() => {
|
ReactTestUtils.act(() => {
|
||||||
this._reactRoot.render(null);
|
this._reactRoot!.render(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._container.parentNode.removeChild(this._container);
|
this.getContainer().parentNode!.removeChild(this.getContainer());
|
||||||
|
|
||||||
this._container = null;
|
this._container = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
on(type, callback) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
on(type: string, callback: (arg: any) => void) {
|
||||||
let listenerSet = this._listeners.get(type);
|
let listenerSet = this._listeners.get(type);
|
||||||
|
|
||||||
if (listenerSet === undefined) {
|
if (listenerSet === undefined) {
|
||||||
@ -205,7 +200,8 @@ class Client {
|
|||||||
listenerSet.add(callback);
|
listenerSet.add(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
off(type, callback) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
off(type: string, callback: (arg: any) => void) {
|
||||||
const listenerSet = this._listeners.get(type);
|
const listenerSet = this._listeners.get(type);
|
||||||
|
|
||||||
if (listenerSet !== undefined) {
|
if (listenerSet !== undefined) {
|
||||||
@ -213,7 +209,7 @@ class Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_dispatch(type, data) {
|
_dispatch(type: string, data: unknown) {
|
||||||
const listenerSet = this._listeners.get(type);
|
const listenerSet = this._listeners.get(type);
|
||||||
|
|
||||||
if (listenerSet !== undefined) {
|
if (listenerSet !== undefined) {
|
||||||
@ -222,7 +218,7 @@ class Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getHTML() {
|
getHTML() {
|
||||||
return (this._container.firstChild as HTMLElement).innerHTML;
|
return (this.getContainer().firstChild as HTMLElement).innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDocJSON() {
|
getDocJSON() {
|
||||||
@ -230,36 +226,32 @@ class Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getEditorState() {
|
getEditorState() {
|
||||||
return this._editor.getEditorState();
|
return this.getEditor().getEditorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
getEditor() {
|
getEditor() {
|
||||||
return this._editor;
|
return this._editor!;
|
||||||
}
|
}
|
||||||
|
|
||||||
getContainer() {
|
getContainer() {
|
||||||
return this._container;
|
return this._container!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async focus() {
|
async focus() {
|
||||||
this._container.focus();
|
this.getContainer().focus();
|
||||||
|
|
||||||
await Promise.resolve().then();
|
await Promise.resolve().then();
|
||||||
}
|
}
|
||||||
|
|
||||||
update(cb) {
|
update(cb: () => void) {
|
||||||
this._editor.update(cb);
|
this.getEditor().update(cb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestConnection {
|
class TestConnection {
|
||||||
_clients: Map<string, Client>;
|
_clients = new Map<string, Client>();
|
||||||
|
|
||||||
constructor() {
|
createClient(id: string) {
|
||||||
this._clients = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
createClient(id) {
|
|
||||||
const client = new Client(id, this);
|
const client = new Client(id, this);
|
||||||
|
|
||||||
this._clients.set(id, client);
|
this._clients.set(id, client);
|
||||||
@ -272,7 +264,7 @@ export function createTestConnection() {
|
|||||||
return new TestConnection();
|
return new TestConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function waitForReact(cb) {
|
export async function waitForReact(cb: () => void) {
|
||||||
await ReactTestUtils.act(async () => {
|
await ReactTestUtils.act(async () => {
|
||||||
cb();
|
cb();
|
||||||
await Promise.resolve().then();
|
await Promise.resolve().then();
|
||||||
|
@ -11,7 +11,13 @@ import {
|
|||||||
$isHeadingNode,
|
$isHeadingNode,
|
||||||
HeadingNode,
|
HeadingNode,
|
||||||
} from '@lexical/rich-text';
|
} from '@lexical/rich-text';
|
||||||
import {$createTextNode, $getRoot, $getSelection, ParagraphNode} from 'lexical';
|
import {
|
||||||
|
$createTextNode,
|
||||||
|
$getRoot,
|
||||||
|
$getSelection,
|
||||||
|
ParagraphNode,
|
||||||
|
RangeSelection,
|
||||||
|
} from 'lexical';
|
||||||
import {initializeUnitTest} from 'lexical/src/__tests__/utils';
|
import {initializeUnitTest} from 'lexical/src/__tests__/utils';
|
||||||
|
|
||||||
const editorConfig = Object.freeze({
|
const editorConfig = Object.freeze({
|
||||||
@ -81,7 +87,7 @@ describe('LexicalHeadingNode tests', () => {
|
|||||||
|
|
||||||
test('HeadingNode.insertNewAfter()', async () => {
|
test('HeadingNode.insertNewAfter()', async () => {
|
||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
let headingNode;
|
let headingNode: HeadingNode;
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
headingNode = new HeadingNode('h1');
|
headingNode = new HeadingNode('h1');
|
||||||
@ -91,7 +97,7 @@ describe('LexicalHeadingNode tests', () => {
|
|||||||
'<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><h1><br></h1></div>',
|
'<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><h1><br></h1></div>',
|
||||||
);
|
);
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const selection = $getSelection();
|
const selection = $getSelection() as RangeSelection;
|
||||||
const result = headingNode.insertNewAfter(selection);
|
const result = headingNode.insertNewAfter(selection);
|
||||||
expect(result).toBeInstanceOf(ParagraphNode);
|
expect(result).toBeInstanceOf(ParagraphNode);
|
||||||
expect(result.getDirection()).toEqual(headingNode.getDirection());
|
expect(result.getDirection()).toEqual(headingNode.getDirection());
|
||||||
@ -122,7 +128,7 @@ describe('LexicalHeadingNode tests', () => {
|
|||||||
|
|
||||||
test('creates a h2 with text and can insert a new paragraph after', async () => {
|
test('creates a h2 with text and can insert a new paragraph after', async () => {
|
||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
let headingNode;
|
let headingNode: HeadingNode;
|
||||||
const text = 'hello world';
|
const text = 'hello world';
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {$createQuoteNode} from '@lexical/rich-text';
|
import {$createQuoteNode, QuoteNode} from '@lexical/rich-text';
|
||||||
import {$getRoot, ParagraphNode} from 'lexical';
|
import {$createRangeSelection, $getRoot, ParagraphNode} from 'lexical';
|
||||||
import {initializeUnitTest} from 'lexical/src/__tests__/utils';
|
import {initializeUnitTest} from 'lexical/src/__tests__/utils';
|
||||||
|
|
||||||
const editorConfig = Object.freeze({
|
const editorConfig = Object.freeze({
|
||||||
@ -64,7 +64,7 @@ describe('LexicalQuoteNode tests', () => {
|
|||||||
|
|
||||||
test('QuoteNode.insertNewAfter()', async () => {
|
test('QuoteNode.insertNewAfter()', async () => {
|
||||||
const {editor} = testEnv;
|
const {editor} = testEnv;
|
||||||
let quoteNode;
|
let quoteNode: QuoteNode;
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
quoteNode = $createQuoteNode();
|
quoteNode = $createQuoteNode();
|
||||||
@ -74,7 +74,7 @@ describe('LexicalQuoteNode tests', () => {
|
|||||||
'<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><blockquote><br></blockquote></div>',
|
'<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><blockquote><br></blockquote></div>',
|
||||||
);
|
);
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const result = quoteNode.insertNewAfter();
|
const result = quoteNode.insertNewAfter($createRangeSelection());
|
||||||
expect(result).toBeInstanceOf(ParagraphNode);
|
expect(result).toBeInstanceOf(ParagraphNode);
|
||||||
expect(result.getDirection()).toEqual(quoteNode.getDirection());
|
expect(result.getDirection()).toEqual(quoteNode.getDirection());
|
||||||
});
|
});
|
||||||
|
@ -30,7 +30,13 @@ import {
|
|||||||
$isElementNode,
|
$isElementNode,
|
||||||
$isRangeSelection,
|
$isRangeSelection,
|
||||||
$setSelection,
|
$setSelection,
|
||||||
|
DecoratorNode,
|
||||||
|
ElementNode,
|
||||||
|
LexicalEditor,
|
||||||
|
LexicalNode,
|
||||||
ParagraphNode,
|
ParagraphNode,
|
||||||
|
PointType,
|
||||||
|
TextNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
import {
|
import {
|
||||||
$assertRangeSelection,
|
$assertRangeSelection,
|
||||||
@ -72,6 +78,13 @@ import {
|
|||||||
undo,
|
undo,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
|
|
||||||
|
interface ExpectedSelection {
|
||||||
|
anchorPath: number[];
|
||||||
|
anchorOffset: number;
|
||||||
|
focusPath: number[];
|
||||||
|
focusOffset: number;
|
||||||
|
}
|
||||||
|
|
||||||
initializeClipboard();
|
initializeClipboard();
|
||||||
|
|
||||||
jest.mock('shared/environment', () => {
|
jest.mock('shared/environment', () => {
|
||||||
@ -116,7 +129,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
container = null;
|
container = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
let editor = null;
|
let editor: LexicalEditor | null = null;
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
function TestBase() {
|
function TestBase() {
|
||||||
@ -162,8 +175,8 @@ describe('LexicalSelection tests', () => {
|
|||||||
}}>
|
}}>
|
||||||
<RichTextPlugin
|
<RichTextPlugin
|
||||||
contentEditable={
|
contentEditable={
|
||||||
// eslint-disable-next-line jsx-a11y/aria-role
|
// eslint-disable-next-line jsx-a11y/aria-role, @typescript-eslint/no-explicit-any
|
||||||
<ContentEditable role={null} spellCheck={null} />
|
<ContentEditable role={null as any} spellCheck={null as any} />
|
||||||
}
|
}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
ErrorBoundary={LexicalErrorBoundary}
|
ErrorBoundary={LexicalErrorBoundary}
|
||||||
@ -175,32 +188,41 @@ describe('LexicalSelection tests', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ReactTestUtils.act(() => {
|
ReactTestUtils.act(() => {
|
||||||
createRoot(container).render(<TestBase />);
|
createRoot(container!).render(<TestBase />);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.getRootElement().focus();
|
editor!.getRootElement()!.focus();
|
||||||
|
|
||||||
await Promise.resolve().then();
|
await Promise.resolve().then();
|
||||||
// Focus first element
|
// Focus first element
|
||||||
setNativeSelectionWithPaths(editor.getRootElement(), [0, 0], 0, [0, 0], 0);
|
setNativeSelectionWithPaths(
|
||||||
|
editor!.getRootElement()!,
|
||||||
|
[0, 0],
|
||||||
|
0,
|
||||||
|
[0, 0],
|
||||||
|
0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function update(fn) {
|
async function update(fn: () => void) {
|
||||||
await ReactTestUtils.act(async () => {
|
await ReactTestUtils.act(async () => {
|
||||||
await editor.update(fn);
|
await editor!.update(fn);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.resolve().then();
|
return Promise.resolve().then();
|
||||||
}
|
}
|
||||||
|
|
||||||
test('Expect initial output to be a block with no text.', () => {
|
test('Expect initial output to be a block with no text.', () => {
|
||||||
expect(container.innerHTML).toBe(
|
expect(container!.innerHTML).toBe(
|
||||||
'<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><p class="editor-paragraph"><br></p></div>',
|
'<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><p class="editor-paragraph"><br></p></div>',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
function assertSelection(rootElement, expectedSelection) {
|
function assertSelection(
|
||||||
const actualSelection = window.getSelection();
|
rootElement: HTMLElement,
|
||||||
|
expectedSelection: ExpectedSelection,
|
||||||
|
) {
|
||||||
|
const actualSelection = window.getSelection()!;
|
||||||
|
|
||||||
expect(actualSelection.anchorNode).toBe(
|
expect(actualSelection.anchorNode).toBe(
|
||||||
getNodeFromPath(expectedSelection.anchorPath, rootElement),
|
getNodeFromPath(expectedSelection.anchorPath, rootElement),
|
||||||
@ -1107,13 +1129,13 @@ describe('LexicalSelection tests', () => {
|
|||||||
const name = testUnit.name || 'Test case';
|
const name = testUnit.name || 'Test case';
|
||||||
|
|
||||||
test(name + ` (#${i + 1})`, async () => {
|
test(name + ` (#${i + 1})`, async () => {
|
||||||
await applySelectionInputs(testUnit.inputs, update, editor);
|
await applySelectionInputs(testUnit.inputs, update, editor!);
|
||||||
|
|
||||||
// Validate HTML matches
|
// Validate HTML matches
|
||||||
expect(container.innerHTML).toBe(testUnit.expectedHTML);
|
expect(container!.innerHTML).toBe(testUnit.expectedHTML);
|
||||||
|
|
||||||
// Validate selection matches
|
// Validate selection matches
|
||||||
const rootElement = editor.getRootElement();
|
const rootElement = editor!.getRootElement()!;
|
||||||
const expectedSelection = testUnit.expectedSelection;
|
const expectedSelection = testUnit.expectedSelection;
|
||||||
|
|
||||||
assertSelection(rootElement, expectedSelection);
|
assertSelection(rootElement, expectedSelection);
|
||||||
@ -1122,10 +1144,10 @@ describe('LexicalSelection tests', () => {
|
|||||||
|
|
||||||
test('insert text one selected node element selection', async () => {
|
test('insert text one selected node element selection', async () => {
|
||||||
await ReactTestUtils.act(async () => {
|
await ReactTestUtils.act(async () => {
|
||||||
await editor.update(() => {
|
await editor!.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const paragraph = root.getFirstChild<ParagraphNode>();
|
const paragraph = root.getFirstChild<ParagraphNode>()!;
|
||||||
|
|
||||||
const elementNode = $createTestElementNode();
|
const elementNode = $createTestElementNode();
|
||||||
const text = $createTextNode('foo');
|
const text = $createTextNode('foo');
|
||||||
@ -1146,10 +1168,10 @@ describe('LexicalSelection tests', () => {
|
|||||||
|
|
||||||
test('getNodes resolves nested block nodes', async () => {
|
test('getNodes resolves nested block nodes', async () => {
|
||||||
await ReactTestUtils.act(async () => {
|
await ReactTestUtils.act(async () => {
|
||||||
await editor.update(() => {
|
await editor!.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const paragraph = root.getFirstChild<ParagraphNode>();
|
const paragraph = root.getFirstChild<ParagraphNode>()!;
|
||||||
|
|
||||||
const elementNode = $createTestElementNode();
|
const elementNode = $createTestElementNode();
|
||||||
const text = $createTextNode();
|
const text = $createTextNode();
|
||||||
@ -1157,7 +1179,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
paragraph.append(elementNode);
|
paragraph.append(elementNode);
|
||||||
elementNode.append(text);
|
elementNode.append(text);
|
||||||
|
|
||||||
const selectedNodes = $getSelection().getNodes();
|
const selectedNodes = $getSelection()!.getNodes();
|
||||||
|
|
||||||
expect(selectedNodes.length).toBe(1);
|
expect(selectedNodes.length).toBe(1);
|
||||||
expect(selectedNodes[0].getKey()).toBe(text.getKey());
|
expect(selectedNodes[0].getKey()).toBe(text.getKey());
|
||||||
@ -1166,7 +1188,23 @@ describe('LexicalSelection tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Block selection moves when new nodes are inserted', () => {
|
describe('Block selection moves when new nodes are inserted', () => {
|
||||||
[
|
const baseCases: {
|
||||||
|
name: string;
|
||||||
|
anchorOffset: number;
|
||||||
|
focusOffset: number;
|
||||||
|
fn: (
|
||||||
|
paragraph: ElementNode,
|
||||||
|
text: TextNode,
|
||||||
|
) => {
|
||||||
|
expectedAnchor: LexicalNode;
|
||||||
|
expectedAnchorOffset: number;
|
||||||
|
expectedFocus: LexicalNode;
|
||||||
|
expectedFocusOffset: number;
|
||||||
|
};
|
||||||
|
fnBefore?: (paragraph: ElementNode, text: TextNode) => void;
|
||||||
|
invertSelection?: true;
|
||||||
|
only?: true;
|
||||||
|
}[] = [
|
||||||
// Collapsed selection on end; add/remove/replace beginning
|
// Collapsed selection on end; add/remove/replace beginning
|
||||||
{
|
{
|
||||||
anchorOffset: 2,
|
anchorOffset: 2,
|
||||||
@ -1313,8 +1351,8 @@ describe('LexicalSelection tests', () => {
|
|||||||
{
|
{
|
||||||
anchorOffset: 0,
|
anchorOffset: 0,
|
||||||
fn: (paragraph, originalText1) => {
|
fn: (paragraph, originalText1) => {
|
||||||
const originalText2 = originalText1.getPreviousSibling();
|
const originalText2 = originalText1.getPreviousSibling()!;
|
||||||
const lastChild = paragraph.getLastChild();
|
const lastChild = paragraph.getLastChild()!;
|
||||||
const newText = $createTextNode('2');
|
const newText = $createTextNode('2');
|
||||||
lastChild.insertBefore(newText);
|
lastChild.insertBefore(newText);
|
||||||
|
|
||||||
@ -1335,7 +1373,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
{
|
{
|
||||||
anchorOffset: 0,
|
anchorOffset: 0,
|
||||||
fn: (paragraph, text) => {
|
fn: (paragraph, text) => {
|
||||||
const lastChild = paragraph.getLastChild();
|
const lastChild = paragraph.getLastChild()!;
|
||||||
const newText = $createTextNode('2');
|
const newText = $createTextNode('2');
|
||||||
lastChild.insertAfter(newText);
|
lastChild.insertAfter(newText);
|
||||||
|
|
||||||
@ -1352,7 +1390,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
{
|
{
|
||||||
anchorOffset: 0,
|
anchorOffset: 0,
|
||||||
fn: (paragraph, originalText1) => {
|
fn: (paragraph, originalText1) => {
|
||||||
const originalText2 = originalText1.getPreviousSibling();
|
const originalText2 = originalText1.getPreviousSibling()!;
|
||||||
const [, text] = originalText1.splitText(1);
|
const [, text] = originalText1.splitText(1);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -1372,7 +1410,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
{
|
{
|
||||||
anchorOffset: 0,
|
anchorOffset: 0,
|
||||||
fn: (paragraph, text) => {
|
fn: (paragraph, text) => {
|
||||||
const lastChild = paragraph.getLastChild();
|
const lastChild = paragraph.getLastChild()!;
|
||||||
lastChild.remove();
|
lastChild.remove();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -1389,7 +1427,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
anchorOffset: 0,
|
anchorOffset: 0,
|
||||||
fn: (paragraph, text) => {
|
fn: (paragraph, text) => {
|
||||||
const newText = $createTextNode('replacement');
|
const newText = $createTextNode('replacement');
|
||||||
const lastChild = paragraph.getLastChild();
|
const lastChild = paragraph.getLastChild()!;
|
||||||
lastChild.replace(newText);
|
lastChild.replace(newText);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -1406,7 +1444,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
{
|
{
|
||||||
anchorOffset: 0,
|
anchorOffset: 0,
|
||||||
fn: (paragraph, text) => {
|
fn: (paragraph, text) => {
|
||||||
const lastChild = paragraph.getLastChild();
|
const lastChild = paragraph.getLastChild()!;
|
||||||
const newText = $createTextNode('2');
|
const newText = $createTextNode('2');
|
||||||
lastChild.insertBefore(newText);
|
lastChild.insertBefore(newText);
|
||||||
|
|
||||||
@ -1439,7 +1477,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
{
|
{
|
||||||
anchorOffset: 0,
|
anchorOffset: 0,
|
||||||
fn: (paragraph, originalText1) => {
|
fn: (paragraph, originalText1) => {
|
||||||
const originalText2 = originalText1.getPreviousSibling();
|
const originalText2 = originalText1.getPreviousSibling()!;
|
||||||
const [, text] = originalText1.splitText(1);
|
const [, text] = originalText1.splitText(1);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -1459,7 +1497,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
{
|
{
|
||||||
anchorOffset: 1,
|
anchorOffset: 1,
|
||||||
fn: (paragraph, originalText1) => {
|
fn: (paragraph, originalText1) => {
|
||||||
const lastChild = paragraph.getLastChild();
|
const lastChild = paragraph.getLastChild()!;
|
||||||
lastChild.remove();
|
lastChild.remove();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -1480,7 +1518,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
anchorOffset: 1,
|
anchorOffset: 1,
|
||||||
fn: (paragraph, originalText1) => {
|
fn: (paragraph, originalText1) => {
|
||||||
const newText = $createTextNode('replacement');
|
const newText = $createTextNode('replacement');
|
||||||
const lastChild = paragraph.getLastChild();
|
const lastChild = paragraph.getLastChild()!;
|
||||||
lastChild.replace(newText);
|
lastChild.replace(newText);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -1501,8 +1539,8 @@ describe('LexicalSelection tests', () => {
|
|||||||
{
|
{
|
||||||
anchorOffset: 0,
|
anchorOffset: 0,
|
||||||
fn: (paragraph, originalText1) => {
|
fn: (paragraph, originalText1) => {
|
||||||
const originalText2 = originalText1.getPreviousSibling();
|
const originalText2 = originalText1.getPreviousSibling()!;
|
||||||
const lastChild = paragraph.getLastChild();
|
const lastChild = paragraph.getLastChild()!;
|
||||||
const newText = $createTextNode('2');
|
const newText = $createTextNode('2');
|
||||||
lastChild.insertBefore(newText);
|
lastChild.insertBefore(newText);
|
||||||
|
|
||||||
@ -1523,7 +1561,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
{
|
{
|
||||||
anchorOffset: 0,
|
anchorOffset: 0,
|
||||||
fn: (paragraph, originalText1) => {
|
fn: (paragraph, originalText1) => {
|
||||||
const originalText2 = originalText1.getPreviousSibling();
|
const originalText2 = originalText1.getPreviousSibling()!;
|
||||||
const newText = $createTextNode('2');
|
const newText = $createTextNode('2');
|
||||||
originalText1.insertAfter(newText);
|
originalText1.insertAfter(newText);
|
||||||
|
|
||||||
@ -1544,7 +1582,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
{
|
{
|
||||||
anchorOffset: 0,
|
anchorOffset: 0,
|
||||||
fn: (paragraph, originalText1) => {
|
fn: (paragraph, originalText1) => {
|
||||||
const originalText2 = originalText1.getPreviousSibling();
|
const originalText2 = originalText1.getPreviousSibling()!;
|
||||||
originalText1.splitText(1);
|
originalText1.splitText(1);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -1564,7 +1602,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
{
|
{
|
||||||
anchorOffset: 0,
|
anchorOffset: 0,
|
||||||
fn: (paragraph, originalText1) => {
|
fn: (paragraph, originalText1) => {
|
||||||
const originalText2 = originalText1.getPreviousSibling();
|
const originalText2 = originalText1.getPreviousSibling()!;
|
||||||
originalText1.remove();
|
originalText1.remove();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -1605,7 +1643,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
{
|
{
|
||||||
anchorOffset: 3,
|
anchorOffset: 3,
|
||||||
fn: (paragraph, originalText1) => {
|
fn: (paragraph, originalText1) => {
|
||||||
const originalText2 = paragraph.getLastChild();
|
const originalText2 = paragraph.getLastChild()!;
|
||||||
const newText = $createTextNode('new');
|
const newText = $createTextNode('new');
|
||||||
originalText1.insertBefore(newText);
|
originalText1.insertBefore(newText);
|
||||||
|
|
||||||
@ -1626,7 +1664,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
{
|
{
|
||||||
anchorOffset: 0,
|
anchorOffset: 0,
|
||||||
fn: (paragraph, originalText1) => {
|
fn: (paragraph, originalText1) => {
|
||||||
const originalText2 = paragraph.getLastChild();
|
const originalText2 = paragraph.getLastChild()!;
|
||||||
const newText = $createTextNode('new');
|
const newText = $createTextNode('new');
|
||||||
originalText1.insertBefore(newText);
|
originalText1.insertBefore(newText);
|
||||||
|
|
||||||
@ -1647,7 +1685,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
{
|
{
|
||||||
anchorOffset: 1,
|
anchorOffset: 1,
|
||||||
fn: (paragraph, originalText1) => {
|
fn: (paragraph, originalText1) => {
|
||||||
originalText1.getNextSibling().remove();
|
originalText1.getNextSibling()!.remove();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
expectedAnchor: originalText1,
|
expectedAnchor: originalText1,
|
||||||
@ -1662,7 +1700,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
{
|
{
|
||||||
anchorOffset: 0,
|
anchorOffset: 0,
|
||||||
fn: (paragraph, originalText1) => {
|
fn: (paragraph, originalText1) => {
|
||||||
originalText1.getNextSibling().remove();
|
originalText1.getNextSibling()!.remove();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
expectedAnchor: originalText1,
|
expectedAnchor: originalText1,
|
||||||
@ -1674,8 +1712,9 @@ describe('LexicalSelection tests', () => {
|
|||||||
focusOffset: 1,
|
focusOffset: 1,
|
||||||
name: 'remove - Remove with non-collapsed selection at offset',
|
name: 'remove - Remove with non-collapsed selection at offset',
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
.reduce((testSuite, testCase) => {
|
baseCases
|
||||||
|
.flatMap((testCase) => {
|
||||||
// Test inverse selection
|
// Test inverse selection
|
||||||
const inverse = {
|
const inverse = {
|
||||||
...testCase,
|
...testCase,
|
||||||
@ -1684,9 +1723,8 @@ describe('LexicalSelection tests', () => {
|
|||||||
invertSelection: true,
|
invertSelection: true,
|
||||||
name: testCase.name + ' (inverse selection)',
|
name: testCase.name + ' (inverse selection)',
|
||||||
};
|
};
|
||||||
|
return [testCase, inverse];
|
||||||
return testSuite.concat(testCase, inverse);
|
})
|
||||||
}, [])
|
|
||||||
.forEach(
|
.forEach(
|
||||||
({
|
({
|
||||||
name,
|
name,
|
||||||
@ -1703,10 +1741,10 @@ describe('LexicalSelection tests', () => {
|
|||||||
const test_ = only === true ? test.only : test;
|
const test_ = only === true ? test.only : test;
|
||||||
test_(name, async () => {
|
test_(name, async () => {
|
||||||
await ReactTestUtils.act(async () => {
|
await ReactTestUtils.act(async () => {
|
||||||
await editor.update(() => {
|
await editor!.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const paragraph = root.getFirstChild<ParagraphNode>();
|
const paragraph = root.getFirstChild<ParagraphNode>()!;
|
||||||
const textNode = $createTextNode('foo');
|
const textNode = $createTextNode('foo');
|
||||||
// Note: line break can't be selected by the DOM
|
// Note: line break can't be selected by the DOM
|
||||||
const linebreak = $createLineBreakNode();
|
const linebreak = $createLineBreakNode();
|
||||||
@ -1755,7 +1793,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
describe('Selection correctly resolves to a sibling ElementNode when a node is removed', () => {
|
describe('Selection correctly resolves to a sibling ElementNode when a node is removed', () => {
|
||||||
test('', async () => {
|
test('', async () => {
|
||||||
await ReactTestUtils.act(async () => {
|
await ReactTestUtils.act(async () => {
|
||||||
await editor.update(() => {
|
await editor!.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const listNode = $createListNode('bullet');
|
const listNode = $createListNode('bullet');
|
||||||
@ -1785,8 +1823,8 @@ describe('LexicalSelection tests', () => {
|
|||||||
describe('Selection correctly resolves to a sibling ElementNode when a selected node child is removed', () => {
|
describe('Selection correctly resolves to a sibling ElementNode when a selected node child is removed', () => {
|
||||||
test('', async () => {
|
test('', async () => {
|
||||||
await ReactTestUtils.act(async () => {
|
await ReactTestUtils.act(async () => {
|
||||||
let paragraphNodeKey;
|
let paragraphNodeKey: string;
|
||||||
await editor.update(() => {
|
await editor!.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const paragraphNode = $createParagraphNode();
|
const paragraphNode = $createParagraphNode();
|
||||||
@ -1808,7 +1846,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
|
|
||||||
listNode.remove();
|
listNode.remove();
|
||||||
});
|
});
|
||||||
await editor.getEditorState().read(() => {
|
await editor!.getEditorState().read(() => {
|
||||||
const selection = $assertRangeSelection($getSelection());
|
const selection = $assertRangeSelection($getSelection());
|
||||||
expect(selection.anchor.key).toBe(paragraphNodeKey);
|
expect(selection.anchor.key).toBe(paragraphNodeKey);
|
||||||
expect(selection.focus.key).toBe(paragraphNodeKey);
|
expect(selection.focus.key).toBe(paragraphNodeKey);
|
||||||
@ -1820,7 +1858,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
describe('Selection correctly resolves to a sibling ElementNode that has multiple children with the correct offset when a node is removed', () => {
|
describe('Selection correctly resolves to a sibling ElementNode that has multiple children with the correct offset when a node is removed', () => {
|
||||||
test('', async () => {
|
test('', async () => {
|
||||||
await ReactTestUtils.act(async () => {
|
await ReactTestUtils.act(async () => {
|
||||||
await editor.update(() => {
|
await editor!.update(() => {
|
||||||
// Arrange
|
// Arrange
|
||||||
// Root
|
// Root
|
||||||
// |- Paragraph
|
// |- Paragraph
|
||||||
@ -1871,10 +1909,10 @@ describe('LexicalSelection tests', () => {
|
|||||||
|
|
||||||
test('isBackward', async () => {
|
test('isBackward', async () => {
|
||||||
await ReactTestUtils.act(async () => {
|
await ReactTestUtils.act(async () => {
|
||||||
await editor.update(() => {
|
await editor!.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const paragraph = root.getFirstChild<ParagraphNode>();
|
const paragraph = root.getFirstChild<ParagraphNode>()!;
|
||||||
const paragraphKey = paragraph.getKey();
|
const paragraphKey = paragraph.getKey();
|
||||||
const textNode = $createTextNode('foo');
|
const textNode = $createTextNode('foo');
|
||||||
const textNodeKey = textNode.getKey();
|
const textNodeKey = textNode.getKey();
|
||||||
@ -1914,7 +1952,18 @@ describe('LexicalSelection tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Decorator text content for selection', () => {
|
describe('Decorator text content for selection', () => {
|
||||||
[
|
const baseCases: {
|
||||||
|
name: string;
|
||||||
|
fn: (opts: {
|
||||||
|
textNode1: TextNode;
|
||||||
|
textNode2: TextNode;
|
||||||
|
decorator: DecoratorNode<unknown>;
|
||||||
|
paragraph: ParagraphNode;
|
||||||
|
anchor: PointType;
|
||||||
|
focus: PointType;
|
||||||
|
}) => string;
|
||||||
|
invertSelection?: true;
|
||||||
|
}[] = [
|
||||||
{
|
{
|
||||||
fn: ({textNode1, anchor, focus}) => {
|
fn: ({textNode1, anchor, focus}) => {
|
||||||
anchor.set(textNode1.getKey(), 1, 'text');
|
anchor.set(textNode1.getKey(), 1, 'text');
|
||||||
@ -1971,23 +2020,24 @@ describe('LexicalSelection tests', () => {
|
|||||||
},
|
},
|
||||||
name: 'Included if decorator is selected as the only node',
|
name: 'Included if decorator is selected as the only node',
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
.reduce((testSuite, testCase) => {
|
baseCases
|
||||||
|
.flatMap((testCase) => {
|
||||||
const inverse = {
|
const inverse = {
|
||||||
...testCase,
|
...testCase,
|
||||||
invertSelection: true,
|
invertSelection: true,
|
||||||
name: testCase.name + ' (inverse selection)',
|
name: testCase.name + ' (inverse selection)',
|
||||||
};
|
};
|
||||||
|
|
||||||
return testSuite.concat(testCase, inverse);
|
return [testCase, inverse];
|
||||||
}, [])
|
})
|
||||||
.forEach(({name, fn, invertSelection}) => {
|
.forEach(({name, fn, invertSelection}) => {
|
||||||
it(name, async () => {
|
it(name, async () => {
|
||||||
await ReactTestUtils.act(async () => {
|
await ReactTestUtils.act(async () => {
|
||||||
await editor.update(() => {
|
await editor!.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const paragraph = root.getFirstChild<ParagraphNode>();
|
const paragraph = root.getFirstChild<ParagraphNode>()!;
|
||||||
const textNode1 = $createTextNode('1');
|
const textNode1 = $createTextNode('1');
|
||||||
const textNode2 = $createTextNode('2');
|
const textNode2 = $createTextNode('2');
|
||||||
const decorator = $createTestDecoratorNode();
|
const decorator = $createTestDecoratorNode();
|
||||||
@ -2113,7 +2163,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
it('adjust offset for inline elements text formatting', async () => {
|
it('adjust offset for inline elements text formatting', async () => {
|
||||||
init();
|
init();
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor!.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
|
|
||||||
const text1 = $createTextNode('--');
|
const text1 = $createTextNode('--');
|
||||||
@ -2154,7 +2204,11 @@ describe('LexicalSelection tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Node.replace', () => {
|
describe('Node.replace', () => {
|
||||||
let text1, text2, text3, paragraph, testEditor;
|
let text1: TextNode,
|
||||||
|
text2: TextNode,
|
||||||
|
text3: TextNode,
|
||||||
|
paragraph: ParagraphNode,
|
||||||
|
testEditor: LexicalEditor;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
testEditor = createTestEditor();
|
testEditor = createTestEditor();
|
||||||
@ -2481,7 +2535,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
expect(rootChildren[0].__type).toBe('heading');
|
expect(rootChildren[0].__type).toBe('heading');
|
||||||
expect(rootChildren[1].__type).toBe('heading');
|
expect(rootChildren[1].__type).toBe('heading');
|
||||||
expect(rootChildren.length).toBe(2);
|
expect(rootChildren.length).toBe(2);
|
||||||
const sel = $getSelection();
|
const sel = $getSelection()!;
|
||||||
expect(sel.getNodes().length).toBe(2);
|
expect(sel.getNodes().length).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -2540,7 +2594,7 @@ describe('LexicalSelection tests', () => {
|
|||||||
const paragraph = column.getFirstChild();
|
const paragraph = column.getFirstChild();
|
||||||
invariant($isElementNode(paragraph));
|
invariant($isElementNode(paragraph));
|
||||||
if (paragraph.getFirstChild()) {
|
if (paragraph.getFirstChild()) {
|
||||||
paragraph.getFirstChild().remove();
|
paragraph.getFirstChild()!.remove();
|
||||||
}
|
}
|
||||||
root.append(table);
|
root.append(table);
|
||||||
|
|
||||||
|
@ -25,6 +25,9 @@ import {
|
|||||||
$isParagraphNode,
|
$isParagraphNode,
|
||||||
$isRangeSelection,
|
$isRangeSelection,
|
||||||
$setSelection,
|
$setSelection,
|
||||||
|
ElementNode,
|
||||||
|
LexicalEditor,
|
||||||
|
ParagraphNode,
|
||||||
RangeSelection,
|
RangeSelection,
|
||||||
TextNode,
|
TextNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
@ -59,9 +62,12 @@ Range.prototype.getBoundingClientRect = function (): DOMRect {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function createParagraphWithNodes(editor, nodes) {
|
function createParagraphWithNodes(
|
||||||
|
editor: LexicalEditor,
|
||||||
|
nodes: {text: string; key: string; mergeable?: boolean}[],
|
||||||
|
) {
|
||||||
const paragraph = $createParagraphNode();
|
const paragraph = $createParagraphNode();
|
||||||
const nodeMap = editor._pendingEditorState._nodeMap;
|
const nodeMap = editor._pendingEditorState!._nodeMap;
|
||||||
|
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
const {text, key, mergeable} = nodes[i];
|
const {text, key, mergeable} = nodes[i];
|
||||||
@ -81,7 +87,9 @@ function createParagraphWithNodes(editor, nodes) {
|
|||||||
describe('LexicalSelectionHelpers tests', () => {
|
describe('LexicalSelectionHelpers tests', () => {
|
||||||
describe('Collapsed', () => {
|
describe('Collapsed', () => {
|
||||||
test('Can handle a text point', () => {
|
test('Can handle a text point', () => {
|
||||||
const setupTestCase = (cb) => {
|
const setupTestCase = (
|
||||||
|
cb: (selection: RangeSelection, node: ElementNode) => void,
|
||||||
|
) => {
|
||||||
const editor = createTestEditor();
|
const editor = createTestEditor();
|
||||||
|
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
@ -119,7 +127,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
});
|
});
|
||||||
const selection = $getSelection();
|
const selection = $getSelection();
|
||||||
cb(selection, element);
|
cb(selection as RangeSelection, element);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -137,7 +145,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
setupTestCase((selection, state) => {
|
setupTestCase((selection, state) => {
|
||||||
selection.insertText('Test');
|
selection.insertText('Test');
|
||||||
|
|
||||||
expect($getNodeByKey('a').getTextContent()).toBe('Testa');
|
expect($getNodeByKey('a')!.getTextContent()).toBe('Testa');
|
||||||
|
|
||||||
expect(selection.anchor).toEqual(
|
expect(selection.anchor).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@ -162,7 +170,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
|
|
||||||
expect(selection.anchor).toEqual(
|
expect(selection.anchor).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
key: element.getFirstChild().getKey(),
|
key: element.getFirstChild()!.getKey(),
|
||||||
offset: 3,
|
offset: 3,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
}),
|
}),
|
||||||
@ -170,7 +178,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
|
|
||||||
expect(selection.focus).toEqual(
|
expect(selection.focus).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
key: element.getFirstChild().getKey(),
|
key: element.getFirstChild()!.getKey(),
|
||||||
offset: 3,
|
offset: 3,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
}),
|
}),
|
||||||
@ -224,11 +232,11 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
selection.formatText('bold');
|
selection.formatText('bold');
|
||||||
selection.insertText('Test');
|
selection.insertText('Test');
|
||||||
|
|
||||||
expect(element.getFirstChild().getTextContent()).toBe('Test');
|
expect(element.getFirstChild()!.getTextContent()).toBe('Test');
|
||||||
|
|
||||||
expect(selection.anchor).toEqual(
|
expect(selection.anchor).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
key: element.getFirstChild().getKey(),
|
key: element.getFirstChild()!.getKey(),
|
||||||
offset: 4,
|
offset: 4,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
}),
|
}),
|
||||||
@ -236,15 +244,15 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
|
|
||||||
expect(selection.focus).toEqual(
|
expect(selection.focus).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
key: element.getFirstChild().getKey(),
|
key: element.getFirstChild()!.getKey(),
|
||||||
offset: 4,
|
offset: 4,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(element.getFirstChild().getNextSibling().getTextContent()).toBe(
|
expect(
|
||||||
'a',
|
element.getFirstChild()!.getNextSibling()!.getTextContent(),
|
||||||
);
|
).toBe('a');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Extract selection
|
// Extract selection
|
||||||
@ -414,7 +422,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
const editor = createTestEditor();
|
const editor = createTestEditor();
|
||||||
|
|
||||||
const domElement = document.createElement('div');
|
const domElement = document.createElement('div');
|
||||||
let element;
|
let element: ParagraphNode;
|
||||||
|
|
||||||
editor.setRootElement(domElement);
|
editor.setRootElement(domElement);
|
||||||
|
|
||||||
@ -690,7 +698,9 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Can handle an element point on empty element', () => {
|
test('Can handle an element point on empty element', () => {
|
||||||
const setupTestCase = (cb) => {
|
const setupTestCase = (
|
||||||
|
cb: (selection: RangeSelection, el: ElementNode) => void,
|
||||||
|
) => {
|
||||||
const editor = createTestEditor();
|
const editor = createTestEditor();
|
||||||
|
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
@ -712,7 +722,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
type: 'element',
|
type: 'element',
|
||||||
});
|
});
|
||||||
const selection = $getSelection();
|
const selection = $getSelection();
|
||||||
cb(selection, element);
|
cb(selection as RangeSelection, element);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -729,7 +739,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
// insertText
|
// insertText
|
||||||
setupTestCase((selection, element) => {
|
setupTestCase((selection, element) => {
|
||||||
selection.insertText('Test');
|
selection.insertText('Test');
|
||||||
const firstChild = element.getFirstChild();
|
const firstChild = element.getFirstChild()!;
|
||||||
|
|
||||||
expect(firstChild.getTextContent()).toBe('Test');
|
expect(firstChild.getTextContent()).toBe('Test');
|
||||||
|
|
||||||
@ -753,7 +763,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
// insertParagraph
|
// insertParagraph
|
||||||
setupTestCase((selection, element) => {
|
setupTestCase((selection, element) => {
|
||||||
selection.insertParagraph();
|
selection.insertParagraph();
|
||||||
const nextElement = element.getNextSibling();
|
const nextElement = element.getNextSibling()!;
|
||||||
|
|
||||||
expect(selection.anchor).toEqual(
|
expect(selection.anchor).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@ -797,7 +807,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
setupTestCase((selection, element) => {
|
setupTestCase((selection, element) => {
|
||||||
selection.formatText('bold');
|
selection.formatText('bold');
|
||||||
selection.insertText('Test');
|
selection.insertText('Test');
|
||||||
const firstChild = element.getFirstChild();
|
const firstChild = element.getFirstChild()!;
|
||||||
|
|
||||||
expect(firstChild.getTextContent()).toBe('Test');
|
expect(firstChild.getTextContent()).toBe('Test');
|
||||||
|
|
||||||
@ -825,7 +835,9 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Can handle a start element point', () => {
|
test('Can handle a start element point', () => {
|
||||||
const setupTestCase = (cb) => {
|
const setupTestCase = (
|
||||||
|
cb: (selection: RangeSelection, el: ElementNode) => void,
|
||||||
|
) => {
|
||||||
const editor = createTestEditor();
|
const editor = createTestEditor();
|
||||||
|
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
@ -863,7 +875,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
type: 'element',
|
type: 'element',
|
||||||
});
|
});
|
||||||
const selection = $getSelection();
|
const selection = $getSelection();
|
||||||
cb(selection, element);
|
cb(selection as RangeSelection, element);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -880,7 +892,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
// insertText
|
// insertText
|
||||||
setupTestCase((selection, element) => {
|
setupTestCase((selection, element) => {
|
||||||
selection.insertText('Test');
|
selection.insertText('Test');
|
||||||
const firstChild = element.getFirstChild();
|
const firstChild = element.getFirstChild()!;
|
||||||
|
|
||||||
expect(firstChild.getTextContent()).toBe('Test');
|
expect(firstChild.getTextContent()).toBe('Test');
|
||||||
|
|
||||||
@ -948,7 +960,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
selection.formatText('bold');
|
selection.formatText('bold');
|
||||||
selection.insertText('Test');
|
selection.insertText('Test');
|
||||||
|
|
||||||
const firstChild = element.getFirstChild();
|
const firstChild = element.getFirstChild()!;
|
||||||
|
|
||||||
expect(firstChild.getTextContent()).toBe('Test');
|
expect(firstChild.getTextContent()).toBe('Test');
|
||||||
|
|
||||||
@ -976,7 +988,9 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Can handle an end element point', () => {
|
test('Can handle an end element point', () => {
|
||||||
const setupTestCase = (cb) => {
|
const setupTestCase = (
|
||||||
|
cb: (selection: RangeSelection, el: ElementNode) => void,
|
||||||
|
) => {
|
||||||
const editor = createTestEditor();
|
const editor = createTestEditor();
|
||||||
|
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
@ -1014,7 +1028,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
type: 'element',
|
type: 'element',
|
||||||
});
|
});
|
||||||
const selection = $getSelection();
|
const selection = $getSelection();
|
||||||
cb(selection, element);
|
cb(selection as RangeSelection, element);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1031,7 +1045,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
// insertText
|
// insertText
|
||||||
setupTestCase((selection, element) => {
|
setupTestCase((selection, element) => {
|
||||||
selection.insertText('Test');
|
selection.insertText('Test');
|
||||||
const lastChild = element.getLastChild();
|
const lastChild = element.getLastChild()!;
|
||||||
|
|
||||||
expect(lastChild.getTextContent()).toBe('Test');
|
expect(lastChild.getTextContent()).toBe('Test');
|
||||||
|
|
||||||
@ -1055,7 +1069,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
// insertParagraph
|
// insertParagraph
|
||||||
setupTestCase((selection, element) => {
|
setupTestCase((selection, element) => {
|
||||||
selection.insertParagraph();
|
selection.insertParagraph();
|
||||||
const nextSibling = element.getNextSibling();
|
const nextSibling = element.getNextSibling()!;
|
||||||
|
|
||||||
expect(selection.anchor).toEqual(
|
expect(selection.anchor).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@ -1099,7 +1113,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
setupTestCase((selection, element) => {
|
setupTestCase((selection, element) => {
|
||||||
selection.formatText('bold');
|
selection.formatText('bold');
|
||||||
selection.insertText('Test');
|
selection.insertText('Test');
|
||||||
const lastChild = element.getLastChild();
|
const lastChild = element.getLastChild()!;
|
||||||
|
|
||||||
expect(lastChild.getTextContent()).toBe('Test');
|
expect(lastChild.getTextContent()).toBe('Test');
|
||||||
|
|
||||||
@ -1271,7 +1285,9 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
|
|
||||||
describe('Simple range', () => {
|
describe('Simple range', () => {
|
||||||
test('Can handle multiple text points', () => {
|
test('Can handle multiple text points', () => {
|
||||||
const setupTestCase = (cb) => {
|
const setupTestCase = (
|
||||||
|
cb: (selection: RangeSelection, el: ElementNode) => void,
|
||||||
|
) => {
|
||||||
const editor = createTestEditor();
|
const editor = createTestEditor();
|
||||||
|
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
@ -1333,7 +1349,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
setupTestCase((selection, state) => {
|
setupTestCase((selection, state) => {
|
||||||
selection.insertText('Test');
|
selection.insertText('Test');
|
||||||
|
|
||||||
expect($getNodeByKey('a').getTextContent()).toBe('Test');
|
expect($getNodeByKey('a')!.getTextContent()).toBe('Test');
|
||||||
|
|
||||||
expect(selection.anchor).toEqual(
|
expect(selection.anchor).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@ -1358,7 +1374,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
|
|
||||||
expect(selection.anchor).toEqual(
|
expect(selection.anchor).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
key: element.getFirstChild().getKey(),
|
key: element.getFirstChild()!.getKey(),
|
||||||
offset: 3,
|
offset: 3,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
}),
|
}),
|
||||||
@ -1366,7 +1382,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
|
|
||||||
expect(selection.focus).toEqual(
|
expect(selection.focus).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
key: element.getFirstChild().getKey(),
|
key: element.getFirstChild()!.getKey(),
|
||||||
offset: 3,
|
offset: 3,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
}),
|
}),
|
||||||
@ -1420,11 +1436,11 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
selection.formatText('bold');
|
selection.formatText('bold');
|
||||||
selection.insertText('Test');
|
selection.insertText('Test');
|
||||||
|
|
||||||
expect(element.getFirstChild().getTextContent()).toBe('Test');
|
expect(element.getFirstChild()!.getTextContent()).toBe('Test');
|
||||||
|
|
||||||
expect(selection.anchor).toEqual(
|
expect(selection.anchor).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
key: element.getFirstChild().getKey(),
|
key: element.getFirstChild()!.getKey(),
|
||||||
offset: 4,
|
offset: 4,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
}),
|
}),
|
||||||
@ -1432,7 +1448,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
|
|
||||||
expect(selection.focus).toEqual(
|
expect(selection.focus).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
key: element.getFirstChild().getKey(),
|
key: element.getFirstChild()!.getKey(),
|
||||||
offset: 4,
|
offset: 4,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
}),
|
}),
|
||||||
@ -1446,7 +1462,9 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Can handle multiple element points', () => {
|
test('Can handle multiple element points', () => {
|
||||||
const setupTestCase = (cb) => {
|
const setupTestCase = (
|
||||||
|
cb: (selection: RangeSelection, el: ElementNode) => void,
|
||||||
|
) => {
|
||||||
const editor = createTestEditor();
|
const editor = createTestEditor();
|
||||||
|
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
@ -1504,7 +1522,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
// insertText
|
// insertText
|
||||||
setupTestCase((selection, element) => {
|
setupTestCase((selection, element) => {
|
||||||
selection.insertText('Test');
|
selection.insertText('Test');
|
||||||
const firstChild = element.getFirstChild();
|
const firstChild = element.getFirstChild()!;
|
||||||
|
|
||||||
expect(firstChild.getTextContent()).toBe('Test');
|
expect(firstChild.getTextContent()).toBe('Test');
|
||||||
|
|
||||||
@ -1571,7 +1589,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
setupTestCase((selection, element) => {
|
setupTestCase((selection, element) => {
|
||||||
selection.formatText('bold');
|
selection.formatText('bold');
|
||||||
selection.insertText('Test');
|
selection.insertText('Test');
|
||||||
const firstChild = element.getFirstChild();
|
const firstChild = element.getFirstChild()!;
|
||||||
|
|
||||||
expect(firstChild.getTextContent()).toBe('Test');
|
expect(firstChild.getTextContent()).toBe('Test');
|
||||||
|
|
||||||
@ -1601,7 +1619,9 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Can handle a mix of text and element points', () => {
|
test('Can handle a mix of text and element points', () => {
|
||||||
const setupTestCase = (cb) => {
|
const setupTestCase = (
|
||||||
|
cb: (selection: RangeSelection, el: ElementNode) => void,
|
||||||
|
) => {
|
||||||
const editor = createTestEditor();
|
const editor = createTestEditor();
|
||||||
|
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
@ -1668,7 +1688,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
// insertText
|
// insertText
|
||||||
setupTestCase((selection, element) => {
|
setupTestCase((selection, element) => {
|
||||||
selection.insertText('Test');
|
selection.insertText('Test');
|
||||||
const firstChild = element.getFirstChild();
|
const firstChild = element.getFirstChild()!;
|
||||||
|
|
||||||
expect(firstChild.getTextContent()).toBe('Test');
|
expect(firstChild.getTextContent()).toBe('Test');
|
||||||
|
|
||||||
@ -1692,7 +1712,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
// insertParagraph
|
// insertParagraph
|
||||||
setupTestCase((selection, element) => {
|
setupTestCase((selection, element) => {
|
||||||
selection.insertParagraph();
|
selection.insertParagraph();
|
||||||
const nextElement = element.getNextSibling();
|
const nextElement = element.getNextSibling()!;
|
||||||
|
|
||||||
expect(selection.anchor).toEqual(
|
expect(selection.anchor).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@ -1736,7 +1756,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
setupTestCase((selection, element) => {
|
setupTestCase((selection, element) => {
|
||||||
selection.formatText('bold');
|
selection.formatText('bold');
|
||||||
selection.insertText('Test');
|
selection.insertText('Test');
|
||||||
const firstChild = element.getFirstChild();
|
const firstChild = element.getFirstChild()!;
|
||||||
|
|
||||||
expect(firstChild.getTextContent()).toBe('Test');
|
expect(firstChild.getTextContent()).toBe('Test');
|
||||||
|
|
||||||
@ -2121,7 +2141,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
expect(selection.anchor).toEqual(
|
expect(selection.anchor).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
key: paragraph
|
key: paragraph
|
||||||
.getChildAtIndex(paragraph.getChildrenSize() - 2)
|
.getChildAtIndex(paragraph.getChildrenSize() - 2)!
|
||||||
.getKey(),
|
.getKey(),
|
||||||
offset: 1,
|
offset: 1,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@ -2131,7 +2151,7 @@ describe('LexicalSelectionHelpers tests', () => {
|
|||||||
expect(selection.focus).toEqual(
|
expect(selection.focus).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
key: paragraph
|
key: paragraph
|
||||||
.getChildAtIndex(paragraph.getChildrenSize() - 2)
|
.getChildAtIndex(paragraph.getChildrenSize() - 2)!
|
||||||
.getKey(),
|
.getKey(),
|
||||||
offset: 1,
|
offset: 1,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@ -2547,7 +2567,7 @@ describe('extract', () => {
|
|||||||
const selection = $getSelection();
|
const selection = $getSelection();
|
||||||
expect($isRangeSelection(selection)).toBeTruthy();
|
expect($isRangeSelection(selection)).toBeTruthy();
|
||||||
|
|
||||||
expect(selection.extract()).toEqual([text]);
|
expect(selection!.extract()).toEqual([text]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -2605,7 +2625,7 @@ describe('insertNodes', () => {
|
|||||||
});
|
});
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const selection = $createRangeSelection();
|
const selection = $createRangeSelection();
|
||||||
const text = $getRoot().getLastDescendant();
|
const text = $getRoot().getLastDescendant()!;
|
||||||
selection.anchor.set(text.getKey(), 0, 'text');
|
selection.anchor.set(text.getKey(), 0, 'text');
|
||||||
selection.focus.set(text.getKey(), 0, 'text');
|
selection.focus.set(text.getKey(), 0, 'text');
|
||||||
|
|
||||||
@ -2629,7 +2649,7 @@ describe('insertNodes', () => {
|
|||||||
$createParagraphNode().append(emptyTextNode, $createTextNode('text')),
|
$createParagraphNode().append(emptyTextNode, $createTextNode('text')),
|
||||||
);
|
);
|
||||||
emptyTextNode.select(0, 0);
|
emptyTextNode.select(0, 0);
|
||||||
const selection = $getSelection();
|
const selection = $getSelection()!;
|
||||||
expect($isRangeSelection(selection)).toBeTruthy();
|
expect($isRangeSelection(selection)).toBeTruthy();
|
||||||
selection.insertNodes([$createTextNode('foo')]);
|
selection.insertNodes([$createTextNode('foo')]);
|
||||||
|
|
||||||
@ -2692,7 +2712,7 @@ describe('$patchStyleText', () => {
|
|||||||
const link = $createLinkNode('https://');
|
const link = $createLinkNode('https://');
|
||||||
link.append($createTextNode('link'));
|
link.append($createTextNode('link'));
|
||||||
|
|
||||||
const a = $getNodeByKey('a');
|
const a = $getNodeByKey('a')!;
|
||||||
a.insertAfter(link);
|
a.insertAfter(link);
|
||||||
|
|
||||||
setAnchorPoint({
|
setAnchorPoint({
|
||||||
@ -2793,7 +2813,7 @@ describe('$patchStyleText', () => {
|
|||||||
const link = $createLinkNode('https://');
|
const link = $createLinkNode('https://');
|
||||||
link.append($createTextNode('link'));
|
link.append($createTextNode('link'));
|
||||||
|
|
||||||
const a = $getNodeByKey('a');
|
const a = $getNodeByKey('a')!;
|
||||||
a.insertAfter(link);
|
a.insertAfter(link);
|
||||||
|
|
||||||
setAnchorPoint({
|
setAnchorPoint({
|
||||||
@ -2846,7 +2866,7 @@ describe('$patchStyleText', () => {
|
|||||||
const link = $createLinkNode('https://');
|
const link = $createLinkNode('https://');
|
||||||
link.append($createTextNode('link'));
|
link.append($createTextNode('link'));
|
||||||
|
|
||||||
const a = $getNodeByKey('a');
|
const a = $getNodeByKey('a')!;
|
||||||
a.insertAfter(link);
|
a.insertAfter(link);
|
||||||
|
|
||||||
// Select from the end of the link _element_
|
// Select from the end of the link _element_
|
||||||
|
@ -12,6 +12,8 @@ import {
|
|||||||
$isNodeSelection,
|
$isNodeSelection,
|
||||||
$isRangeSelection,
|
$isRangeSelection,
|
||||||
$isTextNode,
|
$isTextNode,
|
||||||
|
LexicalEditor,
|
||||||
|
PointType,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
Object.defineProperty(HTMLElement.prototype, 'contentEditable', {
|
Object.defineProperty(HTMLElement.prototype, 'contentEditable', {
|
||||||
@ -49,7 +51,7 @@ if (!Selection.prototype.modify) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getWordsFromString = function (string: string): Array<Segment> {
|
const getWordsFromString = function (string: string): Array<Segment> {
|
||||||
const segments = [];
|
const segments: Segment[] = [];
|
||||||
let wordString = '';
|
let wordString = '';
|
||||||
let nonWordString = '';
|
let nonWordString = '';
|
||||||
let i;
|
let i;
|
||||||
@ -90,7 +92,8 @@ if (!Selection.prototype.modify) {
|
|||||||
// This is not a thorough implementation, it was more to get tests working
|
// This is not a thorough implementation, it was more to get tests working
|
||||||
// given the refactor to use this selection method.
|
// given the refactor to use this selection method.
|
||||||
const symbol = Object.getOwnPropertySymbols(this)[0];
|
const symbol = Object.getOwnPropertySymbols(this)[0];
|
||||||
const impl = this[symbol];
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const impl = (this as any)[symbol];
|
||||||
const focus = impl._focus;
|
const focus = impl._focus;
|
||||||
const anchor = impl._anchor;
|
const anchor = impl._anchor;
|
||||||
|
|
||||||
@ -164,11 +167,11 @@ if (!Selection.prototype.modify) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (granularity === 'word') {
|
} else if (granularity === 'word') {
|
||||||
const anchorNode = this.anchorNode;
|
const anchorNode = this.anchorNode!;
|
||||||
const targetTextContent =
|
const targetTextContent =
|
||||||
direction === 'backward'
|
direction === 'backward'
|
||||||
? anchorNode.textContent.slice(0, this.anchorOffset)
|
? anchorNode.textContent!.slice(0, this.anchorOffset)
|
||||||
: anchorNode.textContent.slice(this.anchorOffset);
|
: anchorNode.textContent!.slice(this.anchorOffset);
|
||||||
const segments = getWordsFromString(targetTextContent);
|
const segments = getWordsFromString(targetTextContent);
|
||||||
const segmentsLength = segments.length;
|
const segmentsLength = segments.length;
|
||||||
let index = anchor.offset;
|
let index = anchor.offset;
|
||||||
@ -218,27 +221,27 @@ if (!Selection.prototype.modify) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function printWhitespace(whitespaceCharacter) {
|
export function printWhitespace(whitespaceCharacter: string) {
|
||||||
return whitespaceCharacter.charCodeAt(0) === 160
|
return whitespaceCharacter.charCodeAt(0) === 160
|
||||||
? ' '
|
? ' '
|
||||||
: whitespaceCharacter;
|
: whitespaceCharacter;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function insertText(text) {
|
export function insertText(text: string) {
|
||||||
return {
|
return {
|
||||||
text,
|
text,
|
||||||
type: 'insert_text',
|
type: 'insert_text',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function insertTokenNode(text) {
|
export function insertTokenNode(text: string) {
|
||||||
return {
|
return {
|
||||||
text,
|
text,
|
||||||
type: 'insert_token_node',
|
type: 'insert_token_node',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function insertSegmentedNode(text) {
|
export function insertSegmentedNode(text: string) {
|
||||||
return {
|
return {
|
||||||
text,
|
text,
|
||||||
type: 'insert_segmented_node',
|
type: 'insert_segmented_node',
|
||||||
@ -385,10 +388,10 @@ export function pasteHTML(text: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function moveNativeSelection(
|
export function moveNativeSelection(
|
||||||
anchorPath,
|
anchorPath: number[],
|
||||||
anchorOffset,
|
anchorOffset: number,
|
||||||
focusPath,
|
focusPath: number[],
|
||||||
focusOffset,
|
focusOffset: number,
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
anchorOffset,
|
anchorOffset,
|
||||||
@ -399,7 +402,7 @@ export function moveNativeSelection(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNodeFromPath(path, rootElement) {
|
export function getNodeFromPath(path: number[], rootElement: Node) {
|
||||||
let node = rootElement;
|
let node = rootElement;
|
||||||
|
|
||||||
for (let i = 0; i < path.length; i++) {
|
for (let i = 0; i < path.length; i++) {
|
||||||
@ -410,12 +413,12 @@ export function getNodeFromPath(path, rootElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function setNativeSelection(
|
export function setNativeSelection(
|
||||||
anchorNode,
|
anchorNode: Node,
|
||||||
anchorOffset,
|
anchorOffset: number,
|
||||||
focusNode,
|
focusNode: Node,
|
||||||
focusOffset,
|
focusOffset: number,
|
||||||
) {
|
) {
|
||||||
const domSelection = window.getSelection();
|
const domSelection = window.getSelection()!;
|
||||||
const range = document.createRange();
|
const range = document.createRange();
|
||||||
range.setStart(anchorNode, anchorOffset);
|
range.setStart(anchorNode, anchorOffset);
|
||||||
range.setEnd(focusNode, focusOffset);
|
range.setEnd(focusNode, focusOffset);
|
||||||
@ -427,18 +430,18 @@ export function setNativeSelection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function setNativeSelectionWithPaths(
|
export function setNativeSelectionWithPaths(
|
||||||
rootElement,
|
rootElement: Node,
|
||||||
anchorPath,
|
anchorPath: number[],
|
||||||
anchorOffset,
|
anchorOffset: number,
|
||||||
focusPath,
|
focusPath: number[],
|
||||||
focusOffset,
|
focusOffset: number,
|
||||||
) {
|
) {
|
||||||
const anchorNode = getNodeFromPath(anchorPath, rootElement);
|
const anchorNode = getNodeFromPath(anchorPath, rootElement);
|
||||||
const focusNode = getNodeFromPath(focusPath, rootElement);
|
const focusNode = getNodeFromPath(focusPath, rootElement);
|
||||||
setNativeSelection(anchorNode, anchorOffset, focusNode, focusOffset);
|
setNativeSelection(anchorNode, anchorOffset, focusNode, focusOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLastTextNode(startingNode) {
|
function getLastTextNode(startingNode: Node) {
|
||||||
let node = startingNode;
|
let node = startingNode;
|
||||||
|
|
||||||
mainLoop: while (node !== null) {
|
mainLoop: while (node !== null) {
|
||||||
@ -477,7 +480,7 @@ function getLastTextNode(startingNode) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNextTextNode(startingNode) {
|
function getNextTextNode(startingNode: Node) {
|
||||||
let node = startingNode;
|
let node = startingNode;
|
||||||
|
|
||||||
mainLoop: while (node !== null) {
|
mainLoop: while (node !== null) {
|
||||||
@ -517,12 +520,14 @@ function getNextTextNode(startingNode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function moveNativeSelectionBackward() {
|
function moveNativeSelectionBackward() {
|
||||||
const domSelection = window.getSelection();
|
const domSelection = window.getSelection()!;
|
||||||
let {anchorNode, anchorOffset} = domSelection;
|
let anchorNode = domSelection.anchorNode!;
|
||||||
|
let anchorOffset = domSelection.anchorOffset!;
|
||||||
|
|
||||||
if (domSelection.isCollapsed) {
|
if (domSelection.isCollapsed) {
|
||||||
const target =
|
const target = (
|
||||||
anchorNode.nodeType === 1 ? anchorNode : anchorNode.parentNode;
|
anchorNode.nodeType === 1 ? anchorNode : anchorNode.parentNode
|
||||||
|
)!;
|
||||||
const keyDownEvent = new KeyboardEvent('keydown', {
|
const keyDownEvent = new KeyboardEvent('keydown', {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
@ -539,7 +544,7 @@ function moveNativeSelectionBackward() {
|
|||||||
if (lastTextNode === null) {
|
if (lastTextNode === null) {
|
||||||
throw new Error('moveNativeSelectionBackward: TODO');
|
throw new Error('moveNativeSelectionBackward: TODO');
|
||||||
} else {
|
} else {
|
||||||
const textLength = lastTextNode.nodeValue.length;
|
const textLength = lastTextNode.nodeValue!.length;
|
||||||
setNativeSelection(
|
setNativeSelection(
|
||||||
lastTextNode,
|
lastTextNode,
|
||||||
textLength,
|
textLength,
|
||||||
@ -557,7 +562,7 @@ function moveNativeSelectionBackward() {
|
|||||||
}
|
}
|
||||||
} else if (anchorNode.nodeType === 1) {
|
} else if (anchorNode.nodeType === 1) {
|
||||||
if (anchorNode.nodeName === 'BR') {
|
if (anchorNode.nodeName === 'BR') {
|
||||||
const parentNode = anchorNode.parentNode;
|
const parentNode = anchorNode.parentNode!;
|
||||||
const childNodes = Array.from(parentNode.childNodes);
|
const childNodes = Array.from(parentNode.childNodes);
|
||||||
anchorOffset = childNodes.indexOf(anchorNode as ChildNode);
|
anchorOffset = childNodes.indexOf(anchorNode as ChildNode);
|
||||||
anchorNode = parentNode;
|
anchorNode = parentNode;
|
||||||
@ -584,12 +589,14 @@ function moveNativeSelectionBackward() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function moveNativeSelectionForward() {
|
function moveNativeSelectionForward() {
|
||||||
const domSelection = window.getSelection();
|
const domSelection = window.getSelection()!;
|
||||||
const {anchorNode, anchorOffset} = domSelection;
|
const anchorNode = domSelection.anchorNode!;
|
||||||
|
const anchorOffset = domSelection.anchorOffset!;
|
||||||
|
|
||||||
if (domSelection.isCollapsed) {
|
if (domSelection.isCollapsed) {
|
||||||
const target =
|
const target = (
|
||||||
anchorNode.nodeType === 1 ? anchorNode : anchorNode.parentNode;
|
anchorNode.nodeType === 1 ? anchorNode : anchorNode.parentNode
|
||||||
|
)!;
|
||||||
const keyDownEvent = new KeyboardEvent('keydown', {
|
const keyDownEvent = new KeyboardEvent('keydown', {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
@ -600,7 +607,7 @@ function moveNativeSelectionForward() {
|
|||||||
|
|
||||||
if (!keyDownEvent.defaultPrevented) {
|
if (!keyDownEvent.defaultPrevented) {
|
||||||
if (anchorNode.nodeType === 3) {
|
if (anchorNode.nodeType === 3) {
|
||||||
const text = anchorNode.nodeValue;
|
const text = anchorNode.nodeValue!;
|
||||||
|
|
||||||
if (text.length === anchorOffset) {
|
if (text.length === anchorOffset) {
|
||||||
const nextTextNode = getNextTextNode(anchorNode);
|
const nextTextNode = getNextTextNode(anchorNode);
|
||||||
@ -635,8 +642,13 @@ function moveNativeSelectionForward() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function applySelectionInputs(inputs, update, editor) {
|
export async function applySelectionInputs(
|
||||||
const rootElement = editor.getRootElement();
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
inputs: Record<string, any>[],
|
||||||
|
update: (fn: () => void) => Promise<void>,
|
||||||
|
editor: LexicalEditor,
|
||||||
|
) {
|
||||||
|
const rootElement = editor.getRootElement()!;
|
||||||
|
|
||||||
for (let i = 0; i < inputs.length; i++) {
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
const input = inputs[i];
|
const input = inputs[i];
|
||||||
@ -644,7 +656,7 @@ export async function applySelectionInputs(inputs, update, editor) {
|
|||||||
|
|
||||||
for (let j = 0; j < times; j++) {
|
for (let j = 0; j < times; j++) {
|
||||||
await update(() => {
|
await update(() => {
|
||||||
const selection = $getSelection();
|
const selection = $getSelection()!;
|
||||||
|
|
||||||
switch (input.type) {
|
switch (input.type) {
|
||||||
case 'insert_text': {
|
case 'insert_text': {
|
||||||
@ -800,7 +812,7 @@ export async function applySelectionInputs(inputs, update, editor) {
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
clipboardData: {
|
clipboardData: {
|
||||||
getData: (type) => {
|
getData: (type: string) => {
|
||||||
if (type === 'text/plain') {
|
if (type === 'text/plain') {
|
||||||
return input.text;
|
return input.text;
|
||||||
}
|
}
|
||||||
@ -823,7 +835,7 @@ export async function applySelectionInputs(inputs, update, editor) {
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
clipboardData: {
|
clipboardData: {
|
||||||
getData: (type) => {
|
getData: (type: string) => {
|
||||||
if (type === 'application/x-lexical-editor') {
|
if (type === 'application/x-lexical-editor') {
|
||||||
return input.text;
|
return input.text;
|
||||||
}
|
}
|
||||||
@ -846,7 +858,7 @@ export async function applySelectionInputs(inputs, update, editor) {
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
clipboardData: {
|
clipboardData: {
|
||||||
getData: (type) => {
|
getData: (type: string) => {
|
||||||
if (type === 'text/html') {
|
if (type === 'text/html') {
|
||||||
return input.text;
|
return input.text;
|
||||||
}
|
}
|
||||||
@ -865,7 +877,9 @@ export async function applySelectionInputs(inputs, update, editor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setAnchorPoint(point) {
|
export function setAnchorPoint(
|
||||||
|
point: Pick<PointType, 'type' | 'offset' | 'key'>,
|
||||||
|
) {
|
||||||
const selection = $getSelection();
|
const selection = $getSelection();
|
||||||
|
|
||||||
if (!$isRangeSelection(selection)) {
|
if (!$isRangeSelection(selection)) {
|
||||||
@ -884,7 +898,9 @@ export function setAnchorPoint(point) {
|
|||||||
anchor.key = point.key;
|
anchor.key = point.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setFocusPoint(point) {
|
export function setFocusPoint(
|
||||||
|
point: Pick<PointType, 'type' | 'offset' | 'key'>,
|
||||||
|
) {
|
||||||
const selection = $getSelection();
|
const selection = $getSelection();
|
||||||
|
|
||||||
if (!$isRangeSelection(selection)) {
|
if (!$isRangeSelection(selection)) {
|
||||||
|
@ -13,23 +13,27 @@ import {
|
|||||||
$createTextNode,
|
$createTextNode,
|
||||||
$getRoot,
|
$getRoot,
|
||||||
$setSelection,
|
$setSelection,
|
||||||
|
EditorState,
|
||||||
|
ParagraphNode,
|
||||||
|
RootNode,
|
||||||
|
TextNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
import {createTestEditor} from 'lexical/src/__tests__/utils';
|
import {createTestEditor} from 'lexical/src/__tests__/utils';
|
||||||
import {createRef, useEffect, useMemo} from 'react';
|
import {createRef, useEffect, useMemo} from 'react';
|
||||||
import {createRoot} from 'react-dom/client';
|
import {createRoot, Root} from 'react-dom/client';
|
||||||
import * as ReactTestUtils from 'react-dom/test-utils';
|
import * as ReactTestUtils from 'react-dom/test-utils';
|
||||||
|
|
||||||
describe('table selection', () => {
|
describe('table selection', () => {
|
||||||
let originalText;
|
let originalText: TextNode;
|
||||||
let parsedParagraph;
|
let parsedParagraph: ParagraphNode;
|
||||||
let parsedRoot;
|
let parsedRoot: RootNode;
|
||||||
let parsedText;
|
let parsedText: TextNode;
|
||||||
let paragraphKey;
|
let paragraphKey: string;
|
||||||
let textKey;
|
let textKey: string;
|
||||||
let parsedEditorState;
|
let parsedEditorState: EditorState;
|
||||||
let reactRoot;
|
let reactRoot: Root;
|
||||||
let container: HTMLDivElement | null = null;
|
let container: HTMLDivElement | null = null;
|
||||||
let editor: LexicalEditor = null;
|
let editor: LexicalEditor | null = null;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
container = document.createElement('div');
|
container = document.createElement('div');
|
||||||
@ -37,7 +41,10 @@ describe('table selection', () => {
|
|||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
});
|
});
|
||||||
|
|
||||||
function useLexicalEditor(rootElementRef, onError) {
|
function useLexicalEditor(
|
||||||
|
rootElementRef: React.RefObject<HTMLDivElement>,
|
||||||
|
onError?: () => void,
|
||||||
|
) {
|
||||||
const editorInHook = useMemo(
|
const editorInHook = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createTestEditor({
|
createTestEditor({
|
||||||
@ -77,8 +84,8 @@ describe('table selection', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function update(fn) {
|
async function update(fn: () => void) {
|
||||||
editor.update(fn);
|
editor!.update(fn);
|
||||||
|
|
||||||
return Promise.resolve().then();
|
return Promise.resolve().then();
|
||||||
}
|
}
|
||||||
@ -101,15 +108,15 @@ describe('table selection', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const stringifiedEditorState = JSON.stringify(
|
const stringifiedEditorState = JSON.stringify(
|
||||||
editor.getEditorState().toJSON(),
|
editor!.getEditorState().toJSON(),
|
||||||
);
|
);
|
||||||
|
|
||||||
parsedEditorState = editor.parseEditorState(stringifiedEditorState);
|
parsedEditorState = editor!.parseEditorState(stringifiedEditorState);
|
||||||
parsedEditorState.read(() => {
|
parsedEditorState.read(() => {
|
||||||
parsedRoot = $getRoot();
|
parsedRoot = $getRoot();
|
||||||
parsedParagraph = parsedRoot.getFirstChild();
|
parsedParagraph = parsedRoot.getFirstChild()!;
|
||||||
paragraphKey = parsedParagraph.getKey();
|
paragraphKey = parsedParagraph.getKey();
|
||||||
parsedText = parsedParagraph.getFirstChild();
|
parsedText = parsedParagraph.getFirstChild()!;
|
||||||
textKey = parsedText.getKey();
|
textKey = parsedText.getKey();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
pasteHTML,
|
pasteHTML,
|
||||||
} from '@lexical/selection/src/__tests__/utils';
|
} from '@lexical/selection/src/__tests__/utils';
|
||||||
import {TableCellNode, TableNode, TableRowNode} from '@lexical/table';
|
import {TableCellNode, TableNode, TableRowNode} from '@lexical/table';
|
||||||
|
import {LexicalEditor} from 'lexical';
|
||||||
import {initializeClipboard, TestComposer} from 'lexical/src/__tests__/utils';
|
import {initializeClipboard, TestComposer} from 'lexical/src/__tests__/utils';
|
||||||
import {createRoot} from 'react-dom/client';
|
import {createRoot} from 'react-dom/client';
|
||||||
import * as ReactTestUtils from 'react-dom/test-utils';
|
import * as ReactTestUtils from 'react-dom/test-utils';
|
||||||
@ -72,7 +73,7 @@ Range.prototype.getBoundingClientRect = function (): DOMRect {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('LexicalEventHelpers', () => {
|
describe('LexicalEventHelpers', () => {
|
||||||
let container = null;
|
let container: HTMLDivElement | null = null;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
container = document.createElement('div');
|
container = document.createElement('div');
|
||||||
@ -81,15 +82,15 @@ describe('LexicalEventHelpers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
document.body.removeChild(container);
|
document.body.removeChild(container!);
|
||||||
container = null;
|
container = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
let editor = null;
|
let editor: LexicalEditor | null = null;
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
function TestBase() {
|
function TestBase() {
|
||||||
function TestPlugin(): JSX.Element {
|
function TestPlugin(): null {
|
||||||
[editor] = useLexicalComposerContext();
|
[editor] = useLexicalComposerContext();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -146,8 +147,8 @@ describe('LexicalEventHelpers', () => {
|
|||||||
}}>
|
}}>
|
||||||
<RichTextPlugin
|
<RichTextPlugin
|
||||||
contentEditable={
|
contentEditable={
|
||||||
// eslint-disable-next-line jsx-a11y/aria-role
|
// eslint-disable-next-line jsx-a11y/aria-role, @typescript-eslint/no-explicit-any
|
||||||
<ContentEditable role={null} spellCheck={null} />
|
<ContentEditable role={null as any} spellCheck={null as any} />
|
||||||
}
|
}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
ErrorBoundary={LexicalErrorBoundary}
|
ErrorBoundary={LexicalErrorBoundary}
|
||||||
@ -159,20 +160,20 @@ describe('LexicalEventHelpers', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ReactTestUtils.act(() => {
|
ReactTestUtils.act(() => {
|
||||||
createRoot(container).render(<TestBase />);
|
createRoot(container!).render(<TestBase />);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function update(fn) {
|
async function update(fn: () => void) {
|
||||||
await ReactTestUtils.act(async () => {
|
await ReactTestUtils.act(async () => {
|
||||||
await editor.update(fn);
|
await editor!.update(fn);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.resolve().then();
|
return Promise.resolve().then();
|
||||||
}
|
}
|
||||||
|
|
||||||
test('Expect initial output to be a block with no text', () => {
|
test('Expect initial output to be a block with no text', () => {
|
||||||
expect(container.innerHTML).toBe(
|
expect(container!.innerHTML).toBe(
|
||||||
'<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><p class="editor-paragraph"><br></p></div>',
|
'<div contenteditable="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" data-lexical-editor="true"><p class="editor-paragraph"><br></p></div>',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -344,10 +345,10 @@ describe('LexicalEventHelpers', () => {
|
|||||||
const name = testUnit.name || 'Test case';
|
const name = testUnit.name || 'Test case';
|
||||||
|
|
||||||
test(name + ` (#${i + 1})`, async () => {
|
test(name + ` (#${i + 1})`, async () => {
|
||||||
await applySelectionInputs(testUnit.inputs, update, editor);
|
await applySelectionInputs(testUnit.inputs, update, editor!);
|
||||||
|
|
||||||
// Validate HTML matches
|
// Validate HTML matches
|
||||||
expect(container.innerHTML).toBe(testUnit.expectedHTML);
|
expect(container!.innerHTML).toBe(testUnit.expectedHTML);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -400,10 +401,10 @@ describe('LexicalEventHelpers', () => {
|
|||||||
const name = testUnit.name || 'Test case';
|
const name = testUnit.name || 'Test case';
|
||||||
|
|
||||||
test(name + ` (#${i + 1})`, async () => {
|
test(name + ` (#${i + 1})`, async () => {
|
||||||
await applySelectionInputs(testUnit.inputs, update, editor);
|
await applySelectionInputs(testUnit.inputs, update, editor!);
|
||||||
|
|
||||||
// Validate HTML matches
|
// Validate HTML matches
|
||||||
expect(container.innerHTML).toBe(testUnit.expectedHTML);
|
expect(container!.innerHTML).toBe(testUnit.expectedHTML);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -673,12 +674,14 @@ describe('LexicalEventHelpers', () => {
|
|||||||
const name = testUnit.name || 'Test case';
|
const name = testUnit.name || 'Test case';
|
||||||
|
|
||||||
// eslint-disable-next-line no-only-tests/no-only-tests, dot-notation
|
// eslint-disable-next-line no-only-tests/no-only-tests, dot-notation
|
||||||
const test_ = testUnit['only'] ? test.only : test;
|
const test_ = 'only' in testUnit && testUnit['only'] ? test.only : test;
|
||||||
test_(name + ` (#${i + 1})`, async () => {
|
test_(name + ` (#${i + 1})`, async () => {
|
||||||
await applySelectionInputs(testUnit.inputs, update, editor);
|
await applySelectionInputs(testUnit.inputs, update, editor!);
|
||||||
|
|
||||||
// Validate HTML matches
|
// Validate HTML matches
|
||||||
expect(container.firstChild.innerHTML).toBe(testUnit.expectedHTML);
|
expect((container!.firstChild as HTMLElement).innerHTML).toBe(
|
||||||
|
testUnit.expectedHTML,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -127,7 +127,7 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
editor.getEditorState().read(() => {
|
editor.getEditorState().read(() => {
|
||||||
const expectedNodes = expectedKeys.map(({depth, node: nodeKey}) => ({
|
const expectedNodes = expectedKeys.map(({depth, node: nodeKey}) => ({
|
||||||
depth,
|
depth,
|
||||||
node: $getNodeByKey(nodeKey).getLatest(),
|
node: $getNodeByKey(nodeKey)!.getLatest(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const first = expectedNodes[0];
|
const first = expectedNodes[0];
|
||||||
@ -147,10 +147,10 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
test('DFS triggers getLatest()', async () => {
|
test('DFS triggers getLatest()', async () => {
|
||||||
const editor: LexicalEditor = testEnv.editor;
|
const editor: LexicalEditor = testEnv.editor;
|
||||||
|
|
||||||
let rootKey;
|
let rootKey: string;
|
||||||
let paragraphKey;
|
let paragraphKey: string;
|
||||||
let block1Key;
|
let block1Key: string;
|
||||||
let block2Key;
|
let block2Key: string;
|
||||||
|
|
||||||
await editor.update(() => {
|
await editor.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
@ -179,14 +179,14 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
|
|
||||||
block1.append(block3);
|
block1.append(block3);
|
||||||
|
|
||||||
expect($dfs(root)).toEqual([
|
expect($dfs(root!)).toEqual([
|
||||||
{
|
{
|
||||||
depth: 0,
|
depth: 0,
|
||||||
node: root.getLatest(),
|
node: root!.getLatest(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
depth: 1,
|
depth: 1,
|
||||||
node: paragraph.getLatest(),
|
node: paragraph!.getLatest(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
depth: 2,
|
depth: 2,
|
||||||
@ -198,7 +198,7 @@ describe('LexicalNodeHelpers tests', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
depth: 2,
|
depth: 2,
|
||||||
node: block2.getLatest(),
|
node: block2!.getLatest(),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
@ -17,7 +17,7 @@ import {$splitNode} from '../../index';
|
|||||||
describe('LexicalUtils#splitNode', () => {
|
describe('LexicalUtils#splitNode', () => {
|
||||||
let editor: LexicalEditor;
|
let editor: LexicalEditor;
|
||||||
|
|
||||||
const update = async (updateFn) => {
|
const update = async (updateFn: () => void) => {
|
||||||
editor.update(updateFn);
|
editor.update(updateFn);
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
};
|
};
|
||||||
@ -115,7 +115,7 @@ describe('LexicalUtils#splitNode', () => {
|
|||||||
|
|
||||||
let nodeToSplit: ElementNode = $getRoot();
|
let nodeToSplit: ElementNode = $getRoot();
|
||||||
for (const index of testCase.splitPath) {
|
for (const index of testCase.splitPath) {
|
||||||
nodeToSplit = nodeToSplit.getChildAtIndex(index);
|
nodeToSplit = nodeToSplit.getChildAtIndex(index)!;
|
||||||
if (!$isElementNode(nodeToSplit)) {
|
if (!$isElementNode(nodeToSplit)) {
|
||||||
throw new Error('Expected node to be element');
|
throw new Error('Expected node to be element');
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ import {$insertNodeToNearestRoot} from '../..';
|
|||||||
describe('LexicalUtils#insertNodeToNearestRoot', () => {
|
describe('LexicalUtils#insertNodeToNearestRoot', () => {
|
||||||
let editor: LexicalEditor;
|
let editor: LexicalEditor;
|
||||||
|
|
||||||
const update = async (updateFn) => {
|
const update = async (updateFn: () => void) => {
|
||||||
editor.update(updateFn);
|
editor.update(updateFn);
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
};
|
};
|
||||||
@ -155,7 +155,7 @@ describe('LexicalUtils#insertNodeToNearestRoot', () => {
|
|||||||
'Expected node to be element (to traverse the tree)',
|
'Expected node to be element (to traverse the tree)',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
selectionNode = selectionNode.getChildAtIndex(index);
|
selectionNode = selectionNode.getChildAtIndex(index)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calling selectionNode.select() would "normalize" selection and move it
|
// Calling selectionNode.select() would "normalize" selection and move it
|
||||||
|
@ -37,6 +37,55 @@ If you've used React Hooks before, you can think of `$` functions as being somet
|
|||||||
|
|
||||||
Internally, we've found this scales really well and developers get to grips with it in almost no time at all.
|
Internally, we've found this scales really well and developers get to grips with it in almost no time at all.
|
||||||
|
|
||||||
|
## When does reconciliation happen?
|
||||||
|
|
||||||
|
Reconciliation is scheduled with
|
||||||
|
[queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask),
|
||||||
|
which means that it will happen very soon, but asynchronously. This is similar
|
||||||
|
to something like `setTimeout(reconcile, 0)` with a bit more immediacy or
|
||||||
|
`Promise.resolve().then(reconcile)` with less overhead. This is done so
|
||||||
|
that all of the updates that occur as a result of a single logical event will
|
||||||
|
be batched into one reconciliation.
|
||||||
|
|
||||||
|
You can force a reconciliation to take place synchronously with the discrete
|
||||||
|
option to `editor.update` (demonstrated below).
|
||||||
|
|
||||||
|
## Why do tests use `await editor.update(…)`
|
||||||
|
|
||||||
|
You may notice that many tests look like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
await editor.update(updateA);
|
||||||
|
await editor.update(updateB);
|
||||||
|
```
|
||||||
|
|
||||||
|
An astute observer would notice that this seems very strange, since
|
||||||
|
`editor.update()` returns `void` and not `Promise<void>`. However,
|
||||||
|
it does happen to work as you would want it to because
|
||||||
|
the implementation of Promise uses the same microtask queue.
|
||||||
|
|
||||||
|
It's not recommended to rely on this in browser code as it could depend on
|
||||||
|
implementation details of the compilers, bundlers, and VM. It's best to stick
|
||||||
|
to using the `discrete` or the `onUpdate` callback options to be sure that
|
||||||
|
the reconciliation has taken place.
|
||||||
|
|
||||||
|
Ignoring any other microtasks that were scheduled elsewhere,
|
||||||
|
it is roughly equivalent to this synchronous code:
|
||||||
|
|
||||||
|
```js
|
||||||
|
editor.update(updateA, {discrete: true});
|
||||||
|
editor.update(updateB, {discrete: true});
|
||||||
|
```
|
||||||
|
|
||||||
|
At a high level, very roughly, the order of operations looks like this:
|
||||||
|
|
||||||
|
1. `editor.update()` is called
|
||||||
|
2. `updateA()` is called and updates the editor state
|
||||||
|
3. `editor.update()` schedules a reconciliation microtask and returns
|
||||||
|
4. `await` schedules a resume microtask and yields control to the task executor
|
||||||
|
5. the reconciliation microtask runs, reconciling the editor state with the DOM
|
||||||
|
6. the resume microtask runs
|
||||||
|
|
||||||
## How do I listen for user text insertions?
|
## How do I listen for user text insertions?
|
||||||
|
|
||||||
Listening to text insertion events is problematic with content editables in general. It's a common source of bugs due to how
|
Listening to text insertion events is problematic with content editables in general. It's a common source of bugs due to how
|
||||||
|
@ -207,7 +207,6 @@
|
|||||||
"include": ["./libdefs", "./packages"],
|
"include": ["./libdefs", "./packages"],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"./libdefs/*.js",
|
"./libdefs/*.js",
|
||||||
"**/lexical-*/src/__tests__/**",
|
|
||||||
"**/dist/**",
|
"**/dist/**",
|
||||||
"**/npm/**",
|
"**/npm/**",
|
||||||
"**/node_modules/**",
|
"**/node_modules/**",
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
"**/dist/**",
|
"**/dist/**",
|
||||||
"**/npm/**",
|
"**/npm/**",
|
||||||
"**/node_modules/**",
|
"**/node_modules/**",
|
||||||
"./packages/playwright-core/**",
|
|
||||||
"./packages/lexical-devtools/**"
|
"./packages/lexical-devtools/**"
|
||||||
],
|
],
|
||||||
"extends": "./tsconfig.json"
|
"extends": "./tsconfig.json"
|
||||||
|
Reference in New Issue
Block a user