@@ -199,19 +199,8 @@ beforeAll(() => {
199199Use data-driven testing across ** all 5 routing universes** :
200200
201201``` typescript
202- export const ROUTING_UNIVERSES: {
203- hash: Hash | undefined ;
204- implicitMode: RoutingOptions [' implicitMode' ];
205- hashMode: Exclude <RoutingOptions [' hashMode' ], undefined >;
206- text: string ;
207- name: string ;
208- }[] = [
209- { hash: undefined , implicitMode: ' path' , hashMode: ' single' , text: " IMP" , name: " Implicit Path Routing" },
210- { hash: undefined , implicitMode: ' hash' , hashMode: ' single' , text: " IMH" , name: " Implicit Hash Routing" },
211- { hash: false , implicitMode: ' path' , hashMode: ' single' , text: " PR" , name: " Path Routing" },
212- { hash: true , implicitMode: ' path' , hashMode: ' single' , text: " HR" , name: " Hash Routing" },
213- { hash: ' p1' , implicitMode: ' path' , hashMode: ' multi' , text: " MHR" , name: " Multi Hash Routing" },
214- ] as const ;
202+ // Import the complete universe definitions
203+ import { ROUTING_UNIVERSES } from " ../testing/test-utils.js" ;
215204
216205ROUTING_UNIVERSES .forEach ((ru ) => {
217206 describe (` Component - ${ru .text } ` , () => {
@@ -239,6 +228,8 @@ ROUTING_UNIVERSES.forEach((ru) => {
239228});
240229```
241230
231+ See ` src/testing/test-utils.ts ` for the complete ` ROUTING_UNIVERSES ` array definition with all universe configurations.
232+
242233### Context Setup
243234
244235``` typescript
@@ -299,10 +290,16 @@ addNonMatchingRoute(router, 'optionalRouteName');
299290// Add multiple routes at once
300291addRoutes (router , {
301292 matching: 2 , // Adds 2 matching routes
302- nonMatching: 1 , // Adds 1 non-matching route
303- ignoreForFallback: 1 // Adds 1 route that ignores fallback
293+ nonMatching: 1 // Adds 1 non-matching route
304294});
305295
296+ // Add explicit custom routes using rest parameters
297+ addRoutes (router ,
298+ { matching: 1 },
299+ { pattern: " /api/:id" , name: " api-route" },
300+ { regex: / ^ \/ test$ / } // Auto-generated name
301+ );
302+
306303// Manual route addition
307304router .routes [" routeName" ] = {
308305 pattern: " /some/path" ,
@@ -341,7 +338,7 @@ import {
341338 createRouterTestSetup ,
342339 createTestSnippet ,
343340 ROUTING_UNIVERSES
344- } from " $lib /testing/test-utils.js" ;
341+ } from " .. /testing/test-utils.js" ; // Note: moved outside $lib
345342```
346343
347344### Snippet Creation for Testing
@@ -452,6 +449,211 @@ afterAll(() => {
4524497 . ** Reactivity** : Remember to call ` flushSync() ` after changing reactive state
4534508 . ** Prop vs State Reactivity** : Test both prop changes AND reactive dependency changes
454451
452+ ## Advanced Testing Infrastructure
453+
454+ ### Browser API Mocking
455+
456+ For testing components that rely on ` window.location ` and ` window.history ` (like ` RouterEngine ` ), use the comprehensive browser mocking utilities:
457+
458+ ``` typescript
459+ import { setupBrowserMocks } from " ../testing/test-utils.js" ;
460+
461+ describe (" Component requiring browser APIs" , () => {
462+ beforeEach (() => {
463+ // Automatically mocks window.location, window.history, and integrates with library Location
464+ setupBrowserMocks (" /initial/path" );
465+ });
466+
467+ test (" Should respond to location changes" , () => {
468+ // Browser APIs are now mocked and integrated with library
469+ window .history .pushState ({}, " " , " /new/path" );
470+ // Test component behavior
471+ });
472+ });
473+ ```
474+
475+ ** What ` setupBrowserMocks() ` provides** :
476+ - Complete ` window.location ` mock with all properties (href, pathname, hash, search, etc.)
477+ - Full ` window.history ` mock with ` pushState ` , ` replaceState ` , and state management
478+ - Automatic ` popstate ` event triggering on location changes
479+ - Integration with library's ` LocationLite ` for synchronized state
480+ - Proper cleanup between tests
481+
482+ ### Enhanced Route Management
483+
484+ The ` addRoutes() ` utility supports multiple approaches for flexible route setup:
485+
486+ ``` typescript
487+ // Simple route counts
488+ addRoutes (router , { matching: 2 , nonMatching: 1 });
489+
490+ // RouteSpecs approach for custom route definitions
491+ addRoutes (router , {
492+ matching: { count: 2 , specs: { pattern: " /custom/:id" } },
493+ nonMatching: { count: 1 , specs: { pattern: " /other" } }
494+ });
495+
496+ // Rest parameters for explicit route definitions (NEW)
497+ addRoutes (router ,
498+ { matching: 1 , nonMatching: 0 },
499+ { pattern: " /api/users/:id" , name: " user-detail" },
500+ { regex: / ^ \/ products\/ \d + $ / , name: " product" },
501+ { pattern: " /settings" } // Name auto-generated if not provided
502+ );
503+
504+ // Combined approach
505+ addRoutes (router ,
506+ { matching: 2 }, // Generate 2 matching routes
507+ { pattern: " /custom" , name: " custom-route" }, // Add specific route
508+ { pattern: " /another" } // Add another with auto-generated name
509+ );
510+ ```
511+
512+ ** Rest Parameters Benefits:**
513+ - ** Explicit control** : Define exact routes with specific patterns/regex
514+ - ** Named routes** : Optional ` name ` property for predictable route keys
515+ - ** Type safety** : Full IntelliSense support for ` RouteInfo ` properties
516+ - ** Flexible mixing** : Combine generated routes with explicit definitions
517+
518+ Refer to ` src/testing/test-utils.ts ` for complete function signatures and type definitions.
519+
520+ ### Universe-Based Testing Pattern
521+
522+ ** Complete test coverage across all 5 routing universes** using the standardized pattern:
523+
524+ ``` typescript
525+ import { ROUTING_UNIVERSES } from " ../testing/test-utils.js" ;
526+
527+ // ✅ Recommended: Test ALL universes with single loop
528+ ROUTING_UNIVERSES .forEach ((universe ) => {
529+ describe (` Component (${universe .text }) ` , () => {
530+ let cleanup: () => void ;
531+ let setup: ReturnType <typeof createRouterTestSetup >;
532+
533+ beforeAll (() => {
534+ cleanup = init ({
535+ implicitMode: universe .implicitMode ,
536+ hashMode: universe .hashMode
537+ });
538+ setup = createRouterTestSetup (universe .hash );
539+ });
540+
541+ afterAll (() => {
542+ cleanup ();
543+ setup .dispose ();
544+ });
545+
546+ beforeEach (() => {
547+ setup .init (); // Fresh router per test
548+ setupBrowserMocks (" /" ); // Fresh browser state
549+ });
550+
551+ test (` Should behave correctly in ${universe .text } ` , () => {
552+ // Test logic that works across all universes
553+ const { hash, context, router } = setup ;
554+
555+ // Use universe.text for concise test descriptions
556+ expect (universe .text ).toMatch (/ ^ (IMP| IMH| PR| HR| MHR)$ / );
557+ });
558+ });
559+ });
560+ ```
561+
562+ ** Benefits** :
563+ - ** 100% Universe Coverage** : Ensures behavior works across all routing modes
564+ - ** Consistent Test Structure** : Standardized setup and teardown patterns
565+ - ** Efficient Execution** : Vitest's dynamic skipping capabilities maintain performance
566+ - ** Clear Reporting** : Each universe shows as separate test suite with meaningful names
567+
568+ ### Self-Documenting Test Constants
569+
570+ Use dictionary-based constants for better maintainability:
571+
572+ ``` typescript
573+ // Import self-documenting hash values
574+ import { ALL_HASHES } from " ../testing/test-utils.js" ;
575+
576+ // Usage in tests
577+ test (" Should validate hash compatibility" , () => {
578+ expect (() => {
579+ new RouterEngine ({ hash: ALL_HASHES .single });
580+ }).not .toThrow ();
581+ });
582+ ```
583+
584+ See ` src/testing/test-utils.ts ` for the complete ` ALL_HASHES ` dictionary definition.
585+
586+ ** Dictionary Benefits** :
587+ - ** Self-Documentation** : ` ALL_HASHES.single ` is clearer than ` true `
588+ - ** Single Source of Truth** : Change values in one place
589+ - ** Type Safety** : TypeScript can validate usage
590+ - ** Discoverability** : IDE autocomplete shows available options
591+
592+ ### Constructor Validation Testing
593+
594+ For components with runtime validation, test all error scenarios systematically:
595+
596+ ``` typescript
597+ describe (" Constructor hash validation" , () => {
598+ test .each ([
599+ { parent: ALL_HASHES .path , child: ALL_HASHES .single , desc: ' path parent vs hash child' },
600+ { parent: ALL_HASHES .single , child: ALL_HASHES .path , desc: ' hash parent vs path child' },
601+ { parent: ALL_HASHES .multi , child: ALL_HASHES .path , desc: ' multi-hash parent vs path child' },
602+ { parent: ALL_HASHES .path , child: ALL_HASHES .multi , desc: ' path parent vs multi-hash child' }
603+ ])(" Should throw error when parent and child have different hash modes: '$desc'" , ({ parent , child }) => {
604+ expect (() => {
605+ const parentRouter = new RouterEngine ({ hash: parent });
606+ new RouterEngine (parentRouter , { hash: child });
607+ }).toThrow (" Parent and child routers must use the same hash mode" );
608+ });
609+
610+ test .each ([
611+ { parent: ALL_HASHES .path , desc: ' path parent' },
612+ { parent: ALL_HASHES .single , desc: ' hash parent' },
613+ { parent: ALL_HASHES .multi , desc: ' multi-hash parent' }
614+ ])(" Should allow child router without explicit hash to inherit parent's hash: '$desc'" , ({ parent }) => {
615+ expect (() => {
616+ const parentRouter = new RouterEngine ({ hash: parent });
617+ new RouterEngine (parentRouter );
618+ }).not .toThrow ();
619+ });
620+ });
621+ ```
622+
623+ ### Performance Optimizations
624+
625+ ** Browser Mock State Synchronization** :
626+ ``` typescript
627+ // ✅ Automatic state sync - setupBrowserMocks handles this
628+ // Best practice: pass the library's location object for full integration
629+ setupBrowserMocks (" /initial" , libraryLocationObject );
630+ window .history .pushState ({}, " " , " /new" ); // Automatically triggers popstate
631+
632+ // ❌ Manual sync required (old approach)
633+ mockLocation .pathname = " /new" ;
634+ window .dispatchEvent (new PopStateEvent (' popstate' )); // Manual event trigger
635+ ```
636+
637+ ** Efficient Test Assertions** :
638+ ``` typescript
639+ // ✅ Fast negative assertions
640+ expect (queryByText (" should not exist" )).toBeNull ();
641+
642+ // ❌ Slow - waits for timeout
643+ await expect (findByText (" should not exist" )).rejects .toThrow ();
644+
645+ // ✅ Use findByText for elements that should exist
646+ const element = await findByText (" should exist" );
647+ expect (element ).toBeInTheDocument ();
648+ ```
649+
455650## Test Utilities Location
456651
457- Test utilities are located in ` src/lib/testing/ ` and excluded from the published package via the ` "files" ` property in ` package.json ` . During development, they build to ` dist/testing/ ` but are not included in ` npm pack ` .
652+ Test utilities are centralized in ` src/testing/test-utils.ts ` (moved from ` src/lib/testing/ ` for better organization) and excluded from the published package via the ` "files" ` property in ` package.json ` . During development, they build to ` dist/testing/ ` but are not included in ` npm pack ` .
653+
654+ ** Key utilities** :
655+ - ` setupBrowserMocks() ` : Complete browser API mocking with library integration
656+ - ` addRoutes() ` : Enhanced route management with RouteSpecs support
657+ - ` createRouterTestSetup() ` : Standardized router setup with proper lifecycle
658+ - ` ROUTING_UNIVERSES ` : Complete universe definitions for comprehensive testing
659+ - ` ALL_HASHES ` : Self-documenting hash value constants
0 commit comments