diff --git a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts index 7815d4f0d..d90853b7c 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts @@ -776,6 +776,7 @@ export function dispatchKeydownEventForNode(node: LexicalNode, editor: LexicalEd key, }); nodeDomEl?.dispatchEvent(event); + editor.commitUpdates(); } export function dispatchKeydownEventForSelectedNode(editor: LexicalEditor, key: string) { diff --git a/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts b/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts index 33b021298..239c49a8c 100644 --- a/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts +++ b/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts @@ -271,11 +271,18 @@ export class ListItemNode extends ElementNode { insertNewAfter( _: RangeSelection, restoreSelection = true, - ): ListItemNode | ParagraphNode { + ): ListItemNode | ParagraphNode | null { if (this.getTextContent().trim() === '' && this.isLastChild()) { const list = this.getParentOrThrow<ListNode>(); - if (!$isListItemNode(list.getParent())) { + const parentListItem = list.getParent(); + if ($isListItemNode(parentListItem)) { + // Un-nest list item if empty nested item + parentListItem.insertAfter(this); + this.selectStart(); + return null; + } else { + // Insert empty paragraph after list if adding after last empty child const paragraph = $createParagraphNode(); list.insertAfter(paragraph, restoreSelection); this.remove(); diff --git a/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts b/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts index 567714bcd..10ff0fc66 100644 --- a/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts +++ b/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts @@ -9,13 +9,13 @@ import { $createParagraphNode, $createRangeSelection, - $getRoot, + $getRoot, LexicalEditor, TextNode, } from 'lexical'; import { + createTestContext, destroyFromContext, expectHtmlToBeEqual, html, - initializeUnitTest, } from 'lexical/__tests__/utils'; import { @@ -24,49 +24,49 @@ import { ListItemNode, ListNode, } from '../..'; - -const editorConfig = Object.freeze({ - namespace: '', - theme: { - list: { - listitem: 'my-listItem-item-class', - nested: { - listitem: 'my-nested-list-listItem-class', - }, - }, - }, -}); +import {EditorUiContext} from "../../../../ui/framework/core"; +import {$htmlToBlockNodes} from "../../../../utils/nodes"; describe('LexicalListItemNode tests', () => { - initializeUnitTest((testEnv) => { - test('ListItemNode.constructor', async () => { - const {editor} = testEnv; - await editor.update(() => { - const listItemNode = new ListItemNode(); + let context!: EditorUiContext; + let editor!: LexicalEditor; - expect(listItemNode.getType()).toBe('listitem'); + beforeEach(() => { + context = createTestContext(); + editor = context.editor; + }); - expect(listItemNode.getTextContent()).toBe(''); - }); + afterEach(() => { + destroyFromContext(context); + }); - expect(() => new ListItemNode()).toThrow(); + test('ListItemNode.constructor', async () => { + + await editor.update(() => { + const listItemNode = new ListItemNode(); + + expect(listItemNode.getType()).toBe('listitem'); + + expect(listItemNode.getTextContent()).toBe(''); }); - test('ListItemNode.createDOM()', async () => { - const {editor} = testEnv; + expect(() => new ListItemNode()).toThrow(); + }); - await editor.update(() => { - const listItemNode = new ListItemNode(); + test('ListItemNode.createDOM()', async () => { - expectHtmlToBeEqual( - listItemNode.createDOM(editorConfig).outerHTML, + await editor.update(() => { + const listItemNode = new ListItemNode(); + + expectHtmlToBeEqual( + listItemNode.createDOM(editor._config).outerHTML, html` <li value="1"></li> `, - ); + ); - expectHtmlToBeEqual( + expectHtmlToBeEqual( listItemNode.createDOM({ namespace: '', theme: {}, @@ -74,108 +74,105 @@ describe('LexicalListItemNode tests', () => { html` <li value="1"></li> `, + ); + }); + }); + + describe('ListItemNode.updateDOM()', () => { + test('base', async () => { + + await editor.update(() => { + const listItemNode = new ListItemNode(); + + const domElement = listItemNode.createDOM(editor._config); + + expectHtmlToBeEqual( + domElement.outerHTML, + html` + <li value="1"></li> + `, + ); + const newListItemNode = new ListItemNode(); + + const result = newListItemNode.updateDOM( + listItemNode, + domElement, + editor._config, + ); + + expect(result).toBe(false); + + expectHtmlToBeEqual( + domElement.outerHTML, + html` + <li value="1"></li> + `, ); }); }); - describe('ListItemNode.updateDOM()', () => { - test('base', async () => { - const {editor} = testEnv; + test('nested list', async () => { - await editor.update(() => { - const listItemNode = new ListItemNode(); + await editor.update(() => { + const parentListNode = new ListNode('bullet', 1); + const parentlistItemNode = new ListItemNode(); - const domElement = listItemNode.createDOM(editorConfig); + parentListNode.append(parentlistItemNode); + const domElement = parentlistItemNode.createDOM(editor._config); - expectHtmlToBeEqual( + expectHtmlToBeEqual( domElement.outerHTML, html` <li value="1"></li> `, - ); - const newListItemNode = new ListItemNode(); - - const result = newListItemNode.updateDOM( - listItemNode, - domElement, - editorConfig, - ); - - expect(result).toBe(false); - - expectHtmlToBeEqual( - domElement.outerHTML, - html` - <li value="1"></li> - `, - ); - }); - }); - - test('nested list', async () => { - const {editor} = testEnv; - - await editor.update(() => { - const parentListNode = new ListNode('bullet', 1); - const parentlistItemNode = new ListItemNode(); - - parentListNode.append(parentlistItemNode); - const domElement = parentlistItemNode.createDOM(editorConfig); - - expectHtmlToBeEqual( - domElement.outerHTML, - html` - <li value="1"></li> - `, - ); - const nestedListNode = new ListNode('bullet', 1); - nestedListNode.append(new ListItemNode()); - parentlistItemNode.append(nestedListNode); - const result = parentlistItemNode.updateDOM( + ); + const nestedListNode = new ListNode('bullet', 1); + nestedListNode.append(new ListItemNode()); + parentlistItemNode.append(nestedListNode); + const result = parentlistItemNode.updateDOM( parentlistItemNode, domElement, - editorConfig, - ); + editor._config, + ); - expect(result).toBe(false); + expect(result).toBe(false); - expectHtmlToBeEqual( + expectHtmlToBeEqual( domElement.outerHTML, html` <li value="1" style="list-style: none;"></li> `, - ); - }); + ); }); }); + }); - describe('ListItemNode.replace()', () => { - let listNode: ListNode; - let listItemNode1: ListItemNode; - let listItemNode2: ListItemNode; - let listItemNode3: ListItemNode; + describe('ListItemNode.replace()', () => { + let listNode: ListNode; + let listItemNode1: ListItemNode; + let listItemNode2: ListItemNode; + let listItemNode3: ListItemNode; - beforeEach(async () => { - const {editor} = testEnv; + beforeEach(async () => { - await editor.update(() => { - const root = $getRoot(); - listNode = new ListNode('bullet', 1); - listItemNode1 = new ListItemNode(); + await editor.update(() => { + const root = $getRoot(); + listNode = new ListNode('bullet', 1); + listItemNode1 = new ListItemNode(); - listItemNode1.append(new TextNode('one')); - listItemNode2 = new ListItemNode(); + listItemNode1.append(new TextNode('one')); + listItemNode2 = new ListItemNode(); - listItemNode2.append(new TextNode('two')); - listItemNode3 = new ListItemNode(); + listItemNode2.append(new TextNode('two')); + listItemNode3 = new ListItemNode(); - listItemNode3.append(new TextNode('three')); - root.append(listNode); - listNode.append(listItemNode1, listItemNode2, listItemNode3); - }); + listItemNode3.append(new TextNode('three')); + root.append(listNode); + listNode.append(listItemNode1, listItemNode2, listItemNode3); + }); - expectHtmlToBeEqual( - testEnv.outerHTML, + expectHtmlToBeEqual( + context.editorDOM.outerHTML, html` <div contenteditable="true" @@ -194,21 +191,20 @@ describe('LexicalListItemNode tests', () => { </ul> </div> `, - ); + ); + }); + + test('another list item node', async () => { + + await editor.update(() => { + const newListItemNode = new ListItemNode(); + + newListItemNode.append(new TextNode('bar')); + listItemNode1.replace(newListItemNode); }); - test('another list item node', async () => { - const {editor} = testEnv; - - await editor.update(() => { - const newListItemNode = new ListItemNode(); - - newListItemNode.append(new TextNode('bar')); - listItemNode1.replace(newListItemNode); - }); - - expectHtmlToBeEqual( - testEnv.outerHTML, + expectHtmlToBeEqual( + context.editorDOM.outerHTML, html` <div contenteditable="true" @@ -227,18 +223,17 @@ describe('LexicalListItemNode tests', () => { </ul> </div> `, - ); + ); + }); + + test('first list item with a non list item node', async () => { + + await editor.update(() => { + return; }); - test('first list item with a non list item node', async () => { - const {editor} = testEnv; - - await editor.update(() => { - return; - }); - - expectHtmlToBeEqual( - testEnv.outerHTML, + expectHtmlToBeEqual( + context.editorDOM.outerHTML, html` <div contenteditable="true" @@ -257,15 +252,15 @@ describe('LexicalListItemNode tests', () => { </ul> </div> `, - ); + ); - await editor.update(() => { - const paragraphNode = $createParagraphNode(); - listItemNode1.replace(paragraphNode); - }); + await editor.update(() => { + const paragraphNode = $createParagraphNode(); + listItemNode1.replace(paragraphNode); + }); - expectHtmlToBeEqual( - testEnv.outerHTML, + expectHtmlToBeEqual( + context.editorDOM.outerHTML, html` <div contenteditable="true" @@ -282,139 +277,135 @@ describe('LexicalListItemNode tests', () => { </ul> </div> `, - ); - }); - - test('last list item with a non list item node', async () => { - const {editor} = testEnv; - - await editor.update(() => { - const paragraphNode = $createParagraphNode(); - listItemNode3.replace(paragraphNode); - }); - - expectHtmlToBeEqual( - testEnv.outerHTML, - html` - <div - contenteditable="true" - style="user-select: text; white-space: pre-wrap; word-break: break-word;" - data-lexical-editor="true"> - <ul> - <li value="1"> - <span data-lexical-text="true">one</span> - </li> - <li value="2"> - <span data-lexical-text="true">two</span> - </li> - </ul> - <p><br></p> - </div> - `, - ); - }); - - test('middle list item with a non list item node', async () => { - const {editor} = testEnv; - - await editor.update(() => { - const paragraphNode = $createParagraphNode(); - listItemNode2.replace(paragraphNode); - }); - - expectHtmlToBeEqual( - testEnv.outerHTML, - html` - <div - contenteditable="true" - style="user-select: text; white-space: pre-wrap; word-break: break-word;" - data-lexical-editor="true"> - <ul> - <li value="1"> - <span data-lexical-text="true">one</span> - </li> - </ul> - <p><br></p> - <ul> - <li value="1"> - <span data-lexical-text="true">three</span> - </li> - </ul> - </div> - `, - ); - }); - - test('the only list item with a non list item node', async () => { - const {editor} = testEnv; - - await editor.update(() => { - listItemNode2.remove(); - listItemNode3.remove(); - }); - - expectHtmlToBeEqual( - testEnv.outerHTML, - html` - <div - contenteditable="true" - style="user-select: text; white-space: pre-wrap; word-break: break-word;" - data-lexical-editor="true"> - <ul> - <li value="1"> - <span data-lexical-text="true">one</span> - </li> - </ul> - </div> - `, - ); - - await editor.update(() => { - const paragraphNode = $createParagraphNode(); - listItemNode1.replace(paragraphNode); - }); - - expectHtmlToBeEqual( - testEnv.outerHTML, - html` - <div - contenteditable="true" - style="user-select: text; white-space: pre-wrap; word-break: break-word;" - data-lexical-editor="true"> - <p><br></p> - </div> - `, - ); - }); + ); }); - describe('ListItemNode.remove()', () => { - // - A - // - x - // - B - test('siblings are not nested', async () => { - const {editor} = testEnv; - let x: ListItemNode; + test('last list item with a non list item node', async () => { - await editor.update(() => { - const root = $getRoot(); - const parent = new ListNode('bullet', 1); + await editor.update(() => { + const paragraphNode = $createParagraphNode(); + listItemNode3.replace(paragraphNode); + }); - const A_listItem = new ListItemNode(); - A_listItem.append(new TextNode('A')); + expectHtmlToBeEqual( + context.editorDOM.outerHTML, + html` + <div + contenteditable="true" + style="user-select: text; white-space: pre-wrap; word-break: break-word;" + data-lexical-editor="true"> + <ul> + <li value="1"> + <span data-lexical-text="true">one</span> + </li> + <li value="2"> + <span data-lexical-text="true">two</span> + </li> + </ul> + <p><br></p> + </div> + `, + ); + }); - x = new ListItemNode(); - x.append(new TextNode('x')); + test('middle list item with a non list item node', async () => { - const B_listItem = new ListItemNode(); - B_listItem.append(new TextNode('B')); + await editor.update(() => { + const paragraphNode = $createParagraphNode(); + listItemNode2.replace(paragraphNode); + }); - parent.append(A_listItem, x, B_listItem); - root.append(parent); - }); + expectHtmlToBeEqual( + context.editorDOM.outerHTML, + html` + <div + contenteditable="true" + style="user-select: text; white-space: pre-wrap; word-break: break-word;" + data-lexical-editor="true"> + <ul> + <li value="1"> + <span data-lexical-text="true">one</span> + </li> + </ul> + <p><br></p> + <ul> + <li value="1"> + <span data-lexical-text="true">three</span> + </li> + </ul> + </div> + `, + ); + }); - expectHtmlToBeEqual( - testEnv.outerHTML, + test('the only list item with a non list item node', async () => { + + await editor.update(() => { + listItemNode2.remove(); + listItemNode3.remove(); + }); + + expectHtmlToBeEqual( + context.editorDOM.outerHTML, + html` + <div + contenteditable="true" + style="user-select: text; white-space: pre-wrap; word-break: break-word;" + data-lexical-editor="true"> + <ul> + <li value="1"> + <span data-lexical-text="true">one</span> + </li> + </ul> + </div> + `, + ); + + await editor.update(() => { + const paragraphNode = $createParagraphNode(); + listItemNode1.replace(paragraphNode); + }); + + expectHtmlToBeEqual( + context.editorDOM.outerHTML, + html` + <div + contenteditable="true" + style="user-select: text; white-space: pre-wrap; word-break: break-word;" + data-lexical-editor="true"> + <p><br></p> + </div> + `, + ); + }); + }); + + describe('ListItemNode.remove()', () => { + // - A + // - x + // - B + test('siblings are not nested', async () => { + let x: ListItemNode; + + await editor.update(() => { + const root = $getRoot(); + const parent = new ListNode('bullet', 1); + + const A_listItem = new ListItemNode(); + A_listItem.append(new TextNode('A')); + + x = new ListItemNode(); + x.append(new TextNode('x')); + + const B_listItem = new ListItemNode(); + B_listItem.append(new TextNode('B')); + + parent.append(A_listItem, x, B_listItem); + root.append(parent); + }); + + expectHtmlToBeEqual( + context.editorDOM.outerHTML, html` <div contenteditable="true" @@ -433,12 +424,12 @@ describe('LexicalListItemNode tests', () => { </ul> </div> `, - ); + ); - await editor.update(() => x.remove()); + await editor.update(() => x.remove()); - expectHtmlToBeEqual( - testEnv.outerHTML, + expectHtmlToBeEqual( + context.editorDOM.outerHTML, html` <div contenteditable="true" @@ -454,39 +445,38 @@ describe('LexicalListItemNode tests', () => { </ul> </div> `, - ); + ); + }); + + // - A + // - x + // - B + test('the previous sibling is nested', async () => { + let x: ListItemNode; + + await editor.update(() => { + const root = $getRoot(); + const parent = new ListNode('bullet', 1); + + const A_listItem = new ListItemNode(); + const A_nestedList = new ListNode('bullet', 1); + const A_nestedListItem = new ListItemNode(); + A_listItem.append(A_nestedList); + A_nestedList.append(A_nestedListItem); + A_nestedListItem.append(new TextNode('A')); + + x = new ListItemNode(); + x.append(new TextNode('x')); + + const B_listItem = new ListItemNode(); + B_listItem.append(new TextNode('B')); + + parent.append(A_listItem, x, B_listItem); + root.append(parent); }); - // - A - // - x - // - B - test('the previous sibling is nested', async () => { - const {editor} = testEnv; - let x: ListItemNode; - - await editor.update(() => { - const root = $getRoot(); - const parent = new ListNode('bullet', 1); - - const A_listItem = new ListItemNode(); - const A_nestedList = new ListNode('bullet', 1); - const A_nestedListItem = new ListItemNode(); - A_listItem.append(A_nestedList); - A_nestedList.append(A_nestedListItem); - A_nestedListItem.append(new TextNode('A')); - - x = new ListItemNode(); - x.append(new TextNode('x')); - - const B_listItem = new ListItemNode(); - B_listItem.append(new TextNode('B')); - - parent.append(A_listItem, x, B_listItem); - root.append(parent); - }); - - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` <ul> <li value="1" style="list-style: none;"> @@ -504,12 +494,12 @@ describe('LexicalListItemNode tests', () => { </li> </ul> `, - ); + ); - await editor.update(() => x.remove()); + await editor.update(() => x.remove()); - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` <ul> <li value="1" style="list-style: none;"> @@ -524,39 +514,38 @@ describe('LexicalListItemNode tests', () => { </li> </ul> `, - ); + ); + }); + + // - A + // - x + // - B + test('the next sibling is nested', async () => { + let x: ListItemNode; + + await editor.update(() => { + const root = $getRoot(); + const parent = new ListNode('bullet', 1); + + const A_listItem = new ListItemNode(); + A_listItem.append(new TextNode('A')); + + x = new ListItemNode(); + x.append(new TextNode('x')); + + const B_listItem = new ListItemNode(); + const B_nestedList = new ListNode('bullet', 1); + const B_nestedListItem = new ListItemNode(); + B_listItem.append(B_nestedList); + B_nestedList.append(B_nestedListItem); + B_nestedListItem.append(new TextNode('B')); + + parent.append(A_listItem, x, B_listItem); + root.append(parent); }); - // - A - // - x - // - B - test('the next sibling is nested', async () => { - const {editor} = testEnv; - let x: ListItemNode; - - await editor.update(() => { - const root = $getRoot(); - const parent = new ListNode('bullet', 1); - - const A_listItem = new ListItemNode(); - A_listItem.append(new TextNode('A')); - - x = new ListItemNode(); - x.append(new TextNode('x')); - - const B_listItem = new ListItemNode(); - const B_nestedList = new ListNode('bullet', 1); - const B_nestedListItem = new ListItemNode(); - B_listItem.append(B_nestedList); - B_nestedList.append(B_nestedListItem); - B_nestedListItem.append(new TextNode('B')); - - parent.append(A_listItem, x, B_listItem); - root.append(parent); - }); - - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` <ul> <li value="1"> @@ -574,12 +563,12 @@ describe('LexicalListItemNode tests', () => { </li> </ul> `, - ); + ); - await editor.update(() => x.remove()); + await editor.update(() => x.remove()); - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` <ul> <li value="1"> @@ -594,43 +583,42 @@ describe('LexicalListItemNode tests', () => { </li> </ul> `, - ); + ); + }); + + // - A + // - x + // - B + test('both siblings are nested', async () => { + let x: ListItemNode; + + await editor.update(() => { + const root = $getRoot(); + const parent = new ListNode('bullet', 1); + + const A_listItem = new ListItemNode(); + const A_nestedList = new ListNode('bullet', 1); + const A_nestedListItem = new ListItemNode(); + A_listItem.append(A_nestedList); + A_nestedList.append(A_nestedListItem); + A_nestedListItem.append(new TextNode('A')); + + x = new ListItemNode(); + x.append(new TextNode('x')); + + const B_listItem = new ListItemNode(); + const B_nestedList = new ListNode('bullet', 1); + const B_nestedListItem = new ListItemNode(); + B_listItem.append(B_nestedList); + B_nestedList.append(B_nestedListItem); + B_nestedListItem.append(new TextNode('B')); + + parent.append(A_listItem, x, B_listItem); + root.append(parent); }); - // - A - // - x - // - B - test('both siblings are nested', async () => { - const {editor} = testEnv; - let x: ListItemNode; - - await editor.update(() => { - const root = $getRoot(); - const parent = new ListNode('bullet', 1); - - const A_listItem = new ListItemNode(); - const A_nestedList = new ListNode('bullet', 1); - const A_nestedListItem = new ListItemNode(); - A_listItem.append(A_nestedList); - A_nestedList.append(A_nestedListItem); - A_nestedListItem.append(new TextNode('A')); - - x = new ListItemNode(); - x.append(new TextNode('x')); - - const B_listItem = new ListItemNode(); - const B_nestedList = new ListNode('bullet', 1); - const B_nestedListItem = new ListItemNode(); - B_listItem.append(B_nestedList); - B_nestedList.append(B_nestedListItem); - B_nestedListItem.append(new TextNode('B')); - - parent.append(A_listItem, x, B_listItem); - root.append(parent); - }); - - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` <ul> <li value="1" style="list-style: none;"> @@ -652,12 +640,12 @@ describe('LexicalListItemNode tests', () => { </li> </ul> `, - ); + ); - await editor.update(() => x.remove()); + await editor.update(() => x.remove()); - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` <ul> <li value="1" style="list-style: none;"> @@ -672,51 +660,50 @@ describe('LexicalListItemNode tests', () => { </li> </ul> `, - ); + ); + }); + + // - A1 + // - A2 + // - x + // - B + test('the previous sibling is nested deeper than the next sibling', async () => { + let x: ListItemNode; + + await editor.update(() => { + const root = $getRoot(); + const parent = new ListNode('bullet', 1); + + const A_listItem = new ListItemNode(); + const A_nestedList = new ListNode('bullet', 1); + const A_nestedListItem1 = new ListItemNode(); + const A_nestedListItem2 = new ListItemNode(); + const A_deeplyNestedList = new ListNode('bullet', 1); + const A_deeplyNestedListItem = new ListItemNode(); + A_listItem.append(A_nestedList); + A_nestedList.append(A_nestedListItem1); + A_nestedList.append(A_nestedListItem2); + A_nestedListItem1.append(new TextNode('A1')); + A_nestedListItem2.append(A_deeplyNestedList); + A_deeplyNestedList.append(A_deeplyNestedListItem); + A_deeplyNestedListItem.append(new TextNode('A2')); + + x = new ListItemNode(); + x.append(new TextNode('x')); + + const B_listItem = new ListItemNode(); + const B_nestedList = new ListNode('bullet', 1); + const B_nestedlistItem = new ListItemNode(); + B_listItem.append(B_nestedList); + B_nestedList.append(B_nestedlistItem); + B_nestedlistItem.append(new TextNode('B')); + + parent.append(A_listItem, x, B_listItem); + root.append(parent); }); - // - A1 - // - A2 - // - x - // - B - test('the previous sibling is nested deeper than the next sibling', async () => { - const {editor} = testEnv; - let x: ListItemNode; - - await editor.update(() => { - const root = $getRoot(); - const parent = new ListNode('bullet', 1); - - const A_listItem = new ListItemNode(); - const A_nestedList = new ListNode('bullet', 1); - const A_nestedListItem1 = new ListItemNode(); - const A_nestedListItem2 = new ListItemNode(); - const A_deeplyNestedList = new ListNode('bullet', 1); - const A_deeplyNestedListItem = new ListItemNode(); - A_listItem.append(A_nestedList); - A_nestedList.append(A_nestedListItem1); - A_nestedList.append(A_nestedListItem2); - A_nestedListItem1.append(new TextNode('A1')); - A_nestedListItem2.append(A_deeplyNestedList); - A_deeplyNestedList.append(A_deeplyNestedListItem); - A_deeplyNestedListItem.append(new TextNode('A2')); - - x = new ListItemNode(); - x.append(new TextNode('x')); - - const B_listItem = new ListItemNode(); - const B_nestedList = new ListNode('bullet', 1); - const B_nestedlistItem = new ListItemNode(); - B_listItem.append(B_nestedList); - B_nestedList.append(B_nestedlistItem); - B_nestedlistItem.append(new TextNode('B')); - - parent.append(A_listItem, x, B_listItem); - root.append(parent); - }); - - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` <ul> <li value="1" style="list-style: none;"> @@ -745,12 +732,12 @@ describe('LexicalListItemNode tests', () => { </li> </ul> `, - ); + ); - await editor.update(() => x.remove()); + await editor.update(() => x.remove()); - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` <ul> <li value="1" style="list-style: none;"> @@ -772,51 +759,50 @@ describe('LexicalListItemNode tests', () => { </li> </ul> `, - ); + ); + }); + + // - A + // - x + // - B1 + // - B2 + test('the next sibling is nested deeper than the previous sibling', async () => { + let x: ListItemNode; + + await editor.update(() => { + const root = $getRoot(); + const parent = new ListNode('bullet', 1); + + const A_listItem = new ListItemNode(); + const A_nestedList = new ListNode('bullet', 1); + const A_nestedListItem = new ListItemNode(); + A_listItem.append(A_nestedList); + A_nestedList.append(A_nestedListItem); + A_nestedListItem.append(new TextNode('A')); + + x = new ListItemNode(); + x.append(new TextNode('x')); + + const B_listItem = new ListItemNode(); + const B_nestedList = new ListNode('bullet', 1); + const B_nestedListItem1 = new ListItemNode(); + const B_nestedListItem2 = new ListItemNode(); + const B_deeplyNestedList = new ListNode('bullet', 1); + const B_deeplyNestedListItem = new ListItemNode(); + B_listItem.append(B_nestedList); + B_nestedList.append(B_nestedListItem1); + B_nestedList.append(B_nestedListItem2); + B_nestedListItem1.append(B_deeplyNestedList); + B_nestedListItem2.append(new TextNode('B2')); + B_deeplyNestedList.append(B_deeplyNestedListItem); + B_deeplyNestedListItem.append(new TextNode('B1')); + + parent.append(A_listItem, x, B_listItem); + root.append(parent); }); - // - A - // - x - // - B1 - // - B2 - test('the next sibling is nested deeper than the previous sibling', async () => { - const {editor} = testEnv; - let x: ListItemNode; - - await editor.update(() => { - const root = $getRoot(); - const parent = new ListNode('bullet', 1); - - const A_listItem = new ListItemNode(); - const A_nestedList = new ListNode('bullet', 1); - const A_nestedListItem = new ListItemNode(); - A_listItem.append(A_nestedList); - A_nestedList.append(A_nestedListItem); - A_nestedListItem.append(new TextNode('A')); - - x = new ListItemNode(); - x.append(new TextNode('x')); - - const B_listItem = new ListItemNode(); - const B_nestedList = new ListNode('bullet', 1); - const B_nestedListItem1 = new ListItemNode(); - const B_nestedListItem2 = new ListItemNode(); - const B_deeplyNestedList = new ListNode('bullet', 1); - const B_deeplyNestedListItem = new ListItemNode(); - B_listItem.append(B_nestedList); - B_nestedList.append(B_nestedListItem1); - B_nestedList.append(B_nestedListItem2); - B_nestedListItem1.append(B_deeplyNestedList); - B_nestedListItem2.append(new TextNode('B2')); - B_deeplyNestedList.append(B_deeplyNestedListItem); - B_deeplyNestedListItem.append(new TextNode('B1')); - - parent.append(A_listItem, x, B_listItem); - root.append(parent); - }); - - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` <ul> <li value="1" style="list-style: none;"> @@ -845,12 +831,12 @@ describe('LexicalListItemNode tests', () => { </li> </ul> `, - ); + ); - await editor.update(() => x.remove()); + await editor.update(() => x.remove()); - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` <ul> <li value="1" style="list-style: none;"> @@ -872,59 +858,58 @@ describe('LexicalListItemNode tests', () => { </li> </ul> `, - ); + ); + }); + + // - A1 + // - A2 + // - x + // - B1 + // - B2 + test('both siblings are deeply nested', async () => { + let x: ListItemNode; + + await editor.update(() => { + const root = $getRoot(); + const parent = new ListNode('bullet', 1); + + const A_listItem = new ListItemNode(); + const A_nestedList = new ListNode('bullet', 1); + const A_nestedListItem1 = new ListItemNode(); + const A_nestedListItem2 = new ListItemNode(); + const A_deeplyNestedList = new ListNode('bullet', 1); + const A_deeplyNestedListItem = new ListItemNode(); + A_listItem.append(A_nestedList); + A_nestedList.append(A_nestedListItem1); + A_nestedList.append(A_nestedListItem2); + A_nestedListItem1.append(new TextNode('A1')); + A_nestedListItem2.append(A_deeplyNestedList); + A_deeplyNestedList.append(A_deeplyNestedListItem); + A_deeplyNestedListItem.append(new TextNode('A2')); + + x = new ListItemNode(); + x.append(new TextNode('x')); + + const B_listItem = new ListItemNode(); + const B_nestedList = new ListNode('bullet', 1); + const B_nestedListItem1 = new ListItemNode(); + const B_nestedListItem2 = new ListItemNode(); + const B_deeplyNestedList = new ListNode('bullet', 1); + const B_deeplyNestedListItem = new ListItemNode(); + B_listItem.append(B_nestedList); + B_nestedList.append(B_nestedListItem1); + B_nestedList.append(B_nestedListItem2); + B_nestedListItem1.append(B_deeplyNestedList); + B_nestedListItem2.append(new TextNode('B2')); + B_deeplyNestedList.append(B_deeplyNestedListItem); + B_deeplyNestedListItem.append(new TextNode('B1')); + + parent.append(A_listItem, x, B_listItem); + root.append(parent); }); - // - A1 - // - A2 - // - x - // - B1 - // - B2 - test('both siblings are deeply nested', async () => { - const {editor} = testEnv; - let x: ListItemNode; - - await editor.update(() => { - const root = $getRoot(); - const parent = new ListNode('bullet', 1); - - const A_listItem = new ListItemNode(); - const A_nestedList = new ListNode('bullet', 1); - const A_nestedListItem1 = new ListItemNode(); - const A_nestedListItem2 = new ListItemNode(); - const A_deeplyNestedList = new ListNode('bullet', 1); - const A_deeplyNestedListItem = new ListItemNode(); - A_listItem.append(A_nestedList); - A_nestedList.append(A_nestedListItem1); - A_nestedList.append(A_nestedListItem2); - A_nestedListItem1.append(new TextNode('A1')); - A_nestedListItem2.append(A_deeplyNestedList); - A_deeplyNestedList.append(A_deeplyNestedListItem); - A_deeplyNestedListItem.append(new TextNode('A2')); - - x = new ListItemNode(); - x.append(new TextNode('x')); - - const B_listItem = new ListItemNode(); - const B_nestedList = new ListNode('bullet', 1); - const B_nestedListItem1 = new ListItemNode(); - const B_nestedListItem2 = new ListItemNode(); - const B_deeplyNestedList = new ListNode('bullet', 1); - const B_deeplyNestedListItem = new ListItemNode(); - B_listItem.append(B_nestedList); - B_nestedList.append(B_nestedListItem1); - B_nestedList.append(B_nestedListItem2); - B_nestedListItem1.append(B_deeplyNestedList); - B_nestedListItem2.append(new TextNode('B2')); - B_deeplyNestedList.append(B_deeplyNestedListItem); - B_deeplyNestedListItem.append(new TextNode('B1')); - - parent.append(A_listItem, x, B_listItem); - root.append(parent); - }); - - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` <ul> <li value="1" style="list-style: none;"> @@ -960,12 +945,12 @@ describe('LexicalListItemNode tests', () => { </li> </ul> `, - ); + ); - await editor.update(() => x.remove()); + await editor.update(() => x.remove()); - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` <ul> <li value="1" style="list-style: none;"> @@ -990,37 +975,36 @@ describe('LexicalListItemNode tests', () => { </li> </ul> `, - ); - }); + ); }); + }); - describe('ListItemNode.insertNewAfter(): non-empty list items', () => { - let listNode: ListNode; - let listItemNode1: ListItemNode; - let listItemNode2: ListItemNode; - let listItemNode3: ListItemNode; + describe('ListItemNode.insertNewAfter(): non-empty list items', () => { + let listNode: ListNode; + let listItemNode1: ListItemNode; + let listItemNode2: ListItemNode; + let listItemNode3: ListItemNode; - beforeEach(async () => { - const {editor} = testEnv; + beforeEach(async () => { - await editor.update(() => { - const root = $getRoot(); - listNode = new ListNode('bullet', 1); - listItemNode1 = new ListItemNode(); + await editor.update(() => { + const root = $getRoot(); + listNode = new ListNode('bullet', 1); + listItemNode1 = new ListItemNode(); - listItemNode2 = new ListItemNode(); + listItemNode2 = new ListItemNode(); - listItemNode3 = new ListItemNode(); + listItemNode3 = new ListItemNode(); - root.append(listNode); - listNode.append(listItemNode1, listItemNode2, listItemNode3); - listItemNode1.append(new TextNode('one')); - listItemNode2.append(new TextNode('two')); - listItemNode3.append(new TextNode('three')); - }); + root.append(listNode); + listNode.append(listItemNode1, listItemNode2, listItemNode3); + listItemNode1.append(new TextNode('one')); + listItemNode2.append(new TextNode('two')); + listItemNode3.append(new TextNode('three')); + }); - expectHtmlToBeEqual( - testEnv.outerHTML, + expectHtmlToBeEqual( + context.editorDOM.outerHTML, html` <div contenteditable="true" @@ -1039,18 +1023,17 @@ describe('LexicalListItemNode tests', () => { </ul> </div> `, - ); + ); + }); + + test('first list item', async () => { + + await editor.update(() => { + listItemNode1.insertNewAfter($createRangeSelection()); }); - test('first list item', async () => { - const {editor} = testEnv; - - await editor.update(() => { - listItemNode1.insertNewAfter($createRangeSelection()); - }); - - expectHtmlToBeEqual( - testEnv.outerHTML, + expectHtmlToBeEqual( + context.editorDOM.outerHTML, html` <div contenteditable="true" @@ -1070,18 +1053,17 @@ describe('LexicalListItemNode tests', () => { </ul> </div> `, - ); + ); + }); + + test('last list item', async () => { + + await editor.update(() => { + listItemNode3.insertNewAfter($createRangeSelection()); }); - test('last list item', async () => { - const {editor} = testEnv; - - await editor.update(() => { - listItemNode3.insertNewAfter($createRangeSelection()); - }); - - expectHtmlToBeEqual( - testEnv.outerHTML, + expectHtmlToBeEqual( + context.editorDOM.outerHTML, html` <div contenteditable="true" @@ -1101,18 +1083,17 @@ describe('LexicalListItemNode tests', () => { </ul> </div> `, - ); + ); + }); + + test('middle list item', async () => { + + await editor.update(() => { + listItemNode3.insertNewAfter($createRangeSelection()); }); - test('middle list item', async () => { - const {editor} = testEnv; - - await editor.update(() => { - listItemNode3.insertNewAfter($createRangeSelection()); - }); - - expectHtmlToBeEqual( - testEnv.outerHTML, + expectHtmlToBeEqual( + context.editorDOM.outerHTML, html` <div contenteditable="true" @@ -1132,19 +1113,17 @@ describe('LexicalListItemNode tests', () => { </ul> </div> `, - ); + ); + }); + + test('the only list item', async () => { + await editor.update(() => { + listItemNode2.remove(); + listItemNode3.remove(); }); - test('the only list item', async () => { - const {editor} = testEnv; - - await editor.update(() => { - listItemNode2.remove(); - listItemNode3.remove(); - }); - - expectHtmlToBeEqual( - testEnv.outerHTML, + expectHtmlToBeEqual( + context.editorDOM.outerHTML, html` <div contenteditable="true" @@ -1157,14 +1136,14 @@ describe('LexicalListItemNode tests', () => { </ul> </div> `, - ); + ); - await editor.update(() => { - listItemNode1.insertNewAfter($createRangeSelection()); - }); + await editor.update(() => { + listItemNode1.insertNewAfter($createRangeSelection()); + }); - expectHtmlToBeEqual( - testEnv.outerHTML, + expectHtmlToBeEqual( + context.editorDOM.outerHTML, html` <div contenteditable="true" @@ -1178,32 +1157,68 @@ describe('LexicalListItemNode tests', () => { </ul> </div> `, - ); - }); + ); }); + }); - test('$createListItemNode()', async () => { - const {editor} = testEnv; + describe('ListItemNode.insertNewAfter()', () => { + test('new items after empty nested items un-nests the current item instead of creating new', () => { + let nestedItem!: ListItemNode; + const input = `<ul> + <li> + Item A + <ul><li>Nested item A</li></ul> + </li> + <li>Item B</li> + </ul>`; - await editor.update(() => { - const listItemNode = new ListItemNode(); - - const createdListItemNode = $createListItemNode(); - - expect(listItemNode.__type).toEqual(createdListItemNode.__type); - expect(listItemNode.__parent).toEqual(createdListItemNode.__parent); - expect(listItemNode.__key).not.toEqual(createdListItemNode.__key); + editor.updateAndCommit(() => { + const root = $getRoot(); + root.append(...$htmlToBlockNodes(editor, input)); + const list = root.getFirstChild() as ListNode; + const itemA = list.getFirstChild() as ListItemNode; + const nestedList = itemA.getLastChild() as ListNode; + nestedItem = nestedList.getFirstChild() as ListItemNode; + nestedList.selectEnd(); }); + + editor.updateAndCommit(() => { + nestedItem.insertNewAfter($createRangeSelection()); + const newItem = nestedItem.getNextSibling() as ListItemNode; + newItem.insertNewAfter($createRangeSelection()); + }); + + expectHtmlToBeEqual( + context.editorDOM.innerHTML, + html`<ul> + <li value="1"> + <span data-lexical-text="true">Item A</span> + <ul><li value="1"><span data-lexical-text="true">Nested item A</span></li></ul> + </li> + <li value="2"><br></li> + <li value="3"><span data-lexical-text="true">Item B</span></li> + </ul>`, + ); }); + }); - test('$isListItemNode()', async () => { - const {editor} = testEnv; + test('$createListItemNode()', async () => { + await editor.update(() => { + const listItemNode = new ListItemNode(); - await editor.update(() => { - const listItemNode = new ListItemNode(); + const createdListItemNode = $createListItemNode(); - expect($isListItemNode(listItemNode)).toBe(true); - }); + expect(listItemNode.__type).toEqual(createdListItemNode.__type); + expect(listItemNode.__parent).toEqual(createdListItemNode.__parent); + expect(listItemNode.__key).not.toEqual(createdListItemNode.__key); + }); + }); + + test('$isListItemNode()', async () => { + await editor.update(() => { + const listItemNode = new ListItemNode(); + + expect($isListItemNode(listItemNode)).toBe(true); }); }); }); diff --git a/resources/js/wysiwyg/services/__tests__/keyboard-handling.test.ts b/resources/js/wysiwyg/services/__tests__/keyboard-handling.test.ts index 0ab6935fb..736c3573c 100644 --- a/resources/js/wysiwyg/services/__tests__/keyboard-handling.test.ts +++ b/resources/js/wysiwyg/services/__tests__/keyboard-handling.test.ts @@ -48,7 +48,6 @@ describe('Keyboard-handling service tests', () => { expect(lastRootChild).toBeInstanceOf(DetailsNode); dispatchKeydownEventForNode(detailsPara, editor, 'ArrowDown'); - editor.commitUpdates(); editor.getEditorState().read(() => { lastRootChild = $getRoot().getLastChild(); @@ -79,10 +78,7 @@ describe('Keyboard-handling service tests', () => { expect(lastRootChild).toBeInstanceOf(DetailsNode); dispatchKeydownEventForNode(detailsPara, editor, 'Enter'); - editor.commitUpdates(); - dispatchKeydownEventForSelectedNode(editor, 'Enter'); - editor.commitUpdates(); let detailsChildren!: LexicalNode[]; let lastDetailsText!: string; @@ -115,7 +111,6 @@ describe('Keyboard-handling service tests', () => { }); dispatchKeydownEventForNode(listItemB, editor, 'Tab'); - editor.commitUpdates(); editor.getEditorState().read(() => { const list = $getRoot().getChildren()[0] as ListNode;