@@ -6,6 +6,11 @@ import {
66 gotoVisualRegression ,
77 setEditorHtml ,
88} from '../helpers/visual-regression' ;
9+ import {
10+ copyWholeContent ,
11+ pasteIntoWholeContent ,
12+ pastePlainTextIntoEditor ,
13+ } from '../helpers/clipboard' ;
914
1015test . setTimeout ( 90_000 ) ;
1116
@@ -27,6 +32,8 @@ const sel = {
2732 onLinkDetectedPayload : '[data-testid="on-link-detected-payload"]' ,
2833 editorInner : '[data-testid="test-links-editor"] .eti-editor' ,
2934 editorScreenshot : '[data-testid="test-links-editor"]' ,
35+ linkRegexMode : '[data-testid="test-links-link-regex-mode"]' ,
36+ linkRegexPattern : '[data-testid="test-links-link-regex-pattern"]' ,
3037} as const ;
3138
3239async function gotoTestLinks ( page : Page ) : Promise < void > {
@@ -352,3 +359,158 @@ test.describe('test-links onLinkDetected', () => {
352359 } ) ;
353360 } ) ;
354361} ) ;
362+
363+ test . describe ( 'test-links autolink' , ( ) => {
364+ async function resetEditorAndSetLinkRegexMode (
365+ page : Page ,
366+ mode : 'default' | 'disabled' | 'custom'
367+ ) : Promise < void > {
368+ await gotoTestLinks ( page ) ;
369+ await page . locator ( sel . linkRegexMode ) . selectOption ( mode ) ;
370+ await setTestLinksEditorHtml ( page , '<html><p></p></html>' ) ;
371+ }
372+
373+ test ( 'creates link while typing with default URL regex' , async ( { page } ) => {
374+ await resetEditorAndSetLinkRegexMode ( page , 'default' ) ;
375+
376+ const editor = page . locator ( sel . editorInner ) ;
377+ await editor . click ( ) ;
378+ await expect ( editor . locator ( '.ProseMirror' ) ) . toBeFocused ( ) ;
379+ await page . keyboard . type ( 'Visit https://example.com' ) ;
380+
381+ await expect
382+ . poll ( async ( ) => getTestLinksSerializedHtml ( page ) )
383+ . toContain ( '<a href="https://example.com">https://example.com</a>' ) ;
384+ } ) ;
385+
386+ test ( 'creates link while typing with custom regex' , async ( { page } ) => {
387+ await gotoTestLinks ( page ) ;
388+ await page . locator ( sel . linkRegexMode ) . selectOption ( 'custom' ) ;
389+ await page . fill ( sel . linkRegexPattern , String . raw `issue-\d+` ) ;
390+ await setTestLinksEditorHtml ( page , '<html><p></p></html>' ) ;
391+
392+ const editor = page . locator ( sel . editorInner ) ;
393+ await editor . click ( ) ;
394+ await expect ( editor . locator ( '.ProseMirror' ) ) . toBeFocused ( ) ;
395+ await page . keyboard . type ( 'tick issue-123 done' ) ;
396+
397+ await expect
398+ . poll ( async ( ) => getTestLinksSerializedHtml ( page ) )
399+ . toContain ( '<a href="issue-123">issue-123</a>' ) ;
400+ } ) ;
401+
402+ test ( 'creates link when pasting plain URL with default regex' , async ( {
403+ page,
404+ } ) => {
405+ await resetEditorAndSetLinkRegexMode ( page , 'default' ) ;
406+
407+ await pastePlainTextIntoEditor (
408+ page . locator ( sel . editorInner ) ,
409+ 'https://example.com'
410+ ) ;
411+
412+ await expect
413+ . poll ( async ( ) => getTestLinksSerializedHtml ( page ) )
414+ . toContain ( '<a href="https://example.com">https://example.com</a>' ) ;
415+ } ) ;
416+
417+ test ( 'does not autolink when link regex is disabled' , async ( { page } ) => {
418+ await resetEditorAndSetLinkRegexMode ( page , 'disabled' ) ;
419+
420+ const editor = page . locator ( sel . editorInner ) ;
421+ await editor . click ( ) ;
422+ await expect ( editor . locator ( '.ProseMirror' ) ) . toBeFocused ( ) ;
423+ await page . keyboard . type ( 'https://example.com' ) ;
424+
425+ await expect
426+ . poll ( async ( ) => getTestLinksSerializedHtml ( page ) )
427+ . not . toContain ( '<a href' ) ;
428+ } ) ;
429+ } ) ;
430+
431+ test . describe ( 'test-links copy-paste' , ( ) => {
432+ test . use ( { permissions : [ 'clipboard-read' , 'clipboard-write' ] } ) ;
433+
434+ test ( 'manual link (href ≠ text) survives copy-paste' , async ( { page } ) => {
435+ await gotoTestLinks ( page ) ;
436+ const editor = page . locator ( sel . editorInner ) ;
437+
438+ await setTestLinksEditorHtml (
439+ page ,
440+ '<html><p><a href="https://example.com">Click here</a></p></html>'
441+ ) ;
442+
443+ await copyWholeContent ( editor ) ;
444+ await setTestLinksEditorHtml ( page , '<html><p></p></html>' ) ;
445+ await pasteIntoWholeContent ( editor ) ;
446+
447+ await expect
448+ . poll ( async ( ) => getTestLinksSerializedHtml ( page ) )
449+ . toContain ( '<a href="https://example.com">Click here</a>' ) ;
450+ } ) ;
451+
452+ test ( 'autolink (href == text, matches regex) survives copy-paste' , async ( {
453+ page,
454+ } ) => {
455+ await gotoTestLinks ( page ) ;
456+ await page . locator ( sel . linkRegexMode ) . selectOption ( 'default' ) ;
457+ const editor = page . locator ( sel . editorInner ) ;
458+
459+ await setTestLinksEditorHtml (
460+ page ,
461+ '<html><p><a href="https://example.com">https://example.com</a></p></html>'
462+ ) ;
463+
464+ await copyWholeContent ( editor ) ;
465+ await setTestLinksEditorHtml ( page , '<html><p></p></html>' ) ;
466+ await pasteIntoWholeContent ( editor ) ;
467+
468+ await expect
469+ . poll ( async ( ) => getTestLinksSerializedHtml ( page ) )
470+ . toContain ( '<a href="https://example.com">https://example.com</a>' ) ;
471+ } ) ;
472+
473+ test ( 'manual link where href == text is not removed by autolink' , async ( {
474+ page,
475+ } ) => {
476+ await gotoTestLinks ( page ) ;
477+ await page . locator ( sel . linkRegexMode ) . selectOption ( 'disabled' ) ;
478+ const editor = page . locator ( sel . editorInner ) ;
479+
480+ await setTestLinksEditorHtml (
481+ page ,
482+ '<html><p><a href="custom://link">custom://link</a></p></html>'
483+ ) ;
484+
485+ await copyWholeContent ( editor ) ;
486+ await setTestLinksEditorHtml ( page , '<html><p></p></html>' ) ;
487+ await pasteIntoWholeContent ( editor ) ;
488+
489+ await expect
490+ . poll ( async ( ) => getTestLinksSerializedHtml ( page ) )
491+ . toContain ( '<a href="custom://link">custom://link</a>' ) ;
492+ } ) ;
493+ } ) ;
494+
495+ test . describe ( 'test-links manual link editing' , ( ) => {
496+ test ( 'typing inside a manual link keeps the link covering the typed text' , async ( {
497+ page,
498+ } ) => {
499+ await gotoTestLinks ( page ) ;
500+ await setTestLinksEditorHtml (
501+ page ,
502+ '<html><p><a href="https://example.com">Hello</a></p></html>'
503+ ) ;
504+
505+ await page . fill ( sel . selectionStart , '3' ) ;
506+ await page . fill ( sel . selectionEnd , '3' ) ;
507+ await page . click ( sel . applySelection ) ;
508+
509+ await page . waitForTimeout ( 100 ) ;
510+ await page . keyboard . type ( 'TEST' , { delay : 80 } ) ;
511+
512+ await expect
513+ . poll ( async ( ) => getTestLinksSerializedHtml ( page ) )
514+ . toContain ( '<a href="https://example.com">HelTESTlo</a>' ) ;
515+ } ) ;
516+ } ) ;
0 commit comments