diff --git a/packages/lexical-html/src/__tests__/unit/LexicalHtml.test.ts b/packages/lexical-html/src/__tests__/unit/LexicalHtml.test.ts new file mode 100644 index 00000000000..e4e9c997936 --- /dev/null +++ b/packages/lexical-html/src/__tests__/unit/LexicalHtml.test.ts @@ -0,0 +1,153 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +//@ts-ignore-next-line +import type {RangeSelection} from 'lexical'; + +import {CodeNode} from '@lexical/code'; +import {createHeadlessEditor} from '@lexical/headless'; +import {$generateHtmlFromNodes} from '@lexical/html'; +import {LinkNode} from '@lexical/link'; +import {ListItemNode, ListNode} from '@lexical/list'; +import {HeadingNode, QuoteNode} from '@lexical/rich-text'; +import { + $createParagraphNode, + $createRangeSelection, + $createTextNode, + $getRoot, +} from 'lexical'; + +describe('HTML', () => { + type Input = Array<{ + name: string; + html: string; + initializeEditorState: () => void; + }>; + + const HTML_SERIALIZE: Input = [ + { + html: '


', + initializeEditorState: () => { + $getRoot().append($createParagraphNode()); + }, + name: 'Empty editor state', + }, + ]; + for (const {name, html, initializeEditorState} of HTML_SERIALIZE) { + test(`[Lexical -> HTML]: ${name}`, () => { + const editor = createHeadlessEditor({ + nodes: [ + HeadingNode, + ListNode, + ListItemNode, + QuoteNode, + CodeNode, + LinkNode, + ], + }); + + editor.update(initializeEditorState, { + discrete: true, + }); + + expect( + editor.getEditorState().read(() => $generateHtmlFromNodes(editor)), + ).toBe(html); + }); + } + + test(`[Lexical -> HTML]: Use provided selection`, () => { + const editor = createHeadlessEditor({ + nodes: [ + HeadingNode, + ListNode, + ListItemNode, + QuoteNode, + CodeNode, + LinkNode, + ], + }); + + let selection: RangeSelection | null = null; + + editor.update( + () => { + const root = $getRoot(); + const p1 = $createParagraphNode(); + const text1 = $createTextNode('Hello'); + p1.append(text1); + const p2 = $createParagraphNode(); + const text2 = $createTextNode('World'); + p2.append(text2); + root.append(p1).append(p2); + // Root + // - ParagraphNode + // -- TextNode "Hello" + // - ParagraphNode + // -- TextNode "World" + p1.select(0, text1.getTextContentSize()); + selection = $createRangeSelection(); + selection.setTextNodeRange(text2, 0, text2, text2.getTextContentSize()); + }, + { + discrete: true, + }, + ); + + let html = ''; + + editor.update(() => { + html = $generateHtmlFromNodes(editor, selection); + }); + + expect(html).toBe('World'); + }); + + test(`[Lexical -> HTML]: Default selection (undefined) should serialize entire editor state`, () => { + const editor = createHeadlessEditor({ + nodes: [ + HeadingNode, + ListNode, + ListItemNode, + QuoteNode, + CodeNode, + LinkNode, + ], + }); + + editor.update( + () => { + const root = $getRoot(); + const p1 = $createParagraphNode(); + const text1 = $createTextNode('Hello'); + p1.append(text1); + const p2 = $createParagraphNode(); + const text2 = $createTextNode('World'); + p2.append(text2); + root.append(p1).append(p2); + // Root + // - ParagraphNode + // -- TextNode "Hello" + // - ParagraphNode + // -- TextNode "World" + p1.select(0, text1.getTextContentSize()); + }, + { + discrete: true, + }, + ); + + let html = ''; + + editor.update(() => { + html = $generateHtmlFromNodes(editor); + }); + + expect(html).toBe('

Hello

World

'); + }); +}); diff --git a/packages/lexical-html/src/index.ts b/packages/lexical-html/src/index.ts index bc18e1d6de5..763015a2f29 100644 --- a/packages/lexical-html/src/index.ts +++ b/packages/lexical-html/src/index.ts @@ -79,7 +79,8 @@ function $appendNodesToHTML( parentElement: HTMLElement | DocumentFragment, selection: RangeSelection | NodeSelection | GridSelection | null = null, ): boolean { - let shouldInclude = selection != null ? currentNode.isSelected() : true; + let shouldInclude = + selection != null ? currentNode.isSelected(selection) : true; const shouldExclude = $isElementNode(currentNode) && currentNode.excludeFromCopy('html'); let target = currentNode;