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;