From fca8f928a380350a1a6441530a722f4578f8ae14 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 17 Dec 2024 16:50:03 +0000 Subject: [PATCH] Lexical: Aligned new empty item behaviour for nested lists - Makes enter on empty nested list item un-nest instead of just creating new list items. - Also updated existing lists tests to use newer helper setup. --- .../lexical/core/__tests__/utils/index.ts | 1 + .../lexical/list/LexicalListItemNode.ts | 11 +- .../unit/LexicalListItemNode.test.ts | 1235 +++++++++-------- .../__tests__/keyboard-handling.test.ts | 5 - 4 files changed, 635 insertions(+), 617 deletions(-) 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(); - 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`
  • `, - ); + ); - expectHtmlToBeEqual( + expectHtmlToBeEqual( listItemNode.createDOM({ namespace: '', theme: {}, @@ -74,108 +74,105 @@ describe('LexicalListItemNode tests', () => { html`
  • `, + ); + }); + }); + + describe('ListItemNode.updateDOM()', () => { + test('base', async () => { + + await editor.update(() => { + const listItemNode = new ListItemNode(); + + const domElement = listItemNode.createDOM(editor._config); + + expectHtmlToBeEqual( + domElement.outerHTML, + html` +
  • + `, + ); + const newListItemNode = new ListItemNode(); + + const result = newListItemNode.updateDOM( + listItemNode, + domElement, + editor._config, + ); + + expect(result).toBe(false); + + expectHtmlToBeEqual( + domElement.outerHTML, + html` +
  • + `, ); }); }); - 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`
  • `, - ); - const newListItemNode = new ListItemNode(); - - const result = newListItemNode.updateDOM( - listItemNode, - domElement, - editorConfig, - ); - - expect(result).toBe(false); - - expectHtmlToBeEqual( - domElement.outerHTML, - html` -
  • - `, - ); - }); - }); - - 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` -
  • - `, - ); - 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`
  • `, - ); - }); + ); }); }); + }); - 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`
    {
    `, - ); + ); + }); + + 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`
    {
    `, - ); + ); + }); + + 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`
    {
    `, - ); + ); - 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`
    {
    `, - ); - }); - - 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` -
    -
      -
    • - one -
    • -
    • - two -
    • -
    -


    -
    - `, - ); - }); - - 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` -
    -
      -
    • - one -
    • -
    -


    -
      -
    • - three -
    • -
    -
    - `, - ); - }); - - 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` -
    -
      -
    • - one -
    • -
    -
    - `, - ); - - await editor.update(() => { - const paragraphNode = $createParagraphNode(); - listItemNode1.replace(paragraphNode); - }); - - expectHtmlToBeEqual( - testEnv.outerHTML, - html` -
    -


    -
    - `, - ); - }); + ); }); - 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` +
    +
      +
    • + one +
    • +
    • + two +
    • +
    +


    +
    + `, + ); + }); - 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` +
    +
      +
    • + one +
    • +
    +


    +
      +
    • + three +
    • +
    +
    + `, + ); + }); - 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` +
    +
      +
    • + one +
    • +
    +
    + `, + ); + + await editor.update(() => { + const paragraphNode = $createParagraphNode(); + listItemNode1.replace(paragraphNode); + }); + + expectHtmlToBeEqual( + context.editorDOM.outerHTML, + html` +
    +


    +
    + `, + ); + }); + }); + + 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`
    {
    `, - ); + ); - await editor.update(() => x.remove()); + await editor.update(() => x.remove()); - expectHtmlToBeEqual( - testEnv.outerHTML, + expectHtmlToBeEqual( + context.editorDOM.outerHTML, html`
    {
    `, - ); + ); + }); + + // - 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` `, - ); + ); - await editor.update(() => x.remove()); + await editor.update(() => x.remove()); - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` `, - ); + ); + }); + + // - 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` `, - ); + ); - await editor.update(() => x.remove()); + await editor.update(() => x.remove()); - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` `, - ); + ); + }); + + // - 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` `, - ); + ); - await editor.update(() => x.remove()); + await editor.update(() => x.remove()); - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` `, - ); + ); + }); + + // - 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` `, - ); + ); - await editor.update(() => x.remove()); + await editor.update(() => x.remove()); - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` `, - ); + ); + }); + + // - 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` `, - ); + ); - await editor.update(() => x.remove()); + await editor.update(() => x.remove()); - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` `, - ); + ); + }); + + // - 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` `, - ); + ); - await editor.update(() => x.remove()); + await editor.update(() => x.remove()); - expectHtmlToBeEqual( - testEnv.innerHTML, + expectHtmlToBeEqual( + context.editorDOM.innerHTML, html` `, - ); - }); + ); }); + }); - 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`
    {
    `, - ); + ); + }); + + 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`
    {
    `, - ); + ); + }); + + 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`
    {
    `, - ); + ); + }); + + 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`
    {
    `, - ); + ); + }); + + 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`
    {
    `, - ); + ); - await editor.update(() => { - listItemNode1.insertNewAfter($createRangeSelection()); - }); + await editor.update(() => { + listItemNode1.insertNewAfter($createRangeSelection()); + }); - expectHtmlToBeEqual( - testEnv.outerHTML, + expectHtmlToBeEqual( + context.editorDOM.outerHTML, html`
    {
    `, - ); - }); + ); }); + }); - 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 = ``; - 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``, + ); }); + }); - 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;