From: Dan Brown Date: Fri, 28 Mar 2025 18:29:00 +0000 (+0000) Subject: Lexical: Added tests to cover recent changes X-Git-Tag: v25.02.2~1^2~1^2 X-Git-Url: http://source.bookstackapp.com/bookstack/commitdiff_plain/bb44334224d6eee53b229e1f59d63b4f1ef45e68 Lexical: Added tests to cover recent changes Also updated list tests to new test process. --- diff --git a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts index d54a64ce8..6a8e45724 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts @@ -37,6 +37,7 @@ import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; import {DetailsNode} from "@lexical/rich-text/LexicalDetailsNode"; import {EditorUiContext} from "../../../../ui/framework/core"; import {EditorUIManager} from "../../../../ui/framework/manager"; +import {ImageNode} from "@lexical/rich-text/LexicalImageNode"; type TestEnv = { readonly container: HTMLDivElement; @@ -484,6 +485,9 @@ export function createTestContext(): EditorUiContext { const editor = createTestEditor({ namespace: 'testing', theme: {}, + nodes: [ + ImageNode, + ] }); editor.setRootElement(editorDOM); diff --git a/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListNode.test.ts b/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListNode.test.ts index 8c7729dbf..b85383e7d 100644 --- a/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListNode.test.ts +++ b/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListNode.test.ts @@ -6,7 +6,7 @@ * */ import {ParagraphNode, TextNode} from 'lexical'; -import {initializeUnitTest} from 'lexical/__tests__/utils'; +import {createTestContext} from 'lexical/__tests__/utils'; import { $createListItemNode, @@ -16,6 +16,7 @@ import { ListItemNode, ListNode, } from '../..'; +import {$htmlToBlockNodes} from "../../../../utils/nodes"; const editorConfig = Object.freeze({ namespace: '', @@ -46,123 +47,122 @@ const editorConfig = Object.freeze({ }); describe('LexicalListNode tests', () => { - initializeUnitTest((testEnv) => { - test('ListNode.constructor', async () => { - const {editor} = testEnv; - - await editor.update(() => { - const listNode = $createListNode('bullet', 1); - expect(listNode.getType()).toBe('list'); - expect(listNode.getTag()).toBe('ul'); - expect(listNode.getTextContent()).toBe(''); - }); - - // @ts-expect-error - expect(() => $createListNode()).toThrow(); + test('ListNode.constructor', async () => { + const {editor} = createTestContext(); + + await editor.update(() => { + const listNode = $createListNode('bullet', 1); + expect(listNode.getType()).toBe('list'); + expect(listNode.getTag()).toBe('ul'); + expect(listNode.getTextContent()).toBe(''); }); - test('ListNode.getTag()', async () => { - const {editor} = testEnv; - - await editor.update(() => { - const ulListNode = $createListNode('bullet', 1); - expect(ulListNode.getTag()).toBe('ul'); - const olListNode = $createListNode('number', 1); - expect(olListNode.getTag()).toBe('ol'); - const checkListNode = $createListNode('check', 1); - expect(checkListNode.getTag()).toBe('ul'); - }); + // @ts-expect-error + expect(() => $createListNode()).toThrow(); + }); + + test('ListNode.getTag()', async () => { + const {editor} = createTestContext(); + + await editor.update(() => { + const ulListNode = $createListNode('bullet', 1); + expect(ulListNode.getTag()).toBe('ul'); + const olListNode = $createListNode('number', 1); + expect(olListNode.getTag()).toBe('ol'); + const checkListNode = $createListNode('check', 1); + expect(checkListNode.getTag()).toBe('ul'); }); + }); - test('ListNode.createDOM()', async () => { - const {editor} = testEnv; + test('ListNode.createDOM()', async () => { + const {editor} = createTestContext(); - await editor.update(() => { - const listNode = $createListNode('bullet', 1); - expect(listNode.createDOM(editorConfig).outerHTML).toBe( + await editor.update(() => { + const listNode = $createListNode('bullet', 1); + expect(listNode.createDOM(editorConfig).outerHTML).toBe( '', - ); - expect( + ); + expect( listNode.createDOM({ namespace: '', theme: { list: {}, }, }).outerHTML, - ).toBe(''); - expect( + ).toBe(''); + expect( listNode.createDOM({ namespace: '', theme: {}, }).outerHTML, - ).toBe(''); - }); + ).toBe(''); }); + }); - test('ListNode.createDOM() correctly applies classes to a nested ListNode', async () => { - const {editor} = testEnv; - - await editor.update(() => { - const listNode1 = $createListNode('bullet'); - const listNode2 = $createListNode('bullet'); - const listNode3 = $createListNode('bullet'); - const listNode4 = $createListNode('bullet'); - const listNode5 = $createListNode('bullet'); - const listNode6 = $createListNode('bullet'); - const listNode7 = $createListNode('bullet'); - - const listItem1 = $createListItemNode(); - const listItem2 = $createListItemNode(); - const listItem3 = $createListItemNode(); - const listItem4 = $createListItemNode(); - - listNode1.append(listItem1); - listItem1.append(listNode2); - listNode2.append(listItem2); - listItem2.append(listNode3); - listNode3.append(listItem3); - listItem3.append(listNode4); - listNode4.append(listItem4); - listNode4.append(listNode5); - listNode5.append(listNode6); - listNode6.append(listNode7); - - expect(listNode1.createDOM(editorConfig).outerHTML).toBe( + test('ListNode.createDOM() correctly applies classes to a nested ListNode', async () => { + const {editor} = createTestContext(); + + await editor.update(() => { + const listNode1 = $createListNode('bullet'); + const listNode2 = $createListNode('bullet'); + const listNode3 = $createListNode('bullet'); + const listNode4 = $createListNode('bullet'); + const listNode5 = $createListNode('bullet'); + const listNode6 = $createListNode('bullet'); + const listNode7 = $createListNode('bullet'); + + const listItem1 = $createListItemNode(); + const listItem2 = $createListItemNode(); + const listItem3 = $createListItemNode(); + const listItem4 = $createListItemNode(); + + listNode1.append(listItem1); + listItem1.append(listNode2); + listNode2.append(listItem2); + listItem2.append(listNode3); + listNode3.append(listItem3); + listItem3.append(listNode4); + listNode4.append(listItem4); + listNode4.append(listNode5); + listNode5.append(listNode6); + listNode6.append(listNode7); + + expect(listNode1.createDOM(editorConfig).outerHTML).toBe( '', - ); - expect( + ); + expect( listNode1.createDOM({ namespace: '', theme: { list: {}, }, }).outerHTML, - ).toBe(''); - expect( + ).toBe(''); + expect( listNode1.createDOM({ namespace: '', theme: {}, }).outerHTML, - ).toBe(''); - expect(listNode2.createDOM(editorConfig).outerHTML).toBe( + ).toBe(''); + expect(listNode2.createDOM(editorConfig).outerHTML).toBe( '', - ); - expect(listNode3.createDOM(editorConfig).outerHTML).toBe( + ); + expect(listNode3.createDOM(editorConfig).outerHTML).toBe( '', - ); - expect(listNode4.createDOM(editorConfig).outerHTML).toBe( + ); + expect(listNode4.createDOM(editorConfig).outerHTML).toBe( '', - ); - expect(listNode5.createDOM(editorConfig).outerHTML).toBe( + ); + expect(listNode5.createDOM(editorConfig).outerHTML).toBe( '', - ); - expect(listNode6.createDOM(editorConfig).outerHTML).toBe( + ); + expect(listNode6.createDOM(editorConfig).outerHTML).toBe( '', - ); - expect(listNode7.createDOM(editorConfig).outerHTML).toBe( + ); + expect(listNode7.createDOM(editorConfig).outerHTML).toBe( '', - ); - expect( + ); + expect( listNode5.createDOM({ namespace: '', theme: { @@ -176,123 +176,135 @@ describe('LexicalListNode tests', () => { }, }, }).outerHTML, - ).toBe(''); - }); + ).toBe(''); }); + }); - test('ListNode.updateDOM()', async () => { - const {editor} = testEnv; + test('ListNode.updateDOM()', async () => { + const {editor} = createTestContext(); - await editor.update(() => { - const listNode = $createListNode('bullet', 1); - const domElement = listNode.createDOM(editorConfig); + await editor.update(() => { + const listNode = $createListNode('bullet', 1); + const domElement = listNode.createDOM(editorConfig); - expect(domElement.outerHTML).toBe( + expect(domElement.outerHTML).toBe( '', - ); + ); - const newListNode = $createListNode('number', 1); - const result = newListNode.updateDOM( + const newListNode = $createListNode('number', 1); + const result = newListNode.updateDOM( listNode, domElement, editorConfig, - ); + ); - expect(result).toBe(true); - expect(domElement.outerHTML).toBe( + expect(result).toBe(true); + expect(domElement.outerHTML).toBe( '', - ); - }); + ); }); + }); - test('ListNode.append() should properly transform a ListItemNode', async () => { - const {editor} = testEnv; + test('ListNode.append() should properly transform a ListItemNode', async () => { + const {editor} = createTestContext(); - await editor.update(() => { - const listNode = new ListNode('bullet', 1); - const listItemNode = new ListItemNode(); - const textNode = new TextNode('Hello'); + await editor.update(() => { + const listNode = new ListNode('bullet', 1); + const listItemNode = new ListItemNode(); + const textNode = new TextNode('Hello'); - listItemNode.append(textNode); - const nodesToAppend = [listItemNode]; + listItemNode.append(textNode); + const nodesToAppend = [listItemNode]; - expect(listNode.append(...nodesToAppend)).toBe(listNode); - expect(listNode.getFirstChild()).toBe(listItemNode); - expect(listNode.getFirstChild()?.getTextContent()).toBe('Hello'); - }); + expect(listNode.append(...nodesToAppend)).toBe(listNode); + expect(listNode.getFirstChild()).toBe(listItemNode); + expect(listNode.getFirstChild()?.getTextContent()).toBe('Hello'); }); + }); - test('ListNode.append() should properly transform a ListNode', async () => { - const {editor} = testEnv; + test('ListNode.append() should properly transform a ListNode', async () => { + const {editor} = createTestContext(); - await editor.update(() => { - const listNode = new ListNode('bullet', 1); - const nestedListNode = new ListNode('bullet', 1); - const listItemNode = new ListItemNode(); - const textNode = new TextNode('Hello'); + await editor.update(() => { + const listNode = new ListNode('bullet', 1); + const nestedListNode = new ListNode('bullet', 1); + const listItemNode = new ListItemNode(); + const textNode = new TextNode('Hello'); - listItemNode.append(textNode); - nestedListNode.append(listItemNode); + listItemNode.append(textNode); + nestedListNode.append(listItemNode); - const nodesToAppend = [nestedListNode]; + const nodesToAppend = [nestedListNode]; - expect(listNode.append(...nodesToAppend)).toBe(listNode); - expect($isListItemNode(listNode.getFirstChild())).toBe(true); - expect(listNode.getFirstChild()!.getFirstChild()).toBe( + expect(listNode.append(...nodesToAppend)).toBe(listNode); + expect($isListItemNode(listNode.getFirstChild())).toBe(true); + expect(listNode.getFirstChild()!.getFirstChild()).toBe( nestedListNode, - ); - }); + ); }); + }); - test('ListNode.append() should properly transform a ParagraphNode', async () => { - const {editor} = testEnv; + test('ListNode.append() should properly transform a ParagraphNode', async () => { + const {editor} = createTestContext(); - await editor.update(() => { - const listNode = new ListNode('bullet', 1); - const paragraph = new ParagraphNode(); - const textNode = new TextNode('Hello'); - paragraph.append(textNode); - const nodesToAppend = [paragraph]; + await editor.update(() => { + const listNode = new ListNode('bullet', 1); + const paragraph = new ParagraphNode(); + const textNode = new TextNode('Hello'); + paragraph.append(textNode); + const nodesToAppend = [paragraph]; - expect(listNode.append(...nodesToAppend)).toBe(listNode); - expect($isListItemNode(listNode.getFirstChild())).toBe(true); - expect(listNode.getFirstChild()?.getTextContent()).toBe('Hello'); - }); + expect(listNode.append(...nodesToAppend)).toBe(listNode); + expect($isListItemNode(listNode.getFirstChild())).toBe(true); + expect(listNode.getFirstChild()?.getTextContent()).toBe('Hello'); }); + }); - test('$createListNode()', async () => { - const {editor} = testEnv; + test('$createListNode()', async () => { + const {editor} = createTestContext(); - await editor.update(() => { - const listNode = $createListNode('bullet', 1); - const createdListNode = $createListNode('bullet'); + await editor.update(() => { + const listNode = $createListNode('bullet', 1); + const createdListNode = $createListNode('bullet'); - expect(listNode.__type).toEqual(createdListNode.__type); - expect(listNode.__parent).toEqual(createdListNode.__parent); - expect(listNode.__tag).toEqual(createdListNode.__tag); - expect(listNode.__key).not.toEqual(createdListNode.__key); - }); + expect(listNode.__type).toEqual(createdListNode.__type); + expect(listNode.__parent).toEqual(createdListNode.__parent); + expect(listNode.__tag).toEqual(createdListNode.__tag); + expect(listNode.__key).not.toEqual(createdListNode.__key); }); + }); + + test('$isListNode()', async () => { + const {editor} = createTestContext(); + + await editor.update(() => { + const listNode = $createListNode('bullet', 1); - test('$isListNode()', async () => { - const {editor} = testEnv; + expect($isListNode(listNode)).toBe(true); + }); + }); - await editor.update(() => { - const listNode = $createListNode('bullet', 1); + test('$createListNode() with tag name (backward compatibility)', async () => { + const {editor} = createTestContext(); - expect($isListNode(listNode)).toBe(true); - }); + await editor.update(() => { + const numberList = $createListNode('number', 1); + const bulletList = $createListNode('bullet', 1); + expect(numberList.__listType).toBe('number'); + expect(bulletList.__listType).toBe('bullet'); }); + }); - test('$createListNode() with tag name (backward compatibility)', async () => { - const {editor} = testEnv; + test('importDOM handles old editor expected task list format', async () => { + const {editor} = createTestContext(); - await editor.update(() => { - const numberList = $createListNode('number', 1); - const bulletList = $createListNode('bullet', 1); - expect(numberList.__listType).toBe('number'); - expect(bulletList.__listType).toBe('bullet'); - }); + let list!: ListNode; + editor.update(() => { + const nodes = $htmlToBlockNodes(editor, `
  • A
`); + list = nodes[0] as ListNode; }); + + expect(list).toBeInstanceOf(ListNode); + expect(list.getListType()).toBe('check'); }); }); diff --git a/resources/js/wysiwyg/services/__tests__/keyboard-handling.test.ts b/resources/js/wysiwyg/services/__tests__/keyboard-handling.test.ts index 736c3573c..cd4235f2f 100644 --- a/resources/js/wysiwyg/services/__tests__/keyboard-handling.test.ts +++ b/resources/js/wysiwyg/services/__tests__/keyboard-handling.test.ts @@ -1,7 +1,7 @@ import { createTestContext, destroyFromContext, dispatchKeydownEventForNode, - dispatchKeydownEventForSelectedNode, + dispatchKeydownEventForSelectedNode, expectNodeShapeToMatch, } from "lexical/__tests__/utils"; import { $createParagraphNode, $createTextNode, @@ -13,6 +13,7 @@ import {registerKeyboardHandling} from "../keyboard-handling"; import {registerRichText} from "@lexical/rich-text"; import {EditorUiContext} from "../../ui/framework/core"; import {$createListItemNode, $createListNode, ListItemNode, ListNode} from "@lexical/list"; +import {$createImageNode, ImageNode} from "@lexical/rich-text/LexicalImageNode"; describe('Keyboard-handling service tests', () => { @@ -127,4 +128,34 @@ describe('Keyboard-handling service tests', () => { expect(selectedNode?.getKey()).toBe(innerList.getChildren()[0].getKey()); }); }); + + test('Images: up on selected image creates new paragraph if none above', () => { + let image!: ImageNode; + editor.updateAndCommit(() => { + const root = $getRoot(); + const imageWrap = $createParagraphNode(); + image = $createImageNode('https://example.com/cat.png'); + imageWrap.append(image); + root.append(imageWrap); + image.select(); + }); + + expectNodeShapeToMatch(editor, [{ + type: 'paragraph', + children: [ + {type: 'image'} + ], + }]); + + dispatchKeydownEventForNode(image, editor, 'ArrowUp'); + + expectNodeShapeToMatch(editor, [{ + type: 'paragraph', + }, { + type: 'paragraph', + children: [ + {type: 'image'} + ], + }]); + }); }); \ No newline at end of file diff --git a/resources/js/wysiwyg/services/keyboard-handling.ts b/resources/js/wysiwyg/services/keyboard-handling.ts index 0ef0b81bf..a7f1ec7f0 100644 --- a/resources/js/wysiwyg/services/keyboard-handling.ts +++ b/resources/js/wysiwyg/services/keyboard-handling.ts @@ -79,22 +79,19 @@ function focusAdjacentOrInsertForSingleSelectNode(editor: LexicalEditor, event: const nearestBlock = $getNearestNodeBlockParent(node) || node; let target = after ? nearestBlock.getNextSibling() : nearestBlock.getPreviousSibling(); - requestAnimationFrame(() => { - editor.update(() => { - if (!target) { - target = $createParagraphNode(); - if (after) { - nearestBlock.insertAfter(target) - } else { - nearestBlock.insertBefore(target); - } + editor.update(() => { + if (!target) { + target = $createParagraphNode(); + if (after) { + nearestBlock.insertAfter(target) + } else { + nearestBlock.insertBefore(target); } + } - target.selectStart(); - }); + target.selectStart(); }); - return true; }