Skip to content

Commit

Permalink
Bug: Undo command after creating a Quote removes text after Quote ele…
Browse files Browse the repository at this point in the history
…ment (#2750) (#2767)
  • Loading branch information
iampava authored Aug 5, 2022
1 parent eb7d564 commit e90eea1
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 1 deletion.
148 changes: 148 additions & 0 deletions packages/lexical-history/src/__tests__/unit/LexicalHistory.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* 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.
*
*/

import {useLexicalComposerContext} from '@lexical/react/src/LexicalComposerContext';
import {ContentEditable} from '@lexical/react/src/LexicalContentEditable';
import {HistoryPlugin} from '@lexical/react/src/LexicalHistoryPlugin';
import {RichTextPlugin} from '@lexical/react/src/LexicalRichTextPlugin';
import {$createQuoteNode} from '@lexical/rich-text/src';
import {$wrapLeafNodesInElements} from '@lexical/selection/src';
import {
$createRangeSelection,
LexicalEditor,
SerializedElementNode,
SerializedTextNode,
UNDO_COMMAND,
} from 'lexical/src';
import {TestComposer} from 'lexical/src/__tests__/utils';
import {$getRoot, $setSelection} from 'lexical/src/LexicalUtils';
import {$createParagraphNode} from 'lexical/src/nodes/LexicalParagraphNode';
import {$createTextNode} from 'lexical/src/nodes/LexicalTextNode';
import React from 'react';
import {createRoot} from 'react-dom/client';
import * as ReactTestUtils from 'react-dom/test-utils';

describe('LexicalHistory tests', () => {
let container: HTMLDivElement | null = null;
let reactRoot;

beforeEach(() => {
container = document.createElement('div');
reactRoot = createRoot(container);
document.body.appendChild(container);
});

afterEach(() => {
if (container !== null) {
document.body.removeChild(container);
}
container = null;

jest.restoreAllMocks();
});

// Shared instance across tests
let editor: LexicalEditor;

test('LexicalHistory.Redo after Quote Node', async () => {
function Test(): JSX.Element {
function TestPlugin() {
// Plugin used just to get our hands on the Editor object
[editor] = useLexicalComposerContext();
return null;
}

return (
<TestComposer>
<RichTextPlugin
contentEditable={<ContentEditable />}
placeholder={
<div className="editor-placeholder">Enter some text...</div>
}
/>
<TestPlugin />
<HistoryPlugin />
</TestComposer>
);
}

ReactTestUtils.act(() => {
reactRoot.render(<Test key="smth" />);
});

// Wait for update to complete
await Promise.resolve().then();

await ReactTestUtils.act(async () => {
await editor.update(() => {
const root = $getRoot();
const paragraph1 = createParagraphNode('AAA');
const paragraph2 = createParagraphNode('BBB');

// The editor has one child that is an empty
// paragraph Node.
root.getChildAtIndex(0)?.replace(paragraph1);
root.append(paragraph2);
});
});

const initialJSONState = editor.getEditorState().toJSON();

await ReactTestUtils.act(async () => {
await editor.update(() => {
const root = $getRoot();
const selection = $createRangeSelection();

const firstTextNode = root.getAllTextNodes()[0];
selection.anchor.set(firstTextNode.getKey(), 0, 'text');
selection.focus.set(firstTextNode.getKey(), 3, 'text');

$setSelection(selection);
$wrapLeafNodesInElements(selection, () => $createQuoteNode());
});
});

const afterQuoteInsertionJSONState = editor.getEditorState().toJSON();
expect(afterQuoteInsertionJSONState.root.children.length).toBe(2);
expect(afterQuoteInsertionJSONState.root.children[0].type).toBe('quote');

expect(
(afterQuoteInsertionJSONState.root.children as SerializedElementNode[])[0]
.children.length,
).toBe(1);
expect(
(afterQuoteInsertionJSONState.root.children as SerializedElementNode[])[0]
.children[0].type,
).toBe('text');
expect(
(
(
afterQuoteInsertionJSONState.root.children as SerializedElementNode[]
)[0].children[0] as SerializedTextNode
).text,
).toBe('AAA');

await ReactTestUtils.act(async () => {
await editor.update(() => {
editor.dispatchCommand(UNDO_COMMAND, undefined);
});
});

expect(JSON.stringify(initialJSONState)).toBe(
JSON.stringify(editor.getEditorState().toJSON()),
);
});
});

const createParagraphNode = (text: string) => {
const paragraph = $createParagraphNode();
const textNode = $createTextNode(text);

paragraph.append(textNode);
return paragraph;
};
3 changes: 2 additions & 1 deletion packages/lexical-history/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ function isTextNodeUnchanged(
prevNode.__mode === nextNode.__mode &&
prevNode.__detail === nextNode.__detail &&
prevNode.__style === nextNode.__style &&
prevNode.__format === nextNode.__format
prevNode.__format === nextNode.__format &&
prevNode.__parent === nextNode.__parent
);
}
return false;
Expand Down

2 comments on commit e90eea1

@vercel
Copy link

@vercel vercel bot commented on e90eea1 Aug 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

lexical – ./packages/lexical-website-new

lexical.dev
lexical-git-main-fbopensource.vercel.app
lexical-fbopensource.vercel.app
www.lexical.dev
lexicaljs.org
lexicaljs.com

@vercel
Copy link

@vercel vercel bot commented on e90eea1 Aug 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

lexical-playground – ./packages/lexical-playground

lexical-playground-fbopensource.vercel.app
lexical-playground.vercel.app
lexical-playground-git-main-fbopensource.vercel.app
playground.lexical.dev

Please sign in to comment.