Skip to content

Commit 9161c5d

Browse files
fix: Propagate public visibility to nested children in registerPublicRoute (#608)
* fix: Propagate public visibility to nested children in registerPublicRoute Fix issue #607 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: simplify applyPublicVisibility helper Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: rename applyPublicVisibility to applyPublicVisibilityToChildren Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1e89509 commit 9161c5d

3 files changed

Lines changed: 161 additions & 1 deletion

File tree

.changeset/cozy-routes-inherit.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@squide/react-router": patch
3+
"@squide/firefly": patch
4+
---
5+
6+
Fixed `registerPublicRoute` to propagate the public visibility to nested children. Children with an explicit visibility option are preserved.

packages/react-router/src/ReactRouterRuntime.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,22 @@ function logRoutesTree(routes: Route[], depth: number = 0) {
3434
return log;
3535
}
3636

37+
function applyPublicVisibilityToChildren(routes: Route[]) {
38+
return routes.map(x => {
39+
const route: Route = {
40+
$visibility: "public",
41+
...x
42+
};
43+
44+
if (route.children) {
45+
// Recursively go through the children.
46+
route.children = applyPublicVisibilityToChildren(route.children);
47+
}
48+
49+
return route;
50+
});
51+
}
52+
3753
export interface IReactRouterRuntime extends IRuntime<Route, RootNavigationItem> {}
3854

3955
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -163,7 +179,8 @@ export class ReactRouterRuntime<TRuntime extends ReactRouterRuntime = any> exten
163179
registerPublicRoute(route: Omit<Route, "$visibility">, options?: RegisterRouteOptions) {
164180
this.registerRoute({
165181
$visibility: "public",
166-
...route
182+
...route,
183+
...(route.children ? { children: applyPublicVisibilityToChildren(route.children) } : {})
167184
} as Route, options);
168185
}
169186

packages/react-router/tests/ReactRouterRuntime.test.tsx

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2517,6 +2517,143 @@ describe.concurrent("startDeferredRegistrationScope & completeDeferredRegistrati
25172517
});
25182518
});
25192519

2520+
describe.concurrent("registerPublicRoute", () => {
2521+
function registerPublicRoutesOutlet(runtime: ReactRouterRuntime) {
2522+
runtime.registerRoute(PublicRoutes);
2523+
}
2524+
2525+
function getPublicRoutes(routes: Route[]): Route[] | undefined {
2526+
for (const route of routes) {
2527+
if (isPublicRoutesOutletRoute(route)) {
2528+
return route.children!;
2529+
}
2530+
2531+
if (route.children) {
2532+
const publicRoutes = getPublicRoutes(route.children);
2533+
2534+
if (publicRoutes) {
2535+
return publicRoutes;
2536+
}
2537+
}
2538+
}
2539+
}
2540+
2541+
test.concurrent("should register a flat public route", ({ expect }) => {
2542+
const runtime = new ReactRouterRuntime({
2543+
loggers: [new NoopLogger()]
2544+
});
2545+
2546+
registerPublicRoutesOutlet(runtime);
2547+
2548+
runtime.registerPublicRoute({
2549+
path: "/foo",
2550+
element: <div>Hello!</div>
2551+
});
2552+
2553+
const routes = getPublicRoutes(runtime.routes)!;
2554+
2555+
expect(routes.length).toBe(1);
2556+
expect(routes[0].path).toBe("/foo");
2557+
expect(routes[0].$visibility).toBe("public");
2558+
});
2559+
2560+
test.concurrent("when a child route has no visibility option, the child route is considered as a \"public\" route", ({ expect }) => {
2561+
const runtime = new ReactRouterRuntime({
2562+
loggers: [new NoopLogger()]
2563+
});
2564+
2565+
registerPublicRoutesOutlet(runtime);
2566+
2567+
runtime.registerPublicRoute({
2568+
element: <div>Layout</div>,
2569+
children: [
2570+
{
2571+
path: "/foo",
2572+
element: <div>Foo</div>
2573+
},
2574+
{
2575+
path: "/bar",
2576+
element: <div>Bar</div>
2577+
}
2578+
]
2579+
});
2580+
2581+
const routes = getPublicRoutes(runtime.routes)!;
2582+
2583+
expect(routes[0].$visibility).toBe("public");
2584+
expect(routes[0].children![0].$visibility).toBe("public");
2585+
expect(routes[0].children![1].$visibility).toBe("public");
2586+
});
2587+
2588+
test.concurrent("should register a child route with an explicit visibility", ({ expect }) => {
2589+
const runtime = new ReactRouterRuntime({
2590+
loggers: [new NoopLogger()]
2591+
});
2592+
2593+
registerPublicRoutesOutlet(runtime);
2594+
2595+
runtime.registerPublicRoute({
2596+
element: <div>Layout</div>,
2597+
children: [
2598+
{
2599+
$visibility: "protected",
2600+
path: "/protected-child",
2601+
element: <div>Protected</div>
2602+
},
2603+
{
2604+
path: "/public-child",
2605+
element: <div>Public</div>
2606+
}
2607+
]
2608+
});
2609+
2610+
const routes = getPublicRoutes(runtime.routes)!;
2611+
2612+
expect(routes[0].children![0].$visibility).toBe("protected");
2613+
expect(routes[0].children![1].$visibility).toBe("public");
2614+
});
2615+
2616+
test.concurrent("when a deeply nested route has no visibility option, the deeply nested route is considered as a \"public\" route", ({ expect }) => {
2617+
const runtime = new ReactRouterRuntime({
2618+
loggers: [new NoopLogger()]
2619+
});
2620+
2621+
registerPublicRoutesOutlet(runtime);
2622+
2623+
runtime.registerPublicRoute({
2624+
element: <div>Root</div>,
2625+
children: [
2626+
{
2627+
element: <div>Layout</div>,
2628+
children: [
2629+
{
2630+
path: "/reviews",
2631+
children: [
2632+
{
2633+
index: true,
2634+
element: <div>Index</div>
2635+
}
2636+
]
2637+
},
2638+
{
2639+
path: "/reviews/auth-redirect",
2640+
element: <div>Auth</div>
2641+
}
2642+
]
2643+
}
2644+
]
2645+
});
2646+
2647+
const routes = getPublicRoutes(runtime.routes)!;
2648+
2649+
expect(routes[0].$visibility).toBe("public");
2650+
expect(routes[0].children![0].$visibility).toBe("public");
2651+
expect(routes[0].children![0].children![0].$visibility).toBe("public");
2652+
expect(routes[0].children![0].children![0].children![0].$visibility).toBe("public");
2653+
expect(routes[0].children![0].children![1].$visibility).toBe("public");
2654+
});
2655+
});
2656+
25202657
describe.concurrent("_validateRegistrations", () => {
25212658
describe.concurrent("managed routes", () => {
25222659
test.concurrent("when public routes are registered but the public routes outlet is missing, the error message mentions the PublicRoutes outlet", ({ expect }) => {

0 commit comments

Comments
 (0)