2 * Copyright (c) Meta Platforms, Inc. and affiliates.
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
9 import type {ElementNode, LexicalEditor} from 'lexical';
11 import {$generateHtmlFromNodes, $generateNodesFromDOM} from '@lexical/html';
12 import {$getRoot, $isElementNode} from 'lexical';
13 import {createTestEditor} from 'lexical/__tests__/utils';
15 import {$splitNode} from '../../index';
17 describe('LexicalUtils#splitNode', () => {
18 let editor: LexicalEditor;
20 const update = async (updateFn: () => void) => {
21 editor.update(updateFn);
22 await Promise.resolve();
25 beforeEach(async () => {
26 editor = createTestEditor();
27 editor._headless = true;
30 const testCases: Array<{
34 splitPath: Array<number>;
39 _: 'split paragraph in between two text nodes',
41 '<p><span style="white-space: pre-wrap;">Hello</span></p><p><span style="white-space: pre-wrap;">world</span></p>',
42 initialHtml: '<p><span>Hello</span><span>world</span></p>',
47 _: 'split paragraph before the first text node',
49 '<p><br></p><p><span style="white-space: pre-wrap;">Hello</span><span style="white-space: pre-wrap;">world</span></p>',
50 initialHtml: '<p><span>Hello</span><span>world</span></p>',
55 _: 'split paragraph after the last text node',
57 '<p><span style="white-space: pre-wrap;">Hello</span><span style="white-space: pre-wrap;">world</span></p><p><br></p>',
58 initialHtml: '<p><span>Hello</span><span>world</span></p>',
59 splitOffset: 2, // Any offset that is higher than children size
63 _: 'split list items between two text nodes',
65 '<ul><li><span style="white-space: pre-wrap;">Hello</span></li></ul>' +
66 '<ul><li><span style="white-space: pre-wrap;">world</span></li></ul>',
67 initialHtml: '<ul><li><span>Hello</span><span>world</span></li></ul>',
68 splitOffset: 1, // Any offset that is higher than children size
72 _: 'split list items before the first text node',
74 '<ul><li></li></ul>' +
75 '<ul><li><span style="white-space: pre-wrap;">Hello</span><span style="white-space: pre-wrap;">world</span></li></ul>',
76 initialHtml: '<ul><li><span>Hello</span><span>world</span></li></ul>',
77 splitOffset: 0, // Any offset that is higher than children size
81 _: 'split nested list items',
84 '<li><span style="white-space: pre-wrap;">Before</span></li>' +
85 '<li><ul><li><span style="white-space: pre-wrap;">Hello</span></li></ul></li>' +
88 '<li><ul><li><span style="white-space: pre-wrap;">world</span></li></ul></li>' +
89 '<li><span style="white-space: pre-wrap;">After</span></li>' +
93 '<li><span>Before</span></li>' +
94 '<ul><li><span>Hello</span><span>world</span></li></ul>' +
95 '<li><span>After</span></li>' +
97 splitOffset: 1, // Any offset that is higher than children size
98 splitPath: [0, 1, 0, 0],
102 for (const testCase of testCases) {
103 it(testCase._, async () => {
105 // Running init, update, assert in the same update loop
106 // to skip text nodes normalization (then separate text
107 // nodes will still be separate and represented by its own
108 // spans in html output) and make assertions more precise
109 const parser = new DOMParser();
110 const dom = parser.parseFromString(testCase.initialHtml, 'text/html');
111 const nodesToInsert = $generateNodesFromDOM(editor, dom);
114 .append(...nodesToInsert);
116 let nodeToSplit: ElementNode = $getRoot();
117 for (const index of testCase.splitPath) {
118 nodeToSplit = nodeToSplit.getChildAtIndex(index)!;
119 if (!$isElementNode(nodeToSplit)) {
120 throw new Error('Expected node to be element');
124 $splitNode(nodeToSplit, testCase.splitOffset);
126 // Cleaning up list value attributes as it's not really needed in this test
127 // and it clutters expected output
128 const actualHtml = $generateHtmlFromNodes(editor).replace(
132 expect(actualHtml).toEqual(testCase.expectedHtml);
137 it('throws when splitting root', async () => {
139 expect(() => $splitNode($getRoot(), 0)).toThrow();