2 * Copyright (c) Meta Platforms, Inc. and affiliates.
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
14 SerializedAutoLinkNode,
15 } from '@lexical/link';
20 SerializedParagraphNode,
23 import {initializeUnitTest} from 'lexical/__tests__/utils';
25 const editorConfig = Object.freeze({
28 link: 'my-autolink-class',
30 bold: 'my-bold-class',
31 code: 'my-code-class',
32 hashtag: 'my-hashtag-class',
33 italic: 'my-italic-class',
34 strikethrough: 'my-strikethrough-class',
35 underline: 'my-underline-class',
36 underlineStrikethrough: 'my-underline-strikethrough-class',
41 describe('LexicalAutoAutoLinkNode tests', () => {
42 initializeUnitTest((testEnv) => {
43 test('AutoAutoLinkNode.constructor', async () => {
44 const {editor} = testEnv;
46 await editor.update(() => {
47 const actutoLinkNode = new AutoLinkNode('/');
49 expect(actutoLinkNode.__type).toBe('autolink');
50 expect(actutoLinkNode.__url).toBe('/');
51 expect(actutoLinkNode.__isUnlinked).toBe(false);
54 expect(() => new AutoLinkNode('')).toThrow();
57 test('AutoAutoLinkNode.constructor with isUnlinked param set to true', async () => {
58 const {editor} = testEnv;
60 await editor.update(() => {
61 const actutoLinkNode = new AutoLinkNode('/', {
65 expect(actutoLinkNode.__type).toBe('autolink');
66 expect(actutoLinkNode.__url).toBe('/');
67 expect(actutoLinkNode.__isUnlinked).toBe(true);
70 expect(() => new AutoLinkNode('')).toThrow();
75 test('LineBreakNode.clone()', async () => {
76 const {editor} = testEnv;
78 await editor.update(() => {
79 const autoLinkNode = new AutoLinkNode('/');
81 const clone = AutoLinkNode.clone(autoLinkNode);
83 expect(clone).not.toBe(autoLinkNode);
84 expect(clone).toStrictEqual(autoLinkNode);
88 test('AutoLinkNode.getURL()', async () => {
89 const {editor} = testEnv;
91 await editor.update(() => {
92 const autoLinkNode = new AutoLinkNode('https://example.com/foo');
94 expect(autoLinkNode.getURL()).toBe('https://example.com/foo');
98 test('AutoLinkNode.setURL()', async () => {
99 const {editor} = testEnv;
101 await editor.update(() => {
102 const autoLinkNode = new AutoLinkNode('https://example.com/foo');
104 expect(autoLinkNode.getURL()).toBe('https://example.com/foo');
106 autoLinkNode.setURL('https://example.com/bar');
108 expect(autoLinkNode.getURL()).toBe('https://example.com/bar');
112 test('AutoLinkNode.getTarget()', async () => {
113 const {editor} = testEnv;
115 await editor.update(() => {
116 const autoLinkNode = new AutoLinkNode('https://example.com/foo', {
120 expect(autoLinkNode.getTarget()).toBe('_blank');
124 test('AutoLinkNode.setTarget()', async () => {
125 const {editor} = testEnv;
127 await editor.update(() => {
128 const autoLinkNode = new AutoLinkNode('https://example.com/foo', {
132 expect(autoLinkNode.getTarget()).toBe('_blank');
134 autoLinkNode.setTarget('_self');
136 expect(autoLinkNode.getTarget()).toBe('_self');
140 test('AutoLinkNode.getRel()', async () => {
141 const {editor} = testEnv;
143 await editor.update(() => {
144 const autoLinkNode = new AutoLinkNode('https://example.com/foo', {
145 rel: 'noopener noreferrer',
149 expect(autoLinkNode.getRel()).toBe('noopener noreferrer');
153 test('AutoLinkNode.setRel()', async () => {
154 const {editor} = testEnv;
156 await editor.update(() => {
157 const autoLinkNode = new AutoLinkNode('https://example.com/foo', {
162 expect(autoLinkNode.getRel()).toBe('noopener');
164 autoLinkNode.setRel('noopener noreferrer');
166 expect(autoLinkNode.getRel()).toBe('noopener noreferrer');
170 test('AutoLinkNode.getTitle()', async () => {
171 const {editor} = testEnv;
173 await editor.update(() => {
174 const autoLinkNode = new AutoLinkNode('https://example.com/foo', {
175 title: 'Hello world',
178 expect(autoLinkNode.getTitle()).toBe('Hello world');
182 test('AutoLinkNode.setTitle()', async () => {
183 const {editor} = testEnv;
185 await editor.update(() => {
186 const autoLinkNode = new AutoLinkNode('https://example.com/foo', {
187 title: 'Hello world',
190 expect(autoLinkNode.getTitle()).toBe('Hello world');
192 autoLinkNode.setTitle('World hello');
194 expect(autoLinkNode.getTitle()).toBe('World hello');
198 test('AutoLinkNode.getIsUnlinked()', async () => {
199 const {editor} = testEnv;
201 await editor.update(() => {
202 const autoLinkNode = new AutoLinkNode('/', {
205 expect(autoLinkNode.getIsUnlinked()).toBe(true);
209 test('AutoLinkNode.setIsUnlinked()', async () => {
210 const {editor} = testEnv;
212 await editor.update(() => {
213 const autoLinkNode = new AutoLinkNode('/');
214 expect(autoLinkNode.getIsUnlinked()).toBe(false);
215 autoLinkNode.setIsUnlinked(true);
216 expect(autoLinkNode.getIsUnlinked()).toBe(true);
220 test('AutoLinkNode.createDOM()', async () => {
221 const {editor} = testEnv;
223 await editor.update(() => {
224 const autoLinkNode = new AutoLinkNode('https://example.com/foo');
226 expect(autoLinkNode.createDOM(editorConfig).outerHTML).toBe(
227 '<a href="https://example.com/foo" class="my-autolink-class"></a>',
230 autoLinkNode.createDOM({
234 ).toBe('<a href="https://example.com/foo"></a>');
238 test('AutoLinkNode.createDOM() for unlinked', async () => {
239 const {editor} = testEnv;
241 await editor.update(() => {
242 const autoLinkNode = new AutoLinkNode('https://example.com/foo', {
246 expect(autoLinkNode.createDOM(editorConfig).outerHTML).toBe(
247 `<span>${autoLinkNode.getTextContent()}</span>`,
252 test('AutoLinkNode.createDOM() with target, rel and title', async () => {
253 const {editor} = testEnv;
255 await editor.update(() => {
256 const autoLinkNode = new AutoLinkNode('https://example.com/foo', {
257 rel: 'noopener noreferrer',
259 title: 'Hello world',
262 expect(autoLinkNode.createDOM(editorConfig).outerHTML).toBe(
263 '<a href="https://example.com/foo" target="_blank" rel="noopener noreferrer" title="Hello world" class="my-autolink-class"></a>',
266 autoLinkNode.createDOM({
271 '<a href="https://example.com/foo" target="_blank" rel="noopener noreferrer" title="Hello world"></a>',
276 test('AutoLinkNode.createDOM() sanitizes javascript: URLs', async () => {
277 const {editor} = testEnv;
279 await editor.update(() => {
280 // eslint-disable-next-line no-script-url
281 const autoLinkNode = new AutoLinkNode('javascript:alert(0)');
282 expect(autoLinkNode.createDOM(editorConfig).outerHTML).toBe(
283 '<a href="about:blank" class="my-autolink-class"></a>',
288 test('AutoLinkNode.updateDOM()', async () => {
289 const {editor} = testEnv;
291 await editor.update(() => {
292 const autoLinkNode = new AutoLinkNode('https://example.com/foo');
294 const domElement = autoLinkNode.createDOM(editorConfig);
296 expect(autoLinkNode.createDOM(editorConfig).outerHTML).toBe(
297 '<a href="https://example.com/foo" class="my-autolink-class"></a>',
300 const newAutoLinkNode = new AutoLinkNode('https://example.com/bar');
301 const result = newAutoLinkNode.updateDOM(
307 expect(result).toBe(false);
308 expect(domElement.outerHTML).toBe(
309 '<a href="https://example.com/bar" class="my-autolink-class"></a>',
314 test('AutoLinkNode.updateDOM() with target, rel and title', async () => {
315 const {editor} = testEnv;
317 await editor.update(() => {
318 const autoLinkNode = new AutoLinkNode('https://example.com/foo', {
319 rel: 'noopener noreferrer',
321 title: 'Hello world',
324 const domElement = autoLinkNode.createDOM(editorConfig);
326 expect(autoLinkNode.createDOM(editorConfig).outerHTML).toBe(
327 '<a href="https://example.com/foo" target="_blank" rel="noopener noreferrer" title="Hello world" class="my-autolink-class"></a>',
330 const newAutoLinkNode = new AutoLinkNode('https://example.com/bar', {
333 title: 'World hello',
335 const result = newAutoLinkNode.updateDOM(
341 expect(result).toBe(false);
342 expect(domElement.outerHTML).toBe(
343 '<a href="https://example.com/bar" target="_self" rel="noopener" title="World hello" class="my-autolink-class"></a>',
348 test('AutoLinkNode.updateDOM() with undefined target, undefined rel and undefined title', async () => {
349 const {editor} = testEnv;
351 await editor.update(() => {
352 const autoLinkNode = new AutoLinkNode('https://example.com/foo', {
353 rel: 'noopener noreferrer',
355 title: 'Hello world',
358 const domElement = autoLinkNode.createDOM(editorConfig);
360 expect(autoLinkNode.createDOM(editorConfig).outerHTML).toBe(
361 '<a href="https://example.com/foo" target="_blank" rel="noopener noreferrer" title="Hello world" class="my-autolink-class"></a>',
364 const newNode = new AutoLinkNode('https://example.com/bar');
365 const result = newNode.updateDOM(
371 expect(result).toBe(false);
372 expect(domElement.outerHTML).toBe(
373 '<a href="https://example.com/bar" class="my-autolink-class"></a>',
378 test('AutoLinkNode.updateDOM() with isUnlinked "true"', async () => {
379 const {editor} = testEnv;
381 await editor.update(() => {
382 const autoLinkNode = new AutoLinkNode('https://example.com/foo', {
386 const domElement = autoLinkNode.createDOM(editorConfig);
387 expect(domElement.outerHTML).toBe(
388 '<a href="https://example.com/foo" class="my-autolink-class"></a>',
391 const newAutoLinkNode = new AutoLinkNode('https://example.com/bar', {
394 const newDomElement = newAutoLinkNode.createDOM(editorConfig);
395 expect(newDomElement.outerHTML).toBe(
396 `<span>${newAutoLinkNode.getTextContent()}</span>`,
399 const result = newAutoLinkNode.updateDOM(
404 expect(result).toBe(true);
408 test('AutoLinkNode.canInsertTextBefore()', async () => {
409 const {editor} = testEnv;
411 await editor.update(() => {
412 const autoLinkNode = new AutoLinkNode('https://example.com/foo');
414 expect(autoLinkNode.canInsertTextBefore()).toBe(false);
418 test('AutoLinkNode.canInsertTextAfter()', async () => {
419 const {editor} = testEnv;
421 await editor.update(() => {
422 const autoLinkNode = new AutoLinkNode('https://example.com/foo');
423 expect(autoLinkNode.canInsertTextAfter()).toBe(false);
427 test('$createAutoLinkNode()', async () => {
428 const {editor} = testEnv;
430 await editor.update(() => {
431 const autoLinkNode = new AutoLinkNode('https://example.com/foo');
432 const createdAutoLinkNode = $createAutoLinkNode(
433 'https://example.com/foo',
436 expect(autoLinkNode.__type).toEqual(createdAutoLinkNode.__type);
437 expect(autoLinkNode.__parent).toEqual(createdAutoLinkNode.__parent);
438 expect(autoLinkNode.__url).toEqual(createdAutoLinkNode.__url);
439 expect(autoLinkNode.__isUnlinked).toEqual(
440 createdAutoLinkNode.__isUnlinked,
442 expect(autoLinkNode.__key).not.toEqual(createdAutoLinkNode.__key);
446 test('$createAutoLinkNode() with target, rel, isUnlinked and title', async () => {
447 const {editor} = testEnv;
449 await editor.update(() => {
450 const autoLinkNode = new AutoLinkNode('https://example.com/foo', {
451 rel: 'noopener noreferrer',
453 title: 'Hello world',
456 const createdAutoLinkNode = $createAutoLinkNode(
457 'https://example.com/foo',
460 rel: 'noopener noreferrer',
462 title: 'Hello world',
466 expect(autoLinkNode.__type).toEqual(createdAutoLinkNode.__type);
467 expect(autoLinkNode.__parent).toEqual(createdAutoLinkNode.__parent);
468 expect(autoLinkNode.__url).toEqual(createdAutoLinkNode.__url);
469 expect(autoLinkNode.__target).toEqual(createdAutoLinkNode.__target);
470 expect(autoLinkNode.__rel).toEqual(createdAutoLinkNode.__rel);
471 expect(autoLinkNode.__title).toEqual(createdAutoLinkNode.__title);
472 expect(autoLinkNode.__key).not.toEqual(createdAutoLinkNode.__key);
473 expect(autoLinkNode.__isUnlinked).not.toEqual(
474 createdAutoLinkNode.__isUnlinked,
479 test('$isAutoLinkNode()', async () => {
480 const {editor} = testEnv;
481 await editor.update(() => {
482 const autoLinkNode = new AutoLinkNode('');
483 expect($isAutoLinkNode(autoLinkNode)).toBe(true);
487 test('$toggleLink applies the title attribute when creating', async () => {
488 const {editor} = testEnv;
489 await editor.update(() => {
490 const p = new ParagraphNode();
491 p.append(new TextNode('Some text'));
492 $getRoot().append(p);
495 await editor.update(() => {
497 $toggleLink('https://lexical.dev/', {title: 'Lexical Website'});
500 const paragraph = editor!.getEditorState().toJSON().root
501 .children[0] as SerializedParagraphNode;
502 const link = paragraph.children[0] as SerializedAutoLinkNode;
503 expect(link.title).toBe('Lexical Website');