@@ -19,6 +19,7 @@ import {
1919 $createLineBreakNode ,
2020 $createParagraphNode ,
2121 $createTextNode ,
22+ $getNodeByKey ,
2223 $getRoot ,
2324 $getSelection ,
2425 $isLineBreakNode ,
@@ -582,6 +583,190 @@ describe('LexicalLinkNode tests', () => {
582583 expect ( $isLineBreakNode ( lineBreakNode ) ) . toBe ( true ) ;
583584 } ) ;
584585 } ) ;
586+
587+ test ( '$toggleLink removes link from trailing space only' , async ( ) => {
588+ const { editor} = testEnv ;
589+ let textNodeKey : string ;
590+
591+ // Create a link with text and a trailing space
592+ await editor . update ( ( ) => {
593+ const paragraph = $createParagraphNode ( ) ;
594+ const textNode = $createTextNode ( 'hello ' ) ;
595+ textNodeKey = textNode . getKey ( ) ;
596+ const linkNode = $createLinkNode ( 'https://example.com' ) ;
597+ linkNode . append ( textNode ) ;
598+ paragraph . append ( linkNode ) ;
599+ $getRoot ( ) . clear ( ) . append ( paragraph ) ;
600+ } ) ;
601+
602+ // Verify initial structure
603+ editor . read ( ( ) => {
604+ const paragraph = $getRoot ( ) . getFirstChild ( ) as ParagraphNode ;
605+ const linkNode = paragraph . getFirstChild ( ) ;
606+ expect ( $isLinkNode ( linkNode ) ) . toBe ( true ) ;
607+ if ( $isLinkNode ( linkNode ) ) {
608+ expect ( linkNode . getTextContent ( ) ) . toBe ( 'hello ' ) ;
609+ }
610+ } ) ;
611+
612+ // Select only the trailing space and remove the link from it
613+ await editor . update ( ( ) => {
614+ const textNode = $getNodeByKey ( textNodeKey ) ;
615+ if ( $isTextNode ( textNode ) ) {
616+ textNode . select ( 5 , 6 ) ; // Select the trailing space
617+ $toggleLink ( null ) ;
618+ }
619+ } ) ;
620+
621+ // Verify that the link was removed only from the space
622+ editor . read ( ( ) => {
623+ const paragraph = $getRoot ( ) . getFirstChild ( ) as ParagraphNode ;
624+ const children = paragraph . getChildren ( ) ;
625+
626+ // Should have two children: linkNode with 'hello' and a text node with ' '
627+ expect ( children . length ) . toBe ( 2 ) ;
628+
629+ const [ linkNode , spaceNode ] = children ;
630+
631+ // First child should still be a link containing 'hello'
632+ expect ( $isLinkNode ( linkNode ) ) . toBe ( true ) ;
633+ if ( $isLinkNode ( linkNode ) ) {
634+ expect ( linkNode . getTextContent ( ) ) . toBe ( 'hello' ) ;
635+ expect ( linkNode . getURL ( ) ) . toBe ( 'https://example.com' ) ;
636+ }
637+
638+ // Second child should be a text node with just the space
639+ expect ( $isTextNode ( spaceNode ) ) . toBe ( true ) ;
640+ if ( $isTextNode ( spaceNode ) ) {
641+ expect ( spaceNode . getTextContent ( ) ) . toBe ( ' ' ) ;
642+ }
643+ } ) ;
644+ } ) ;
645+
646+ test ( '$toggleLink removes link from leading space only' , async ( ) => {
647+ const { editor} = testEnv ;
648+ let textNodeKey : string ;
649+
650+ // Create a link with a leading space and text
651+ await editor . update ( ( ) => {
652+ const paragraph = $createParagraphNode ( ) ;
653+ const textNode = $createTextNode ( ' hello' ) ;
654+ textNodeKey = textNode . getKey ( ) ;
655+ const linkNode = $createLinkNode ( 'https://example.com' ) ;
656+ linkNode . append ( textNode ) ;
657+ paragraph . append ( linkNode ) ;
658+ $getRoot ( ) . clear ( ) . append ( paragraph ) ;
659+ } ) ;
660+
661+ // Verify initial structure
662+ editor . read ( ( ) => {
663+ const paragraph = $getRoot ( ) . getFirstChild ( ) as ParagraphNode ;
664+ const linkNode = paragraph . getFirstChild ( ) ;
665+ expect ( $isLinkNode ( linkNode ) ) . toBe ( true ) ;
666+ if ( $isLinkNode ( linkNode ) ) {
667+ expect ( linkNode . getTextContent ( ) ) . toBe ( ' hello' ) ;
668+ }
669+ } ) ;
670+
671+ // Select only the leading space and remove the link from it
672+ await editor . update ( ( ) => {
673+ const textNode = $getNodeByKey ( textNodeKey ) ;
674+ if ( $isTextNode ( textNode ) ) {
675+ textNode . select ( 0 , 1 ) ; // Select the leading space
676+ $toggleLink ( null ) ;
677+ }
678+ } ) ;
679+
680+ // Verify that the link was removed only from the space
681+ editor . read ( ( ) => {
682+ const paragraph = $getRoot ( ) . getFirstChild ( ) as ParagraphNode ;
683+ const children = paragraph . getChildren ( ) ;
684+
685+ // Should have two children: a text node with ' ' and linkNode with 'hello'
686+ expect ( children . length ) . toBe ( 2 ) ;
687+
688+ const [ spaceNode , linkNode ] = children ;
689+
690+ // First child should be a text node with just the space
691+ expect ( $isTextNode ( spaceNode ) ) . toBe ( true ) ;
692+ if ( $isTextNode ( spaceNode ) ) {
693+ expect ( spaceNode . getTextContent ( ) ) . toBe ( ' ' ) ;
694+ }
695+
696+ // Second child should still be a link containing 'hello'
697+ expect ( $isLinkNode ( linkNode ) ) . toBe ( true ) ;
698+ if ( $isLinkNode ( linkNode ) ) {
699+ expect ( linkNode . getTextContent ( ) ) . toBe ( 'hello' ) ;
700+ expect ( linkNode . getURL ( ) ) . toBe ( 'https://example.com' ) ;
701+ }
702+ } ) ;
703+ } ) ;
704+
705+ test ( '$toggleLink removes link from middle word only, preserving surrounding spaces' , async ( ) => {
706+ const { editor} = testEnv ;
707+ let textNodeKey : string ;
708+
709+ // Create a link with leading space, text, and trailing space
710+ await editor . update ( ( ) => {
711+ const paragraph = $createParagraphNode ( ) ;
712+ const textNode = $createTextNode ( ' hello ' ) ;
713+ textNodeKey = textNode . getKey ( ) ;
714+ const linkNode = $createLinkNode ( 'https://example.com' ) ;
715+ linkNode . append ( textNode ) ;
716+ paragraph . append ( linkNode ) ;
717+ $getRoot ( ) . clear ( ) . append ( paragraph ) ;
718+ } ) ;
719+
720+ // Verify initial structure
721+ editor . read ( ( ) => {
722+ const paragraph = $getRoot ( ) . getFirstChild ( ) as ParagraphNode ;
723+ const linkNode = paragraph . getFirstChild ( ) ;
724+ expect ( $isLinkNode ( linkNode ) ) . toBe ( true ) ;
725+ if ( $isLinkNode ( linkNode ) ) {
726+ expect ( linkNode . getTextContent ( ) ) . toBe ( ' hello ' ) ;
727+ }
728+ } ) ;
729+
730+ // Select only 'hello' (without spaces) and remove the link from it
731+ await editor . update ( ( ) => {
732+ const textNode = $getNodeByKey ( textNodeKey ) ;
733+ if ( $isTextNode ( textNode ) ) {
734+ textNode . select ( 1 , 6 ) ; // Select 'hello'
735+ $toggleLink ( null ) ;
736+ }
737+ } ) ;
738+
739+ // Verify that the link was removed only from 'hello'
740+ editor . read ( ( ) => {
741+ const paragraph = $getRoot ( ) . getFirstChild ( ) as ParagraphNode ;
742+ const children = paragraph . getChildren ( ) ;
743+
744+ // Should have three children: link with ' ', text with 'hello', link with ' '
745+ expect ( children . length ) . toBe ( 3 ) ;
746+
747+ const [ leadingSpaceLink , middleText , trailingSpaceLink ] = children ;
748+
749+ // First child should be a link with leading space
750+ expect ( $isLinkNode ( leadingSpaceLink ) ) . toBe ( true ) ;
751+ if ( $isLinkNode ( leadingSpaceLink ) ) {
752+ expect ( leadingSpaceLink . getTextContent ( ) ) . toBe ( ' ' ) ;
753+ expect ( leadingSpaceLink . getURL ( ) ) . toBe ( 'https://example.com' ) ;
754+ }
755+
756+ // Middle child should be plain text 'hello'
757+ expect ( $isTextNode ( middleText ) ) . toBe ( true ) ;
758+ if ( $isTextNode ( middleText ) ) {
759+ expect ( middleText . getTextContent ( ) ) . toBe ( 'hello' ) ;
760+ }
761+
762+ // Third child should be a link with trailing space
763+ expect ( $isLinkNode ( trailingSpaceLink ) ) . toBe ( true ) ;
764+ if ( $isLinkNode ( trailingSpaceLink ) ) {
765+ expect ( trailingSpaceLink . getTextContent ( ) ) . toBe ( ' ' ) ;
766+ expect ( trailingSpaceLink . getURL ( ) ) . toBe ( 'https://example.com' ) ;
767+ }
768+ } ) ;
769+ } ) ;
585770 } ) ;
586771} ) ;
587772
0 commit comments