Skip to content

Commit

Permalink
More test coverage and change the behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
etrepum committed Oct 1, 2024
1 parent cd50c99 commit b99a11e
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 8 deletions.
11 changes: 11 additions & 0 deletions packages/lexical/src/LexicalSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,17 @@ export class RangeSelection implements BaseSelection {
let lastNode = lastPoint.getNode();
const firstBlock = $getAncestor(firstNode, INTERNAL_$isBlock);
const lastBlock = $getAncestor(lastNode, INTERNAL_$isBlock);
// If a token is partially selected then move the selection to cover the whole selection
if (
firstPoint.offset > 0 &&
$isTextNode(firstNode) &&
firstNode.isToken()
) {
firstPoint.offset = 0;
}
if (lastPoint.offset > 0 && $isTextNode(lastNode) && lastNode.isToken()) {
lastPoint.offset = lastNode.getTextContentSize();
}

selectedNodes.forEach((node) => {
if (
Expand Down
154 changes: 146 additions & 8 deletions packages/lexical/src/__tests__/unit/LexicalSelection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,14 +485,152 @@ describe('LexicalSelection tests', () => {
},
{discrete: true},
);
testEnv.editor.getEditorState().read(() => {
const allTextNodes = $getRoot().getAllTextNodes();
// These should get merged in reconciliation
expect(allTextNodes.map((node) => node.getTextContent())).toEqual([
'leadtext',
]);
expect(leadingText.isAttached()).toBe(true);
});
});
});
describe('with a leading token TextNode and a trailing TextNode', () => {
let leadingTokenText: TextNode;
let trailingText: TextNode;
let paragraph: ParagraphNode;
beforeEach(() => {
testEnv.editor.update(
() => {
leadingTokenText = $createTextNode('token text').setMode('token');
trailingText = $createTextNode('trailing text');
paragraph = $createParagraphNode().append(
leadingTokenText,
trailingText,
);
$getRoot().clear().append(paragraph);
},
{discrete: true},
);
});
test('remove all text', () => {
testEnv.editor.update(
() => {
const sel = $createRangeSelection();
sel.anchor.set(leadingTokenText.getKey(), 0, 'text');
sel.focus.set(
trailingText.getKey(),
trailingText.getTextContentSize(),
'text',
);
$setSelection(sel);
sel.removeText();
expect(leadingTokenText.isAttached()).toBe(false);
expect(trailingText.isAttached()).toBe(false);
expect($getRoot().getAllTextNodes()).toHaveLength(0);
const selection = $assertRangeSelection($getSelection());
expect(selection.isCollapsed()).toBe(true);
expect(selection.anchor.key).toBe(paragraph.getKey());
expect(selection.anchor.offset).toBe(0);
},
{discrete: true},
);
});
test('remove trailing TextNode', () => {
testEnv.editor.update(
() => {
const sel = $createRangeSelection();
sel.anchor.set(trailingText.getKey(), 0, 'text');
sel.focus.set(
trailingText.getKey(),
trailingText.getTextContentSize(),
'text',
);
$setSelection(sel);
sel.removeText();
expect(leadingTokenText.isAttached()).toBe(true);
expect(trailingText.isAttached()).toBe(false);
expect($getRoot().getAllTextNodes()).toHaveLength(1);
const selection = $assertRangeSelection($getSelection());
expect(selection.isCollapsed()).toBe(true);
expect(selection.anchor.key).toBe(leadingTokenText.getKey());
expect(selection.anchor.offset).toBe(
leadingTokenText.getTextContentSize(),
);
},
{discrete: true},
);
});
test('remove leading token TextNode', () => {
testEnv.editor.update(
() => {
const sel = $createRangeSelection();
sel.anchor.set(leadingTokenText.getKey(), 0, 'text');
sel.focus.set(
leadingTokenText.getKey(),
leadingTokenText.getTextContentSize(),
'text',
);
$setSelection(sel);
sel.removeText();
expect(leadingTokenText.isAttached()).toBe(false);
expect(trailingText.isAttached()).toBe(true);
expect($getRoot().getAllTextNodes()).toHaveLength(1);
const selection = $assertRangeSelection($getSelection());
expect(selection.isCollapsed()).toBe(true);
expect(selection.anchor.key).toBe(trailingText.getKey());
expect(selection.anchor.offset).toBe(0);
},
{discrete: true},
);
});
test('remove partial leading token TextNode and trailing TextNode', () => {
testEnv.editor.update(
() => {
const sel = $createRangeSelection();
sel.anchor.set(
leadingTokenText.getKey(),
'token '.length,
'text',
);
sel.focus.set(
trailingText.getKey(),
trailingText.getTextContentSize(),
'text',
);
$setSelection(sel);
sel.removeText();
expect(trailingText.isAttached()).toBe(false);
// expecting no node since it was token
expect(leadingTokenText.isAttached()).toBe(false);
const allTextNodes = $getRoot().getAllTextNodes();
expect(allTextNodes).toHaveLength(0);
const selection = $assertRangeSelection($getSelection());
expect(selection.isCollapsed()).toBe(true);
expect(selection.anchor.key).toBe(paragraph.getKey());
expect(selection.anchor.offset).toBe(0);
},
{discrete: true},
);
});
test('remove partial token TextNode and partial trailing TextNode', () => {
testEnv.editor.update(
() => {
const sel = $createRangeSelection();
sel.anchor.set(
leadingTokenText.getKey(),
'token '.length,
'text',
);
sel.focus.set(trailingText.getKey(), 'trail'.length, 'text');
$setSelection(sel);
sel.removeText();
expect(leadingTokenText.isAttached()).toBe(false);
expect(trailingText.isAttached()).toBe(true);
const allTextNodes = $getRoot().getAllTextNodes();
// The token node will be completely removed
expect(allTextNodes.map((node) => node.getTextContent())).toEqual(
['ing text'],
);
const selection = $assertRangeSelection($getSelection());
expect(selection.isCollapsed()).toBe(true);
expect(selection.anchor.key).toBe(trailingText.getKey());
expect(selection.anchor.offset).toBe(0);
},
{discrete: true},
);
});
});
describe('with a leading TextNode and a trailing segmented TextNode', () => {
Expand Down

0 comments on commit b99a11e

Please sign in to comment.