Skip to content

Commit

Permalink
Merge branch 'main' into enhance/lexical-yjs-provider-awareness-type
Browse files Browse the repository at this point in the history
  • Loading branch information
collierrgbsitisfise authored Oct 2, 2024
2 parents 85f5ff0 + ecb70ac commit 066d01f
Show file tree
Hide file tree
Showing 2 changed files with 300 additions and 0 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 (
$isTextNode(firstNode) &&
firstNode.isToken() &&
firstPoint.offset < firstNode.getTextContentSize()
) {
firstPoint.offset = 0;
}
if (lastPoint.offset > 0 && $isTextNode(lastNode) && lastNode.isToken()) {
lastPoint.offset = lastNode.getTextContentSize();
}

selectedNodes.forEach((node) => {
if (
Expand Down
289 changes: 289 additions & 0 deletions packages/lexical/src/__tests__/unit/LexicalSelection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,295 @@ describe('LexicalSelection tests', () => {
});
});
describe('removeText', () => {
describe('with a leading TextNode and a trailing token TextNode', () => {
let leadingText: TextNode;
let trailingTokenText: TextNode;
let paragraph: ParagraphNode;
beforeEach(() => {
testEnv.editor.update(
() => {
leadingText = $createTextNode('leading text');
trailingTokenText =
$createTextNode('token text').setMode('token');
paragraph = $createParagraphNode().append(
leadingText,
trailingTokenText,
);
$getRoot().clear().append(paragraph);
},
{discrete: true},
);
});
test('remove all text', () => {
testEnv.editor.update(
() => {
const sel = $createRangeSelection();
sel.anchor.set(leadingText.getKey(), 0, 'text');
sel.focus.set(
trailingTokenText.getKey(),
trailingTokenText.getTextContentSize(),
'text',
);
$setSelection(sel);
sel.removeText();
expect(leadingText.isAttached()).toBe(false);
expect(trailingTokenText.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 initial TextNode', () => {
testEnv.editor.update(
() => {
const sel = $createRangeSelection();
sel.anchor.set(leadingText.getKey(), 0, 'text');
sel.focus.set(
leadingText.getKey(),
leadingText.getTextContentSize(),
'text',
);
$setSelection(sel);
sel.removeText();
expect(leadingText.isAttached()).toBe(false);
expect(trailingTokenText.isAttached()).toBe(true);
expect($getRoot().getAllTextNodes()).toHaveLength(1);
const selection = $assertRangeSelection($getSelection());
expect(selection.isCollapsed()).toBe(true);
expect(selection.anchor.key).toBe(trailingTokenText.getKey());
expect(selection.anchor.offset).toBe(0);
},
{discrete: true},
);
});
test('remove trailing token TextNode', () => {
testEnv.editor.update(
() => {
const sel = $createRangeSelection();
sel.anchor.set(trailingTokenText.getKey(), 0, 'text');
sel.focus.set(
trailingTokenText.getKey(),
trailingTokenText.getTextContentSize(),
'text',
);
$setSelection(sel);
sel.removeText();
expect(leadingText.isAttached()).toBe(true);
expect(trailingTokenText.isAttached()).toBe(false);
expect($getRoot().getAllTextNodes()).toHaveLength(1);
const selection = $assertRangeSelection($getSelection());
expect(selection.isCollapsed()).toBe(true);
expect(selection.anchor.key).toBe(leadingText.getKey());
expect(selection.anchor.offset).toBe(
leadingText.getTextContentSize(),
);
},
{discrete: true},
);
});
test('remove initial TextNode and partial token TextNode', () => {
testEnv.editor.update(
() => {
const sel = $createRangeSelection();
sel.anchor.set(leadingText.getKey(), 0, 'text');
sel.focus.set(
trailingTokenText.getKey(),
'token '.length,
'text',
);
$setSelection(sel);
sel.removeText();
expect(leadingText.isAttached()).toBe(false);
// expecting no node since it was token
expect(trailingTokenText.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 initial TextNode and partial token TextNode', () => {
testEnv.editor.update(
() => {
const sel = $createRangeSelection();
sel.anchor.set(leadingText.getKey(), 'lead'.length, 'text');
sel.focus.set(
trailingTokenText.getKey(),
'token '.length,
'text',
);
$setSelection(sel);
sel.removeText();
expect(leadingText.isAttached()).toBe(true);
expect(trailingTokenText.isAttached()).toBe(false);
const allTextNodes = $getRoot().getAllTextNodes();
// The token node will be completely removed
expect(allTextNodes.map((node) => node.getTextContent())).toEqual(
['lead'],
);
const selection = $assertRangeSelection($getSelection());
expect(selection.isCollapsed()).toBe(true);
expect(selection.anchor.key).toBe(leadingText.getKey());
expect(selection.anchor.offset).toBe('lead'.length);
},
{discrete: 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', () => {
let leadingText: TextNode;
let trailingSegmentedText: TextNode;
Expand Down

0 comments on commit 066d01f

Please sign in to comment.