Skip to content

Commit

Permalink
refactor(file-router): make route with non-null children detected as …
Browse files Browse the repository at this point in the history
…a layout (#2493)
  • Loading branch information
Lodin authored May 31, 2024
1 parent 81d9a93 commit 9f087d1
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,6 @@
*/
package com.vaadin.hilla.route;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.server.frontend.FrontendUtils;
import com.vaadin.hilla.route.records.ClientViewConfig;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import com.vaadin.flow.router.internal.ClientRoutesProvider;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
Expand All @@ -36,10 +24,23 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.router.internal.ClientRoutesProvider;
import com.vaadin.flow.server.frontend.FrontendUtils;
import com.vaadin.hilla.route.records.ClientViewConfig;

/**
* Keeps track of registered client side routes.
*/
Expand Down Expand Up @@ -228,10 +229,14 @@ private void registerAndRecurseChildren(String basePath,
var path = view.getRoute() == null || view.getRoute().isEmpty()
? basePath
: basePath + '/' + view.getRoute();

if (!hasMainLayout && isMainLayout(view)) {
hasMainLayout = true;
}
if (view.getChildren() == null || view.getChildren().isEmpty()) {

// Skip layout views without children.
// https://github.com/vaadin/hilla/issues/2379
if (view.getChildren() == null) {
addRoute(path, view);
} else {
view.getChildren().forEach(child -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,21 +111,21 @@ public void setUp() throws Exception {
}

private Map<String, ClientViewConfig> prepareClientRoutes() {
final Map<String, ClientViewConfig> routes = new LinkedHashMap<>();
final var routes = new LinkedHashMap<String, ClientViewConfig>();

ClientViewConfig homeConfig = new ClientViewConfig();
var homeConfig = new ClientViewConfig();
homeConfig.setTitle("Home");
homeConfig.setRolesAllowed(null);
homeConfig.setLoginRequired(false);
homeConfig.setRoute("/home");
homeConfig.setLazy(false);
homeConfig.setAutoRegistered(false);
homeConfig.setMenu(null);
homeConfig.setChildren(Collections.emptyList());
homeConfig.setChildren(null);
homeConfig.setRouteParameters(Collections.emptyMap());
routes.put("/home", homeConfig);

ClientViewConfig profileConfig = new ClientViewConfig();
var profileConfig = new ClientViewConfig();
profileConfig.setTitle("Profile");
profileConfig
.setRolesAllowed(new String[] { "ROLE_USER", "ROLE_ADMIN" });
Expand All @@ -134,19 +134,19 @@ private Map<String, ClientViewConfig> prepareClientRoutes() {
profileConfig.setLazy(false);
profileConfig.setAutoRegistered(false);
profileConfig.setMenu(null);
profileConfig.setChildren(Collections.emptyList());
profileConfig.setChildren(null);
profileConfig.setRouteParameters(Collections.emptyMap());
routes.put("/profile", profileConfig);

ClientViewConfig userProfileConfig = new ClientViewConfig();
var userProfileConfig = new ClientViewConfig();
userProfileConfig.setTitle("User Profile");
userProfileConfig.setRolesAllowed(new String[] { "ROLE_ADMIN" });
userProfileConfig.setLoginRequired(true);
userProfileConfig.setRoute("/user/:userId");
userProfileConfig.setLazy(false);
userProfileConfig.setAutoRegistered(false);
userProfileConfig.setMenu(null);
userProfileConfig.setChildren(Collections.emptyList());
userProfileConfig.setChildren(null);
userProfileConfig
.setRouteParameters(Map.of(":userId", RouteParamType.REQUIRED));
routes.put("/user/:userId", userProfileConfig);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
{
"route": "about",
"title": "About",
"params": {},
"children": []
"params": {}
},
{
"route": "profile",
Expand All @@ -12,8 +11,7 @@
{
"route": "",
"title": "Profile",
"params": {},
"children": []
"params": {}
},
{
"route": "account",
Expand All @@ -27,14 +25,12 @@
{
"route": "password",
"params": {},
"title": "Password",
"children": []
"title": "Password"
},
{
"route": "two-factor-auth",
"params": {},
"title": "Two Factor Auth",
"children": []
"title": "Two Factor Auth"
}
]
}
Expand All @@ -51,8 +47,7 @@
"params": {},
"unknown": {
"anotherProp": "prop"
},
"children": []
}
},
{
"route": ":user?",
Expand All @@ -71,8 +66,7 @@
"title": "Friend Profile",
"params": {
":user": "req"
},
"children": []
}
}
]
},
Expand All @@ -96,31 +90,33 @@
"children": [
{
"route": "empty",
"params": {},
"children": []
"params": {}
},
{
"route": ":optional?",
"title": "Optional",
"params": {
":optional?": "opt"
},
"children": []
}
},
{
"route": "*",
"title": "Wildcard",
"params": {
"*": "*"
},
"children": []
}
},
{
"route": "no-default-export",
"title": "No Default Export",
"params": {},
"children": []
"params": {}
}
]
},
{
"route": "layout-only",
"title": "Layout Only",
"params": {},
"children": []
}
]
1 change: 1 addition & 0 deletions packages/ts/file-router/src/shared/internal.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ViewConfig } from '../types.js';
import type { RouteParamType } from './routeParamType.js';

/**
* Internal type used for server communication and menu building. It extends the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export default async function createViewConfigJson(views: readonly RouteMeta[]):
...config,
params: extractParameterFromRouteSegment(config?.route ?? path),
title,
children: newChildren,
children: newChildren ?? (layout ? [] : undefined),
} satisfies ServerViewConfig;
}),
),
Expand Down
11 changes: 10 additions & 1 deletion packages/ts/file-router/test/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ import type { Logger } from 'vite';
import type { RouteModule } from '../src/types.js';
import type { RouteMeta } from '../src/vite-plugin/collectRoutesFromFS.js';

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Chai {
interface PromisedAssertion {
like(expected: unknown): Promise<void>;
}
}
}

export async function createTmpDir(): Promise<URL> {
return pathToFileURL(`${await mkdtemp(join(tmpdir(), 'file-router-'))}/`);
}
Expand Down Expand Up @@ -149,7 +158,7 @@ export function createTestingRouteMeta(dir: URL): readonly RouteMeta[] {
{
path: 'test',
children: [
// Ignored route (that has the name `_ignored.tsx` is not included in the route meta.
// Ignored route that has the name `_ignored.tsx` is not included in the route meta.
// Also empty files or files without default export are not included.
{
path: '{{optional}}',
Expand Down
121 changes: 71 additions & 50 deletions packages/ts/file-router/test/vite-plugin/createViewConfigJson.spec.ts
Original file line number Diff line number Diff line change
@@ -1,80 +1,101 @@
import { rm } from 'node:fs/promises';
import { appendFile, mkdir, rm } from 'node:fs/promises';
import { expect, use } from '@esm-bundle/chai';
import chaiAsPromised from 'chai-as-promised';
import chaiLike from 'chai-like';
import { RouteParamType } from '../../src/shared/routeParamType.js';
import type { RouteMeta } from '../../src/vite-plugin/collectRoutesFromFS.js';
import createViewConfigJson from '../../src/vite-plugin/createViewConfigJson.js';
import { createTestingRouteFiles, createTestingRouteMeta, createTmpDir } from '../utils.js';

use(chaiLike);
use(chaiAsPromised);

describe('@vaadin/hilla-file-router', () => {
describe('createViewConfigJson', () => {
let tmp: URL;
let meta: readonly RouteMeta[];
let layoutOnlyDir: URL;
let layoutOnlyDirLayout: URL;

before(async () => {
tmp = await createTmpDir();

layoutOnlyDir = new URL('layout-only/', tmp);
layoutOnlyDirLayout = new URL('@layout.tsx', layoutOnlyDir);

await createTestingRouteFiles(tmp);
await mkdir(layoutOnlyDir, { recursive: true });
await appendFile(layoutOnlyDirLayout, 'export default function LayoutOnly() {};');
});

after(async () => {
await rm(tmp, { recursive: true, force: true });
});

beforeEach(() => {
meta = createTestingRouteMeta(tmp);
meta = [
...createTestingRouteMeta(tmp),
{
path: 'layout-only',
layout: layoutOnlyDirLayout,
children: undefined,
},
];
});

it('should generate a JSON representation of the route tree', async () => {
await expect(createViewConfigJson(meta)).to.eventually.equal(
JSON.stringify([
{ route: 'about', title: 'About', params: {} },
{
route: 'profile',
params: {},
children: [
{ route: '', title: 'Profile', params: {} },
{
route: 'account',
title: 'Account',
params: {},
children: [
{
route: 'security',
params: {},
children: [
{ route: 'password', params: {}, title: 'Password' },
{ route: 'two-factor-auth', params: {}, title: 'Two Factor Auth' },
],
},
],
},
{
route: 'friends',
params: {},
title: 'Friends Layout',
children: [
{ route: 'list', params: {}, title: 'List' },
{ route: ':user', params: { ':user': RouteParamType.Required }, title: 'User' },
],
},
],
},
{
route: 'test',
params: {},
children: [
{
route: ':optional?',
title: 'Optional',
params: { ':optional?': RouteParamType.Optional },
},
{ route: '*', title: 'Wildcard', params: { '*': RouteParamType.Wildcard } },
],
},
]),
);
await expect(createViewConfigJson(meta).then(JSON.parse)).to.eventually.be.like([
{ route: 'about', title: 'About', params: {} },
{
route: 'profile',
params: {},
children: [
{ route: '', title: 'Profile', params: {} },
{
route: 'account',
title: 'Account',
params: {},
children: [
{
route: 'security',
params: {},
children: [
{ route: 'password', params: {}, title: 'Password' },
{ route: 'two-factor-auth', params: {}, title: 'Two Factor Auth' },
],
},
],
},
{
route: 'friends',
params: {},
title: 'Friends Layout',
children: [
{ route: 'list', params: {}, title: 'List' },
{ route: ':user', params: { ':user': RouteParamType.Required }, title: 'User' },
],
},
],
},
{
route: 'test',
params: {},
children: [
{
route: ':optional?',
title: 'Optional',
params: { ':optional?': RouteParamType.Optional },
},
{ route: '*', title: 'Wildcard', params: { '*': RouteParamType.Wildcard } },
],
},
{
route: 'layout-only',
params: {},
title: 'Layout Only',
children: [],
},
]);
});
});
});

0 comments on commit 9f087d1

Please sign in to comment.