@@ -3,10 +3,12 @@ import { location } from "$lib/core/Location.js";
33import { describe , test , expect , beforeAll , afterAll , beforeEach , vi , afterEach } from "vitest" ;
44import { render , fireEvent } from "@testing-library/svelte" ;
55import Link from "./Link.svelte" ;
6- import { createRouterTestSetup , createTestSnippet , ROUTING_UNIVERSES , ALL_HASHES } from "../../testing/test-utils.js" ;
6+ import { createRouterTestSetup , createTestSnippet , ROUTING_UNIVERSES , ALL_HASHES , createWindowMock , setupBrowserMocks , type RoutingUniverse , addMatchingRoute } from "../../testing/test-utils.js" ;
77import { flushSync } from "svelte" ;
88import { resetRoutingOptions , setRoutingOptions } from "$lib/core/options.js" ;
99import type { ExtendedRoutingOptions } from "$lib/types.js" ;
10+ import { linkCtxKey , type ILinkContext } from "$lib/LinkContext/LinkContext.svelte" ;
11+ import { calculateHref } from "$lib/core/calculateHref.js" ;
1012
1113function basicLinkTests ( setup : ReturnType < typeof createRouterTestSetup > ) {
1214 const linkText = "Test Link" ;
@@ -192,7 +194,8 @@ function activeStateTests(setup: ReturnType<typeof createRouterTestSetup>) {
192194 props : {
193195 hash,
194196 href,
195- activeState : { key : activeKey , class : "active-link" } ,
197+ activeFor : activeKey ,
198+ activeState : { class : "active-link" } ,
196199 children : content
197200 } ,
198201 context
@@ -222,7 +225,8 @@ function activeStateTests(setup: ReturnType<typeof createRouterTestSetup>) {
222225 props : {
223226 hash,
224227 href,
225- activeState : { key : activeKey , style : "color: red;" } ,
228+ activeFor : activeKey ,
229+ activeState : { style : "color: red;" } ,
226230 children : content
227231 } ,
228232 context
@@ -252,8 +256,8 @@ function activeStateTests(setup: ReturnType<typeof createRouterTestSetup>) {
252256 props : {
253257 hash,
254258 href,
259+ activeFor : activeKey ,
255260 activeState : {
256- key : activeKey ,
257261 aria : {
258262 'aria-selected' : 'true' ,
259263 'aria-current' : 'page'
@@ -289,8 +293,8 @@ function activeStateTests(setup: ReturnType<typeof createRouterTestSetup>) {
289293 props : {
290294 hash,
291295 href,
296+ activeFor : activeKey ,
292297 activeState : {
293- key : activeKey ,
294298 aria : {
295299 'aria-selected' : 'true' ,
296300 'aria-current' : 'page'
@@ -326,7 +330,8 @@ function activeStateTests(setup: ReturnType<typeof createRouterTestSetup>) {
326330 props : {
327331 hash,
328332 href,
329- activeState : { key : activeKey , class : "active-link" } ,
333+ activeFor : activeKey ,
334+ activeState : { class : "active-link" } ,
330335 children : content
331336 } ,
332337 context
@@ -508,11 +513,17 @@ function reactivityTests(setup: ReturnType<typeof createRouterTestSetup>) {
508513 } ) ;
509514 }
510515
511- const initialActiveState = { key : activeKey , class : "initial-active" } ;
512- const updatedActiveState = { key : activeKey , class : "updated-active" } ;
516+ const initialActiveState = { class : "initial-active" } ;
517+ const updatedActiveState = { class : "updated-active" } ;
513518
514519 const { container, rerender } = render ( Link , {
515- props : { hash, href, activeState : initialActiveState , children : content } ,
520+ props : {
521+ hash,
522+ href,
523+ activeFor : activeKey ,
524+ activeState : initialActiveState ,
525+ children : content
526+ } ,
516527 context
517528 } ) ;
518529 const anchor = container . querySelector ( 'a' ) ;
@@ -546,7 +557,8 @@ function reactivityTests(setup: ReturnType<typeof createRouterTestSetup>) {
546557 props : {
547558 hash,
548559 href,
549- get activeState ( ) { return { key : activeKey , class : activeClass } ; } ,
560+ activeFor : activeKey ,
561+ get activeState ( ) { return { class : activeClass } ; } ,
550562 children : content
551563 } ,
552564 context
@@ -682,6 +694,125 @@ function reactivityTests(setup: ReturnType<typeof createRouterTestSetup>) {
682694 } ) ;
683695}
684696
697+ function linkContextTests ( ru : RoutingUniverse ) {
698+ let browserMocks : ReturnType < typeof setupBrowserMocks > ;
699+ let setup : ReturnType < typeof createRouterTestSetup > ;
700+
701+ beforeEach ( ( ) => {
702+ browserMocks = setupBrowserMocks ( 'http://example.com/' , location ) ;
703+ setup = createRouterTestSetup ( ru . hash ) ;
704+ setup . init ( ) ;
705+ } ) ;
706+
707+ afterAll ( ( ) => {
708+ setup . dispose ( ) ;
709+ } ) ;
710+
711+ test ( "Should prepend the parent router's base path when link context demands it." , ( ) => {
712+ // Arrange.
713+ const { router, context } = setup ;
714+ router . basePath = '/base' ;
715+ const linkCtx : ILinkContext = {
716+ prependBasePath : true ,
717+ } ;
718+ context . set ( linkCtxKey , linkCtx ) ;
719+
720+ // Act.
721+ const { container } = render ( Link , {
722+ props : {
723+ hash : ru . hash ,
724+ href : "/test" ,
725+ } ,
726+ context,
727+ } ) ;
728+
729+ // Assert.
730+ const anchor = container . querySelector ( 'a' ) ;
731+ expect ( anchor ?. getAttribute ( 'href' ) ) . toEqual ( calculateHref ( { hash : ru . hash } , '/base/test' ) ) ;
732+ } ) ;
733+
734+ test ( "Should preserve the query string when link context demands it." , ( ) => {
735+ // Arrange.
736+ const { router, context } = setup ;
737+ const queryString = '?a=1&b=2' ;
738+ browserMocks . setUrl ( `http://example.com/${ queryString } ` ) ;
739+ const linkCtx : ILinkContext = {
740+ preserveQuery : true ,
741+ } ;
742+ context . set ( linkCtxKey , linkCtx ) ;
743+
744+ // Act.
745+ const { container } = render ( Link , {
746+ props : {
747+ hash : ru . hash ,
748+ href : "/test" ,
749+ } ,
750+ context,
751+ } ) ;
752+
753+ // Assert.
754+ const anchor = container . querySelector ( 'a' ) ;
755+ expect ( anchor ?. getAttribute ( 'href' ) ) . toContain ( queryString ) ;
756+ } ) ;
757+
758+ test ( "Should apply activeState from link context when link context demands it." , ( ) => {
759+ // Arrange.
760+ const { router, context } = setup ;
761+ const linkCtx : ILinkContext = {
762+ activeState : {
763+ class : 'context-active' ,
764+ aria : { 'aria-current' : 'page' }
765+ }
766+ } ;
767+ const activeFor = 'test-route' ;
768+ addMatchingRoute ( router , { name : activeFor } ) ;
769+ context . set ( linkCtxKey , linkCtx ) ;
770+
771+ // Act.
772+ const { container } = render ( Link , {
773+ props : {
774+ hash : ru . hash ,
775+ href : "/test" ,
776+ activeFor,
777+ } ,
778+ context,
779+ } ) ;
780+
781+ // Assert.
782+ const anchor = container . querySelector ( 'a' ) ;
783+ expect ( anchor ?. className ) . toContain ( 'context-active' ) ;
784+ expect ( anchor ?. getAttribute ( 'aria-current' ) ) . toBe ( 'page' ) ;
785+ } ) ;
786+
787+ test . each < {
788+ replace : boolean ;
789+ fnName : 'pushState' | 'replaceState' ;
790+ } > ( [
791+ { replace : false , fnName : 'pushState' } ,
792+ { replace : true , fnName : 'replaceState' }
793+ ] ) ( "Should call $fnName when link context demands it." , async ( { replace, fnName } ) => {
794+ // Arrange.
795+ const { router, context } = setup ;
796+ const linkCtx : ILinkContext = {
797+ replace,
798+ } ;
799+ context . set ( linkCtxKey , linkCtx ) ;
800+ const { container } = render ( Link , {
801+ props : {
802+ hash : ru . hash ,
803+ href : "/test" ,
804+ } ,
805+ context,
806+ } ) ;
807+ const anchor = container . querySelector ( 'a' ) ;
808+
809+ // Act.
810+ await fireEvent . click ( anchor ! ) ;
811+
812+ expect ( browserMocks . history [ fnName ] ) . toHaveBeenCalledOnce ( ) ;
813+ } ) ;
814+ }
815+
685816describe ( "Routing Mode Assertions" , ( ) => {
686817 const linkText = "Test Link" ;
687818 const content = createTestSnippet ( linkText ) ;
@@ -736,7 +867,6 @@ describe("Routing Mode Assertions", () => {
736867 } ) ;
737868} ) ;
738869
739-
740870ROUTING_UNIVERSES . forEach ( ru => {
741871 describe ( `Link - ${ ru . text } ` , ( ) => {
742872 const setup = createRouterTestSetup ( ru . hash ) ;
@@ -774,3 +904,19 @@ ROUTING_UNIVERSES.forEach(ru => {
774904 } ) ;
775905 } ) ;
776906} ) ;
907+
908+ ROUTING_UNIVERSES . forEach ( ru => {
909+ describe ( `Link Context - ${ ru . text } ` , ( ) => {
910+ let cleanup : ( ) => void ;
911+ beforeAll ( ( ) => {
912+ cleanup = init ( {
913+ implicitMode : ru . implicitMode ,
914+ hashMode : ru . hashMode ,
915+ } ) ;
916+ } ) ;
917+ afterAll ( ( ) => {
918+ cleanup ?.( ) ;
919+ } ) ;
920+ linkContextTests ( ru ) ;
921+ } ) ;
922+ } ) ;
0 commit comments