Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,14 @@ import {
$isParagraphNode,
$isRangeSelection,
} from 'lexical';
import {DataTransferMock} from 'lexical/src/__tests__/utils';
import {assert, describe, expect, test} from 'vitest';

function $initialEditorState(): void {
$getRoot().append($createParagraphNode()).select();
}

function dataTransferWithHtml(html: string): DataTransfer {
const dt = new DataTransferMock();
const dt = new DataTransfer();
dt.setData('text/html', html);
return dt as unknown as DataTransfer;
}
Expand Down Expand Up @@ -165,7 +164,7 @@ describe('ClipboardImportExtension', () => {
name: 'host',
}),
);
const dt = new DataTransferMock();
const dt = new DataTransfer();
dt.setData('text/html', '<p>html-fallback</p>');
dt.setData('application/vnd.myapp+json', '{"a":1}');
editor.update(
Expand Down Expand Up @@ -217,7 +216,7 @@ describe('ClipboardImportExtension', () => {
name: 'host',
}),
);
const dt = new DataTransferMock();
const dt = new DataTransfer();
dt.setData('text/html', '<p>x</p>');
dt.setData('application/vnd.myapp+json', '{}');
editor.update(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
defineExtension,
} from '@lexical/extension';
import {$createParagraphNode, $createTextNode, $getRoot} from 'lexical';
import {DataTransferMock} from 'lexical/src/__tests__/utils';
import {describe, expect, it} from 'vitest';

const SEED_TEXT = 'hello world';
Expand Down Expand Up @@ -189,7 +188,7 @@ describe('GetClipboardDataExtension', () => {
'application/x-myformat': [() => 'custom-payload'],
},
});
const dt = new DataTransferMock();
const dt = new DataTransfer();
editor.read(() => {
setLexicalClipboardDataTransfer(
dt as unknown as DataTransfer,
Expand Down
27 changes: 14 additions & 13 deletions packages/lexical-code/src/__tests__/unit/LexicalCodeNode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import {
expectHtmlToBeEqual,
initializeUnitTest,
invariant,
KeyboardEventMock,
shiftTabKeyboardEvent,
tabKeyboardEvent,
} from 'lexical/src/__tests__/utils';
Expand Down Expand Up @@ -468,8 +467,10 @@ describe('LexicalCodeNode tests', () => {
code.selectStart();
$getSelection()!.insertRawText('abc\tdef\nghi\tjkl');
});
const keyEvent = new KeyboardEventMock();
keyEvent.altKey = true;
const keyEvent = new KeyboardEvent('keydown', {
altKey: true,
key: 'ArrowUp',
});
await editor.dispatchCommand(KEY_ARROW_UP_COMMAND, keyEvent);
expect(testEnv.innerHTML)
.toBe(`<code spellcheck="false" data-language="javascript" data-highlight-language="javascript" dir="auto" data-gutter="1
Expand Down Expand Up @@ -501,8 +502,10 @@ describe('LexicalCodeNode tests', () => {
selection.focus.set(secondCodeText.getKey(), 1, 'text');
$setSelection(selection);
});
const keyEvent = new KeyboardEventMock();
keyEvent.altKey = true;
const keyEvent = new KeyboardEvent('keydown', {
altKey: true,
key: 'ArrowDown',
});
await editor.dispatchCommand(KEY_ARROW_DOWN_COMMAND, keyEvent);
expect(testEnv.innerHTML)
.toBe(`<code spellcheck="false" data-language="javascript" data-highlight-language="javascript" dir="auto" data-gutter="1
Expand Down Expand Up @@ -540,7 +543,7 @@ describe('LexicalCodeNode tests', () => {

await editor.dispatchCommand(
MOVE_TO_END,
new KeyboardEventMock('keydown'),
new KeyboardEvent('keydown'),
);

await editor.update(() => {
Expand All @@ -562,7 +565,7 @@ describe('LexicalCodeNode tests', () => {

await editor.dispatchCommand(
MOVE_TO_START,
new KeyboardEventMock('keydown'),
new KeyboardEvent('keydown'),
);

await editor.update(() => {
Expand All @@ -583,8 +586,7 @@ describe('LexicalCodeNode tests', () => {
test('Shift+MOVE_TO_END preserves anchor and extends focus', async () => {
const {editor} = testEnv;
await setupRTLCode(editor);
const event = new KeyboardEventMock('keydown');
event.shiftKey = true;
const event = new KeyboardEvent('keydown', {shiftKey: true});

const before = editor.read(() => {
const s = $getSelection();
Expand All @@ -606,8 +608,7 @@ describe('LexicalCodeNode tests', () => {
test('Shift+MOVE_TO_START preserves anchor and extends focus', async () => {
const {editor} = testEnv;
await setupRTLCode(editor);
const event = new KeyboardEventMock('keydown');
event.shiftKey = true;
const event = new KeyboardEvent('keydown', {shiftKey: true});

const before = editor.read(() => {
const s = $getSelection();
Expand Down Expand Up @@ -662,12 +663,12 @@ describe('LexicalCodeNode tests', () => {
if (moveTo === 'start') {
await editor.dispatchCommand(
MOVE_TO_START,
new KeyboardEventMock('keydown'),
new KeyboardEvent('keydown'),
);
} else {
await editor.dispatchCommand(
MOVE_TO_END,
new KeyboardEventMock('keydown'),
new KeyboardEvent('keydown'),
);
}
await editor.update(() => {
Expand Down
12 changes: 10 additions & 2 deletions packages/lexical-link/src/LexicalLinkExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
$getSelection,
$isElementNode,
$isRangeSelection,
$isTextNode,
COMMAND_PRIORITY_EDITOR,
COMMAND_PRIORITY_LOW,
defineExtension,
Expand Down Expand Up @@ -110,8 +111,15 @@ export function registerLink(
if (!validateUrl(clipboardText)) {
return false;
}
// If we select nodes that are elements then avoid applying the link.
if (!selection.getNodes().some(node => $isElementNode(node))) {
// Skip link wrapping for non-simple text nodes (e.g. code blocks).
const nodes = selection.getNodes();
if (
!nodes.some(
node =>
$isElementNode(node) ||
($isTextNode(node) && !node.isSimpleText()),
)
) {
editor.dispatchCommand(TOGGLE_LINK_COMMAND, {
...attributes,
url: clipboardText,
Expand Down
86 changes: 86 additions & 0 deletions packages/lexical-link/src/__tests__/unit/LinkExtension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
* LICENSE file in the root directory of this source tree.
*
*/
import {
$createCodeHighlightNode,
$createCodeNode,
$isCodeNode,
CodeExtension,
} from '@lexical/code-core';
import {buildEditorFromExtensions, defineExtension} from '@lexical/extension';
import {
$createAutoLinkNode,
Expand All @@ -23,6 +29,10 @@ import {
$getSelection,
$isParagraphNode,
$isRangeSelection,
$isTextNode,
configExtension,
LexicalEditorWithDispose,
PASTE_COMMAND,
TextNode,
} from 'lexical';
import {assert, describe, expect, it} from 'vitest';
Expand Down Expand Up @@ -339,4 +349,80 @@ describe('Link', () => {
});
});
});

describe('PASTE_COMMAND with URLs', () => {
const pasteUrl = 'https://lexical.dev/';

function dispatchPaste(editor: LexicalEditorWithDispose) {
const clipboardData = new DataTransfer();
clipboardData.setData('text/plain', pasteUrl);
clipboardData.setData('text/html', pasteUrl);
const event = new ClipboardEvent('paste', {clipboardData});
editor.dispatchCommand(PASTE_COMMAND, event);
}

it('does not convert pasted URL to link inside a code block', () => {
const codeExtension = defineExtension({
$initialEditorState: () => {
const code = $createCodeNode();
code.append($createCodeHighlightNode('const x = 5;'));
$getRoot().append(code);
},
dependencies: [
configExtension(LinkExtension, {validateUrl: () => true}),
RichTextExtension,
CodeExtension,
],
name: '[root-code]',
});
using editor = buildEditorFromExtensions(codeExtension);
editor.update(
() => {
const codeHighlight = $getRoot().getFirstDescendant()!;
assert($isTextNode(codeHighlight), 'Expected a TextNode');
codeHighlight.select(0, 5);
dispatchPaste(editor);
},
{discrete: true},
);
editor.read(() => {
const code = $getRoot().getFirstChild()!;
assert($isCodeNode(code), 'Expected a CodeNode');
expect(code.getChildren().some($isLinkNode)).toBe(false);
});
});

it('wraps selected plain text in a link when a URL is pasted', () => {
const pasteExtension = defineExtension({
$initialEditorState: () => {
const p = $createParagraphNode();
p.append($createTextNode('click here'));
$getRoot().append(p);
},
dependencies: [
configExtension(LinkExtension, {validateUrl: () => true}),
RichTextExtension,
],
name: '[root-paste]',
});
using editor = buildEditorFromExtensions(pasteExtension);
editor.update(
() => {
const text = $getRoot().getFirstDescendant()!;
assert($isTextNode(text), 'Expected a TextNode');
text.select(0, text.getTextContentSize());
dispatchPaste(editor);
},
{discrete: true},
);
editor.read(() => {
const p = $getRoot().getFirstChild()!;
assert($isParagraphNode(p), 'Expected a ParagraphNode');
const link = p.getFirstChild();
assert($isLinkNode(link), 'Expected a LinkNode');
expect(link.getURL()).toBe(pasteUrl);
expect(link.getTextContent()).toBe('click here');
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import {
RangeSelection,
TEXT_TYPE_TO_FORMAT,
} from 'lexical';
import {KeyboardEventMock} from 'lexical/src/__tests__/utils';
import {assert, describe, expect, test} from 'vitest';

// This simulates what the $updateSelectionFormatStyleFromTextNode infrastructure
Expand Down Expand Up @@ -204,7 +203,7 @@ describe('RichTextExtension escapeFormatTriggers', () => {
$updateSelectionFormat($getRoot().selectEnd(), IS_CODE);
});

const keyEvent = new KeyboardEventMock();
const keyEvent = new KeyboardEvent('keydown');
editor.dispatchCommand(KEY_ARROW_RIGHT_COMMAND, keyEvent);

editor.read(() => {
Expand All @@ -221,8 +220,10 @@ describe('RichTextExtension escapeFormatTriggers', () => {
$updateSelectionFormat($getRoot().selectEnd(), IS_CODE);
});

const keyEvent = new KeyboardEventMock();
keyEvent.shiftKey = true;
const keyEvent = new KeyboardEvent('keydown', {
key: 'ArrowRight',
shiftKey: true,
});
editor.dispatchCommand(KEY_ARROW_RIGHT_COMMAND, keyEvent);

editor.read(() => {
Expand All @@ -238,7 +239,7 @@ describe('RichTextExtension escapeFormatTriggers', () => {
$updateSelectionFormat($getFirstTextNode().select(3, 3), IS_CODE);
});

const keyEvent = new KeyboardEventMock();
const keyEvent = new KeyboardEvent('keydown', {key: 'ArrowRight'});
editor.dispatchCommand(KEY_ARROW_RIGHT_COMMAND, keyEvent);

editor.read(() => {
Expand All @@ -256,7 +257,7 @@ describe('RichTextExtension escapeFormatTriggers', () => {
$updateSelectionFormat($getRoot().selectStart(), IS_CODE);
});

const keyEvent = new KeyboardEventMock();
const keyEvent = new KeyboardEvent('keydown', {key: 'ArrowLeft'});
editor.dispatchCommand(KEY_ARROW_LEFT_COMMAND, keyEvent);

editor.read(() => {
Expand All @@ -273,8 +274,10 @@ describe('RichTextExtension escapeFormatTriggers', () => {
$updateSelectionFormat($getRoot().selectStart(), IS_CODE);
});

const keyEvent = new KeyboardEventMock();
keyEvent.shiftKey = true;
const keyEvent = new KeyboardEvent('keydown', {
key: 'ArrowLeft',
shiftKey: true,
});
editor.dispatchCommand(KEY_ARROW_LEFT_COMMAND, keyEvent);

editor.read(() => {
Expand All @@ -290,7 +293,7 @@ describe('RichTextExtension escapeFormatTriggers', () => {
$updateSelectionFormat($getFirstTextNode().select(3, 3), IS_CODE);
});

const keyEvent = new KeyboardEventMock();
const keyEvent = new KeyboardEvent('keydown', {key: 'ArrowLeft'});
editor.dispatchCommand(KEY_ARROW_LEFT_COMMAND, keyEvent);

editor.read(() => {
Expand Down Expand Up @@ -332,7 +335,7 @@ describe('RichTextExtension default capitalization reset', () => {
test(`clears on ${COMMAND.type}`, () => {
using editor = createDefaultEditor();

editor.dispatchCommand(COMMAND, new KeyboardEventMock());
editor.dispatchCommand(COMMAND, new KeyboardEvent('keydown'));

editor.read(() => {
const selection = $getSelection();
Expand Down
Loading
Loading