From 1841b2d3b7e54415f552d3b659c13edab43d84c9 Mon Sep 17 00:00:00 2001 From: Gerard Rovira Date: Mon, 30 Oct 2023 12:10:05 -0400 Subject: [PATCH] Fix insertNodes after selection swap (#5175) --- .../unit/LexicalSelectionHelpers.test.ts | 32 +++++++++++++++++++ packages/lexical/src/LexicalSelection.ts | 8 ++++- .../lexical/src/__tests__/utils/index.tsx | 10 ++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/packages/lexical-selection/src/__tests__/unit/LexicalSelectionHelpers.test.ts b/packages/lexical-selection/src/__tests__/unit/LexicalSelectionHelpers.test.ts index 928739afe95..d0ed380c390 100644 --- a/packages/lexical-selection/src/__tests__/unit/LexicalSelectionHelpers.test.ts +++ b/packages/lexical-selection/src/__tests__/unit/LexicalSelectionHelpers.test.ts @@ -15,6 +15,7 @@ import { import { $createLineBreakNode, $createParagraphNode, + $createRangeSelection, $createTextNode, $getNodeByKey, $getRoot, @@ -22,6 +23,7 @@ import { $insertNodes, $isNodeSelection, $isRangeSelection, + $setSelection, RangeSelection, TextNode, } from 'lexical'; @@ -30,6 +32,7 @@ import { $createTestElementNode, $createTestShadowRootNode, createTestEditor, + createTestHeadlessEditor, TestDecoratorNode, } from 'lexical/src/__tests__/utils'; @@ -2669,6 +2672,35 @@ describe('insertNodes', () => { '

Text after

', ); }); + + it('can insert when previous selection was null', async () => { + const editor = createTestHeadlessEditor(); + await editor.update(() => { + const selection = $createRangeSelection(); + selection.anchor.set('root', 0, 'element'); + selection.focus.set('root', 0, 'element'); + + selection.insertNodes([ + $createParagraphNode().append($createTextNode('Text')), + ]); + + expect($getRoot().getTextContent()).toBe('Text'); + + $setSelection(null); + }); + await editor.update(() => { + const selection = $createRangeSelection(); + const text = $getRoot().getLastDescendant(); + selection.anchor.set(text.getKey(), 0, 'text'); + selection.focus.set(text.getKey(), 0, 'text'); + + selection.insertNodes([ + $createParagraphNode().append($createTextNode('Before ')), + ]); + + expect($getRoot().getTextContent()).toBe('Before Text'); + }); + }); }); describe('$patchStyleText', () => { diff --git a/packages/lexical/src/LexicalSelection.ts b/packages/lexical/src/LexicalSelection.ts index 0e0adeffe5e..f00ec4bbc33 100644 --- a/packages/lexical/src/LexicalSelection.ts +++ b/packages/lexical/src/LexicalSelection.ts @@ -1522,12 +1522,18 @@ export class RangeSelection implements BaseSelection { * * @param nodes - the nodes to insert */ - insertNodes(nodes: Array) { + insertNodes(nodes: Array): void { if (nodes.length === 0) { return; } if (this.anchor.key === 'root') { this.insertParagraph(); + const selection = $getSelection(); + invariant( + $isRangeSelection(selection), + 'Expected RangeSelection after insertParagraph', + ); + return selection.insertNodes(nodes); } const firstBlock = $getAncestor(this.anchor.getNode(), INTERNAL_$isBlock)!; diff --git a/packages/lexical/src/__tests__/utils/index.tsx b/packages/lexical/src/__tests__/utils/index.tsx index 0c6cd3cb72e..43123971ce2 100644 --- a/packages/lexical/src/__tests__/utils/index.tsx +++ b/packages/lexical/src/__tests__/utils/index.tsx @@ -8,6 +8,7 @@ import {CodeHighlightNode, CodeNode} from '@lexical/code'; import {HashtagNode} from '@lexical/hashtag'; +import {createHeadlessEditor} from '@lexical/headless'; import {AutoLinkNode, LinkNode} from '@lexical/link'; import {ListItemNode, ListNode} from '@lexical/list'; import {OverflowNode} from '@lexical/overflow'; @@ -504,6 +505,15 @@ export function createTestEditor( return editor; } +export function createTestHeadlessEditor(): LexicalEditor { + return createHeadlessEditor({ + namespace: '', + onError: (error) => { + throw error; + }, + }); +} + export function $assertRangeSelection(selection): RangeSelection { if (!$isRangeSelection(selection)) { throw new Error(`Expected RangeSelection, got ${selection}`);