1- import { beforeEach , describe , expect , it , vi } from 'vitest' ;
1+ import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest' ;
22import {
3+ clearNavigationContext ,
4+ getNavigationContext ,
35 getNormalizedName ,
46 getNumberOfUrlSegments ,
57 initializeRouterUtils ,
@@ -9,6 +11,7 @@ import {
911 prefixWithSlash ,
1012 rebuildRoutePathFromAllRoutes ,
1113 resolveRouteNameAndSource ,
14+ setNavigationContext ,
1215 transactionNameHasWildcard ,
1316} from '../../src/reactrouter-compat-utils' ;
1417import type { Location , MatchRoutes , RouteMatch , RouteObject } from '../../src/types' ;
@@ -664,4 +667,137 @@ describe('reactrouter-compat-utils/utils', () => {
664667 expect ( transactionNameHasWildcard ( '/path/to/asterisk' ) ) . toBe ( false ) ; // 'asterisk' contains 'isk' but not '*'
665668 } ) ;
666669 } ) ;
670+
671+ describe ( 'navigation context management' , ( ) => {
672+ // Clean up navigation context after each test by popping until empty
673+ afterEach ( ( ) => {
674+ // Pop all remaining contexts
675+ while ( getNavigationContext ( ) !== null ) {
676+ const ctx = getNavigationContext ( ) ;
677+ if ( ctx ) {
678+ clearNavigationContext ( ( ctx as any ) . token ) ;
679+ }
680+ }
681+ } ) ;
682+
683+ describe ( 'setNavigationContext' , ( ) => {
684+ it ( 'should return unique tokens (object identity)' , ( ) => {
685+ const token1 = setNavigationContext ( '/path1' , undefined ) ;
686+ const token2 = setNavigationContext ( '/path2' , undefined ) ;
687+ const token3 = setNavigationContext ( '/path3' , undefined ) ;
688+
689+ // Each token should be a unique object
690+ expect ( token1 ) . not . toBe ( token2 ) ;
691+ expect ( token2 ) . not . toBe ( token3 ) ;
692+ expect ( token1 ) . not . toBe ( token3 ) ;
693+ } ) ;
694+
695+ it ( 'should store targetPath and span in context' , ( ) => {
696+ const mockSpan = { name : 'test-span' } as any ;
697+ setNavigationContext ( '/test-path' , mockSpan ) ;
698+
699+ const context = getNavigationContext ( ) ;
700+ expect ( context ) . not . toBeNull ( ) ;
701+ expect ( context ?. targetPath ) . toBe ( '/test-path' ) ;
702+ expect ( context ?. span ) . toBe ( mockSpan ) ;
703+ } ) ;
704+
705+ it ( 'should handle undefined targetPath' , ( ) => {
706+ setNavigationContext ( undefined , undefined ) ;
707+
708+ const context = getNavigationContext ( ) ;
709+ expect ( context ) . not . toBeNull ( ) ;
710+ expect ( context ?. targetPath ) . toBeUndefined ( ) ;
711+ } ) ;
712+ } ) ;
713+
714+ describe ( 'clearNavigationContext' , ( ) => {
715+ it ( 'should remove context when token matches top of stack (LIFO)' , ( ) => {
716+ const token = setNavigationContext ( '/test' , undefined ) ;
717+
718+ expect ( getNavigationContext ( ) ) . not . toBeNull ( ) ;
719+
720+ clearNavigationContext ( token ) ;
721+
722+ expect ( getNavigationContext ( ) ) . toBeNull ( ) ;
723+ } ) ;
724+
725+ it ( 'should NOT remove context when token is not on top (out-of-order completion)' , ( ) => {
726+ // Simulate: Nav1 starts, Nav2 starts, Nav1 tries to complete first
727+ const token1 = setNavigationContext ( '/nav1' , undefined ) ;
728+ const token2 = setNavigationContext ( '/nav2' , undefined ) ;
729+
730+ // Most recent should be nav2
731+ expect ( getNavigationContext ( ) ?. targetPath ) . toBe ( '/nav2' ) ;
732+
733+ // Nav1 tries to complete first (out of order) - should NOT pop because nav1 is not on top
734+ clearNavigationContext ( token1 ) ;
735+
736+ // Nav2 should still be the current context (nav1's context is still buried)
737+ expect ( getNavigationContext ( ) ?. targetPath ) . toBe ( '/nav2' ) ;
738+
739+ // Nav2 completes - should pop because nav2 IS on top
740+ clearNavigationContext ( token2 ) ;
741+
742+ // Now nav1's stale context is on top (will be cleaned by overflow protection)
743+ expect ( getNavigationContext ( ) ?. targetPath ) . toBe ( '/nav1' ) ;
744+ } ) ;
745+
746+ it ( 'should not throw when clearing with unknown token' , ( ) => {
747+ const unknownToken = { } ;
748+ expect ( ( ) => clearNavigationContext ( unknownToken ) ) . not . toThrow ( ) ;
749+ } ) ;
750+
751+ it ( 'should correctly handle LIFO cleanup order' , ( ) => {
752+ const token1 = setNavigationContext ( '/path1' , undefined ) ;
753+ const token2 = setNavigationContext ( '/path2' , undefined ) ;
754+ const token3 = setNavigationContext ( '/path3' , undefined ) ;
755+
756+ // Clear in LIFO order
757+ clearNavigationContext ( token3 ) ;
758+ expect ( getNavigationContext ( ) ?. targetPath ) . toBe ( '/path2' ) ;
759+
760+ clearNavigationContext ( token2 ) ;
761+ expect ( getNavigationContext ( ) ?. targetPath ) . toBe ( '/path1' ) ;
762+
763+ clearNavigationContext ( token1 ) ;
764+ expect ( getNavigationContext ( ) ) . toBeNull ( ) ;
765+ } ) ;
766+ } ) ;
767+
768+ describe ( 'getNavigationContext' , ( ) => {
769+ it ( 'should return null when stack is empty' , ( ) => {
770+ expect ( getNavigationContext ( ) ) . toBeNull ( ) ;
771+ } ) ;
772+
773+ it ( 'should return the most recent context' , ( ) => {
774+ setNavigationContext ( '/first' , undefined ) ;
775+ setNavigationContext ( '/second' , undefined ) ;
776+ setNavigationContext ( '/third' , undefined ) ;
777+
778+ expect ( getNavigationContext ( ) ?. targetPath ) . toBe ( '/third' ) ;
779+ } ) ;
780+ } ) ;
781+
782+ describe ( 'stack overflow protection' , ( ) => {
783+ it ( 'should remove oldest context when stack exceeds limit' , ( ) => {
784+ // Push 12 contexts (limit is 10)
785+ const tokens : object [ ] = [ ] ;
786+ for ( let i = 0 ; i < 12 ; i ++ ) {
787+ tokens . push ( setNavigationContext ( `/path${ i } ` , undefined ) ) ;
788+ }
789+
790+ // Most recent should be /path11
791+ expect ( getNavigationContext ( ) ?. targetPath ) . toBe ( '/path11' ) ;
792+
793+ // The oldest contexts (path0, path1) were evicted due to overflow
794+ // Trying to clear them does nothing (their tokens no longer match anything)
795+ clearNavigationContext ( tokens [ 0 ] ! ) ;
796+ clearNavigationContext ( tokens [ 1 ] ! ) ;
797+
798+ // /path11 should still be current
799+ expect ( getNavigationContext ( ) ?. targetPath ) . toBe ( '/path11' ) ;
800+ } ) ;
801+ } ) ;
802+ } ) ;
667803} ) ;
0 commit comments