Skip to content

Instantly share code, notes, and snippets.

@fantactuka
Created September 16, 2023 14:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fantactuka/cf83eb3ce3999941baf6f31207a25559 to your computer and use it in GitHub Desktop.
Save fantactuka/cf83eb3ce3999941baf6f31207a25559 to your computer and use it in GitHub Desktop.
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import type {LexicalEditor, LexicalNode} from 'lexical';
import {$generateHtmlFromNodes} from '@lexical/html';
import {$createLinkNode} from '@lexical/link';
import {$createListItemNode, $createListNode} from '@lexical/list';
import {$createHeadingNode} from '@lexical/rich-text';
import {
$createParagraphNode,
$createTextNode,
$getRoot,
$getSelection,
$isRangeSelection,
} from 'lexical';
import {
$createTestDecoratorNode,
createTestEditor,
} from 'lexical/src/__tests__/utils';
type TestCase = {
name: string;
insert: () => Array<LexicalNode>;
expectation: string;
};
describe('LexicalSelection#insertNodes', () => {
let editor: LexicalEditor;
const update = (updateFn) => {
editor.update(updateFn, {discrete: true});
};
const toHtml = (): string => {
return editor.getEditorState().read(() => $generateHtmlFromNodes(editor));
};
beforeEach(async () => {
editor = createTestEditor();
editor._headless = true;
});
// Creates test that would insert provided nodes into current
// selection and match resulted state (converted to html-like string)
// with expected one
const createTest = (testCaseFn: (string) => TestCase) => {
let itFn = it;
const runner = (expectation: string) => {
const testCase = testCaseFn(expectation);
itFn(testCase.name, async () => {
await Promise.resolve();
update(() => {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
throw new Error('Expected range selection');
}
selection.insertNodes(testCase.insert());
});
expect(toHtml()).toEqual(testCase.expectation);
});
};
runner.only = (expectation: string) => {
// eslint-disable-next-line no-only-tests/no-only-tests
itFn = it.only;
runner(expectation);
itFn = it;
};
runner.skip = (expectation: string) => {
// eslint-disable-next-line no-only-tests/no-only-tests
itFn = it.skip;
runner(expectation);
itFn = it;
};
return runner;
};
const testTextNode = createTest((expectation: string): TestCase => {
return {
expectation,
insert() {
return [$createTextNode('inserted text')];
},
name: 'insert text node',
};
});
const testParagraphNode = createTest((expectation: string): TestCase => {
return {
expectation,
insert() {
return [
$createParagraphNode().append($createTextNode('inserted text')),
];
},
name: 'insert paragraph element node',
};
});
const testNonParagraphNode = createTest((expectation: string): TestCase => {
return {
expectation,
insert() {
return [
$createHeadingNode('h1').append($createTextNode('inserted text')),
];
},
name: 'insert non-paragraph element node',
};
});
const testInlineElement = createTest((expectation: string): TestCase => {
return {
expectation,
insert() {
return [
$createLinkNode('https://lexical.dev').append(
$createTextNode('inserted text'),
),
];
},
name: 'insert inline element',
};
});
const testInlineElementAndText = createTest(
(expectation: string): TestCase => {
return {
expectation,
insert() {
return [
$createLinkNode('https://lexical.dev').append(
$createTextNode('inserted'),
),
$createTextNode('text'),
];
},
name: 'insert inline element and text',
};
},
);
const testDecorator = createTest((expectation: string): TestCase => {
return {
expectation,
insert() {
return [$createTestDecoratorNode()];
},
name: 'insert decorator',
};
});
const testDecoratorAndText = createTest((expectation: string): TestCase => {
return {
expectation,
insert() {
return [$createTestDecoratorNode(), $createTextNode('inserted text')];
},
name: 'insert decorator and text',
};
});
const testDecoratorAndInline = createTest((expectation: string): TestCase => {
return {
expectation,
insert() {
return [
$createTestDecoratorNode(),
$createLinkNode('https://lexical.dev').append(
$createTextNode('inserted text'),
),
];
},
name: 'insert decorator and inline',
};
});
const testList = createTest((expectation: string): TestCase => {
return {
expectation,
insert() {
return [
$createListNode('bullet').append(
$createListItemNode().append($createTextNode('item 1')),
$createListItemNode().append($createTextNode('item 2')),
$createListItemNode().append(
$createListNode('bullet').append(
$createListItemNode().append($createTextNode('item 2.1')),
),
),
),
];
},
name: 'insert list',
};
});
describe('inserting into empty element', () => {
beforeEach(() => {
update(() => {
$getRoot().append($createParagraphNode()).selectEnd();
});
});
testTextNode('<p><span>inserted text</span></p>');
testParagraphNode('<p><span>inserted text</span></p>');
testNonParagraphNode('<h1><span>inserted text</span></h1>');
testInlineElement(
'<p><a href="https://lexical.dev"><span>inserted text</span></a></p>',
);
testInlineElementAndText(
'<p><a href="https://lexical.dev"><span>inserted</span></a><span>text</span></p>',
);
testDecorator('<p><test-decorator></test-decorator></p>');
testDecoratorAndText(
'<p><test-decorator></test-decorator><span>inserted text</span></p>',
);
testDecoratorAndInline(
'<p><test-decorator></test-decorator><a href="https://lexical.dev"><span>inserted text</span></a></p>',
);
testList(
'<ul><li value="1"><span>item 1</span></li><li value="2"><span>item 2</span></li><li value="3"><ul><li value="1"><span>item 2.1</span></li></ul></li></ul>',
);
});
describe('inserting into element with text', () => {
beforeEach(() => {
update(() => {
$getRoot()
.append(
$createParagraphNode().append($createTextNode('text before ')),
)
.selectEnd();
});
});
testTextNode('<p><span>text before inserted text</span></p>');
testParagraphNode('<p><span>text before inserted text</span></p>');
testNonParagraphNode('<p><span>text before inserted text</span></p>');
testInlineElement(
'<p><span>text before </span><a href="https://lexical.dev"><span>inserted text</span></a></p>',
);
testInlineElementAndText(
'<p><span>text before </span><a href="https://lexical.dev"><span>inserted</span></a><span>text</span></p>',
);
testDecorator(
'<p><span>text before </span><test-decorator></test-decorator></p>',
);
testDecoratorAndText(
'<p><span>text before </span><test-decorator></test-decorator><span>inserted text</span></p>',
);
testDecoratorAndInline(
'<p><span>text before </span><test-decorator></test-decorator><a href="https://lexical.dev"><span>inserted text</span></a></p>',
);
testList(
'<p><span>text before </span></p><ul><li value="1"><span>item 1</span></li><li value="2"><span>item 2</span></li><li><ul><li value="1"><span>item 2.1</span></li></ul></li></ul>',
);
});
describe('inserting into inline element', () => {
beforeEach(() => {
update(() => {
const textNode = $createTextNode('beforeafter');
$getRoot().append(
$createParagraphNode().append(
$createLinkNode('https://lexical.dev').append(textNode),
),
);
textNode.select(6, 6);
});
});
testTextNode(
'<p><a href="https://lexical.dev"><span>beforeinserted textafter</span></a></p>',
);
testParagraphNode(
'<p><a href="https://lexical.dev"><span>beforeinserted textafter</span></a></p>',
);
testNonParagraphNode(
'<p><a href="https://lexical.dev"><span>beforeinserted textafter</span></a></p>',
);
testInlineElement(
'<p><a href="https://lexical.dev"><span>before</span></a><a href="https://lexical.dev"><span>inserted text</span></a><a href="https://lexical.dev"><span>after</span></a></p>',
);
testInlineElementAndText(
'<p><a href="https://lexical.dev"><span>before</span></a><a href="https://lexical.dev"><span>inserted</span></a><span>textundefined</span></p>',
);
testDecorator(
'<p><a href="https://lexical.dev"><span>before</span><test-decorator></test-decorator><span>after</span></a></p>',
);
testDecoratorAndText(
'<p><a href="https://lexical.dev"><span>before</span><test-decorator></test-decorator><span>inserted textafter</span></a></p>',
);
testDecoratorAndInline(
'<p><a href="https://lexical.dev"><span>before</span><test-decorator></test-decorator><span>inserted textafter</span></a></p>',
);
testList(
'<p><a href="https://lexical.dev">before</a></p>' +
'<ul><li>item 1</li><li>item 2</li><li><ul><li>item 2.1</li></ul></li></ul>' +
'<p><a href="https://lexical.dev"><span>after</span></a></p>',
);
});
describe('inserting after inline element', () => {
beforeEach(() => {
update(() => {
const textAfter = $createTextNode('after');
$getRoot().append(
$createParagraphNode().append(
$createTextNode('before'),
$createLinkNode('https://lexical.dev').append(
$createTextNode('inline'),
),
textAfter,
),
);
textAfter.select(0, 0);
});
});
testTextNode(
'<p><span>before</span><a href="https://lexical.dev"><span>inline</span></a><span>inserted textafter</span></p>',
);
testParagraphNode(
'<p><span>before</span><a href="https://lexical.dev"><span>inline</span></a><span>inserted textafter</span></p>',
);
testNonParagraphNode(
'<p><span>before</span><a href="https://lexical.dev"><span>inline</span></a><span>inserted textafter</span></p>',
);
testInlineElement(
'<p><span>before</span><a href="https://lexical.dev"><span>inline</span></a><a href="https://lexical.dev"><span>inserted text</span></a><span>after</span></p>',
);
testInlineElementAndText(
'<p><span>before</span><a href="https://lexical.dev"><span>inline</span></a><a href="https://lexical.dev"><span>inserted</span></a><span>textafter</span></p>',
);
testDecorator(
'<p><span>before</span><a href="https://lexical.dev"><span>inline</span></a><test-decorator></test-decorator><span>after</span></p>',
);
testDecoratorAndText(
'<p><span>before</span><a href="https://lexical.dev"><span>inline</span></a><test-decorator></test-decorator><span>inserted textafter</span></p>',
);
testDecoratorAndInline(
'<p><span>before</span><a href="https://lexical.dev"><span>inline</span></a><test-decorator></test-decorator><a href="https://lexical.dev"><span>inserted text</span></a>after</p>',
);
testList(
'<p><span>before</span><a href="https://lexical.dev"><span>inline</span></a>after</p>',
);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment