@@ -7,6 +7,24 @@ const HERE = path.dirname(fileURLToPath(import.meta.url));
77const PACKAGE_ROOT = path . resolve ( HERE , "../.." ) ;
88const SRC_ROOT = path . join ( PACKAGE_ROOT , "src" ) ;
99const APP_SOURCE = path . join ( SRC_ROOT , "App.tsx" ) ;
10+ const CLOUD_ROUTES_SPEC = path . join ( HERE , "cloud-routes.spec.ts" ) ;
11+
12+ const ROUTE_PARAM_EXAMPLES : Record < string , string > = {
13+ ":approvalId" : "approval_1" ,
14+ ":appId" : "app_1" ,
15+ ":ballotId" : "ballot_1" ,
16+ ":characterRef" : "agent_1" ,
17+ ":chargeId" : "charge_1" ,
18+ ":id" : "agent_1" ,
19+ ":paymentRequestId" : "pay_req_1" ,
20+ } ;
21+
22+ const ROUTE_SAMPLE_OVERRIDES : Record < string , string > = {
23+ "/dashboard/apps/:id" : "/dashboard/apps/app_1" ,
24+ "/dashboard/containers/:id" : "/dashboard/containers/container_1" ,
25+ "/dashboard/containers/agents/:id" : "/dashboard/containers/agents/agent_1" ,
26+ "/dashboard/invoices/:id" : "/dashboard/invoices/inv_1" ,
27+ } ;
1028
1129function walkPageComponents ( dir : string ) : string [ ] {
1230 const files : string [ ] = [ ] ;
@@ -45,6 +63,58 @@ function lazyRouteImports(appSource: string): Set<string> {
4563 ) ;
4664}
4765
66+ function normalizePath ( pathValue : string ) : string {
67+ return pathValue . replace ( / \/ + / g, "/" ) . replace ( / \/ $ / , "" ) || "/" ;
68+ }
69+
70+ function routePatternToSample ( routePattern : string ) : string | null {
71+ if ( routePattern . includes ( "*" ) ) return null ;
72+ const override = ROUTE_SAMPLE_OVERRIDES [ routePattern ] ;
73+ if ( override ) return override ;
74+ const segments = routePattern . split ( "/" ) . map ( ( segment ) => {
75+ if ( ! segment . startsWith ( ":" ) ) return segment ;
76+ return ROUTE_PARAM_EXAMPLES [ segment ] ?? `${ segment . slice ( 1 ) } _1` ;
77+ } ) ;
78+ return normalizePath ( segments . join ( "/" ) ) ;
79+ }
80+
81+ function routerRouteSamples ( appSource : string ) : string [ ] {
82+ const routeStack : string [ ] = [ ] ;
83+ const samples = new Set < string > ( ) ;
84+
85+ for ( const line of appSource . split ( "\n" ) ) {
86+ const routeMatch = line . match ( / < R o u t e (?: \s + [ ^ > ] * ) ? > / ) ;
87+ if ( routeMatch ) {
88+ const pathMatch = line . match ( / p a t h = " ( [ ^ " ] + ) " / ) ;
89+ const indexRoute = / \s i n d e x (?: \s | > | $ ) / . test ( line ) ;
90+ if ( pathMatch || indexRoute ) {
91+ const parentPath = routeStack . at ( - 1 ) ?? "" ;
92+ const ownPath = indexRoute ? "" : ( pathMatch ?. [ 1 ] ?? "" ) ;
93+ const fullPath = normalizePath ( `${ parentPath } /${ ownPath } ` ) ;
94+ const sample = routePatternToSample ( fullPath ) ;
95+ if ( sample ) samples . add ( sample ) ;
96+ if ( ! / \/ > \s * $ / . test ( line ) ) routeStack . push ( fullPath ) ;
97+ continue ;
98+ }
99+ }
100+
101+ if ( line . includes ( "</Route>" ) ) {
102+ routeStack . pop ( ) ;
103+ }
104+ }
105+
106+ return [ ...samples ] . sort ( ) ;
107+ }
108+
109+ function smokeRouteSamplesFromSpec ( specSource : string ) : Set < string > {
110+ return new Set (
111+ [ ...specSource . matchAll ( / [ " ' ] ( \/ [ ^ " ' ] * ) [ " ' ] / g) ]
112+ . map ( ( match ) => match [ 1 ] ?? "" )
113+ . filter ( ( route ) => ! route . includes ( ":" ) )
114+ . map ( ( route ) => normalizePath ( route . split ( "?" ) [ 0 ] ?? route ) ) ,
115+ ) ;
116+ }
117+
48118test ( "every cloud page component is reachable from the router" , async ( ) => {
49119 const appSource = readFileSync ( APP_SOURCE , "utf8" ) ;
50120 const routeImports = lazyRouteImports ( appSource ) ;
@@ -67,3 +137,18 @@ test("every cloud page component is reachable from the router", async () => {
67137 . join ( "\n" ) } `,
68138 ) . toEqual ( [ ] ) ;
69139} ) ;
140+
141+ test ( "cloud route smoke covers every concrete router path" , ( ) => {
142+ const appSource = readFileSync ( APP_SOURCE , "utf8" ) ;
143+ const smokeSource = readFileSync ( CLOUD_ROUTES_SPEC , "utf8" ) ;
144+ const smokeRoutes = smokeRouteSamplesFromSpec ( smokeSource ) ;
145+
146+ const missing = routerRouteSamples ( appSource ) . filter (
147+ ( route ) => ! smokeRoutes . has ( route ) ,
148+ ) ;
149+
150+ expect (
151+ missing ,
152+ `Missing cloud route smoke coverage for: ${ missing . join ( ", " ) } ` ,
153+ ) . toEqual ( [ ] ) ;
154+ } ) ;
0 commit comments