Skip to content

Commit 189a87e

Browse files
committed
add unit test
1 parent 0a6ee8d commit 189a87e

File tree

3 files changed

+223
-66
lines changed

3 files changed

+223
-66
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { AppPathRoutesManifest, RoutesManifest } from "config/index";
2+
import type { RouteDefinition } from "types/next-types";
3+
import type { RouteType } from "types/open-next";
4+
5+
// Add the locale prefix to the regex so we correctly match the rawPath
6+
const optionalLocalePrefixRegex = RoutesManifest.locales.length
7+
? `^/(?:${RoutesManifest.locales.map((locale) => `${locale}/?`).join("|")})?`
8+
: "^/";
9+
10+
// Add the basepath prefix to the regex so we correctly match the rawPath
11+
const optionalBasepathPrefixRegex = RoutesManifest.basePath
12+
? `^${RoutesManifest.basePath}/?`
13+
: "^/";
14+
15+
// Add the basePath prefix to the api routes
16+
export const apiPrefix = RoutesManifest.basePath
17+
? `${RoutesManifest.basePath}/api`
18+
: "/api";
19+
20+
function routeMatcher(routeDefinitions: RouteDefinition[]) {
21+
const regexp = routeDefinitions.map((route) => {
22+
return {
23+
page: route.page,
24+
regexp: new RegExp(
25+
route.regex
26+
.replace("^/", optionalLocalePrefixRegex)
27+
.replace("^/", optionalBasepathPrefixRegex),
28+
),
29+
};
30+
});
31+
32+
// We need to use AppPathRoutesManifest here
33+
const appPathsSet = new Set(
34+
Object.entries(AppPathRoutesManifest)
35+
.filter(([key, _]) => key.endsWith("page"))
36+
.map(([_, value]) => value),
37+
);
38+
const routePathsSet = new Set(
39+
Object.entries(AppPathRoutesManifest)
40+
.filter(([key, _]) => key.endsWith("route"))
41+
.map(([_, value]) => value),
42+
);
43+
return function matchRoute(path: string) {
44+
const foundRoutes = regexp.filter((route) => route.regexp.test(path));
45+
46+
if (foundRoutes.length > 0) {
47+
return foundRoutes.map((foundRoute) => {
48+
const routeType: RouteType | undefined = appPathsSet.has(
49+
foundRoute.page,
50+
)
51+
? "app"
52+
: routePathsSet.has(foundRoute.page)
53+
? "route"
54+
: "page";
55+
return {
56+
route: foundRoute.page,
57+
type: routeType,
58+
};
59+
});
60+
}
61+
return false;
62+
};
63+
}
64+
65+
export const staticRouteMatcher = routeMatcher(RoutesManifest.routes.static);
66+
export const dynamicRouteMatcher = routeMatcher(RoutesManifest.routes.dynamic);

packages/open-next/src/core/routingHandler.ts

Lines changed: 5 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
import {
2-
AppPathRoutesManifest,
32
BuildId,
43
ConfigHeaders,
54
PrerenderManifest,
65
RoutesManifest,
76
} from "config/index";
8-
import type { RouteDefinition } from "types/next-types";
97
import type {
108
InternalEvent,
119
InternalResult,
1210
ResolvedRoute,
13-
RouteType,
1411
RoutingResult,
1512
} from "types/open-next";
1613

@@ -24,76 +21,18 @@ import {
2421
handleRewrites,
2522
} from "./routing/matcher";
2623
import { handleMiddleware } from "./routing/middleware";
24+
import {
25+
apiPrefix,
26+
dynamicRouteMatcher,
27+
staticRouteMatcher,
28+
} from "./routing/routeMatcher";
2729

2830
export const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
2931
export const INTERNAL_HEADER_PREFIX = "x-opennext-";
3032
export const INTERNAL_HEADER_INITIAL_PATH = `${INTERNAL_HEADER_PREFIX}initial-path`;
3133
export const INTERNAL_HEADER_RESOLVED_ROUTES = `${INTERNAL_HEADER_PREFIX}resolved-routes`;
3234
export const MIDDLEWARE_HEADER_PREFIX_LEN = MIDDLEWARE_HEADER_PREFIX.length;
3335

34-
// Add the locale prefix to the regex so we correctly match the rawPath
35-
const optionalLocalePrefixRegex = RoutesManifest.locales.length
36-
? `^/(?:${RoutesManifest.locales.map((locale) => `${locale}/?`).join("|")})?`
37-
: "^/";
38-
39-
// Add the basepath prefix to the regex so we correctly match the rawPath
40-
const optionalBasepathPrefixRegex = RoutesManifest.basePath
41-
? `^${RoutesManifest.basePath}/?`
42-
: "^/";
43-
44-
// Add the basePath prefix to the api routes
45-
const apiPrefix = RoutesManifest.basePath
46-
? `${RoutesManifest.basePath}/api`
47-
: "/api";
48-
49-
export function routeMatcher(routeDefinitions: RouteDefinition[]) {
50-
const regexp = routeDefinitions.map((route) => {
51-
return {
52-
page: route.page,
53-
regexp: new RegExp(
54-
route.regex
55-
.replace("^/", optionalLocalePrefixRegex)
56-
.replace("^/", optionalBasepathPrefixRegex),
57-
),
58-
};
59-
});
60-
61-
// We need to use AppPathRoutesManifest here
62-
const appPathsSet = new Set(
63-
Object.entries(AppPathRoutesManifest)
64-
.filter(([key, _]) => key.endsWith("page"))
65-
.map(([_, value]) => value),
66-
);
67-
const routePathsSet = new Set(
68-
Object.entries(AppPathRoutesManifest)
69-
.filter(([key, _]) => key.endsWith("route"))
70-
.map(([_, value]) => value),
71-
);
72-
return function matchRoute(path: string) {
73-
const foundRoutes = regexp.filter((route) => route.regexp.test(path));
74-
75-
if (foundRoutes.length > 0) {
76-
return foundRoutes.map((foundRoute) => {
77-
const routeType: RouteType | undefined = appPathsSet.has(
78-
foundRoute.page,
79-
)
80-
? "app"
81-
: routePathsSet.has(foundRoute.page)
82-
? "route"
83-
: "page";
84-
return {
85-
route: foundRoute.page,
86-
type: routeType,
87-
};
88-
});
89-
}
90-
return false;
91-
};
92-
}
93-
94-
const staticRouteMatcher = routeMatcher(RoutesManifest.routes.static);
95-
const dynamicRouteMatcher = routeMatcher(RoutesManifest.routes.dynamic);
96-
9736
// Geolocation headers starting from Nextjs 15
9837
// See https://github.com/vercel/vercel/blob/7714b1c/packages/functions/src/headers.ts
9938
const geoHeaderToNextHeader = {
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import {
2+
staticRouteMatcher,
3+
dynamicRouteMatcher,
4+
} from "@opennextjs/aws/core/routing/routeMatcher.js";
5+
import { vi } from "vitest";
6+
7+
vi.mock("@opennextjs/aws/adapters/config/index.js", () => ({
8+
NextConfig: {},
9+
AppPathRoutesManifest: {
10+
"/api/app/route": "/api/app",
11+
"/app/page": "/app",
12+
"/catchAll/[...slug]/page": "/catchAll/[...slug]",
13+
},
14+
RoutesManifest: {
15+
version: 3,
16+
pages404: true,
17+
caseSensitive: false,
18+
basePath: "",
19+
locales: [],
20+
redirects: [],
21+
headers: [],
22+
routes: {
23+
dynamic: [
24+
{
25+
page: "/catchAll/[...slug]",
26+
regex: "^/catchAll/(.+?)(?:/)?$",
27+
routeKeys: {
28+
nxtPslug: "nxtPslug",
29+
},
30+
namedRegex: "^/catchAll/(?<nxtPslug>.+?)(?:/)?$",
31+
},
32+
{
33+
page: "/page/catchAll/[...slug]",
34+
regex: "^/page/catchAll/(.+?)(?:/)?$",
35+
routeKeys: {
36+
nxtPslug: "nxtPslug",
37+
},
38+
namedRegex: "^/page/catchAll/(?<nxtPslug>.+?)(?:/)?$",
39+
},
40+
],
41+
static: [
42+
{
43+
page: "/app",
44+
regex: "^/app(?:/)?$",
45+
routeKeys: {},
46+
namedRegex: "^/app(?:/)?$",
47+
},
48+
{
49+
page: "/api/app",
50+
regex: "^/api/app(?:/)?$",
51+
routeKeys: {},
52+
namedRegex: "^/api/app(?:/)?$",
53+
},
54+
{
55+
page: "/page",
56+
regex: "^/page(?:/)?$",
57+
routeKeys: {},
58+
namedRegex: "^/page(?:/)?$",
59+
},
60+
{
61+
page: "/page/catchAll/static",
62+
regex: "^/page/catchAll/static(?:/)?$",
63+
routeKeys: {},
64+
namedRegex: "^/page/catchAll/static(?:/)?$",
65+
},
66+
],
67+
},
68+
},
69+
}));
70+
71+
describe("routeMatcher", () => {
72+
beforeEach(() => {
73+
vi.resetAllMocks();
74+
});
75+
76+
describe("staticRouteMatcher", () => {
77+
it("should match static app route", () => {
78+
const route = staticRouteMatcher("/app");
79+
expect(route).toEqual([
80+
{
81+
route: "/app",
82+
type: "app",
83+
},
84+
]);
85+
});
86+
87+
it("should match static api route", () => {
88+
const route = staticRouteMatcher("/api/app");
89+
expect(route).toEqual([
90+
{
91+
route: "/api/app",
92+
type: "route",
93+
},
94+
]);
95+
});
96+
97+
it("should not match app dynamic route", () => {
98+
const route = staticRouteMatcher("/catchAll/slug");
99+
expect(route).toEqual(false);
100+
});
101+
102+
it("should not match page dynamic route", () => {
103+
const route = staticRouteMatcher("/page/catchAll/slug");
104+
expect(route).toEqual(false);
105+
});
106+
107+
it("should not match random route", () => {
108+
const route = staticRouteMatcher("/random");
109+
expect(route).toEqual(false);
110+
});
111+
});
112+
113+
describe("dynamicRouteMatcher", () => {
114+
it("should match dynamic app page", () => {
115+
const route = dynamicRouteMatcher("/catchAll/slug/b");
116+
expect(route).toEqual([
117+
{
118+
route: "/catchAll/[...slug]",
119+
type: "app",
120+
},
121+
]);
122+
});
123+
124+
it("should match dynamic page router page", () => {
125+
const route = dynamicRouteMatcher("/page/catchAll/slug/b");
126+
expect(route).toEqual([
127+
{
128+
route: "/page/catchAll/[...slug]",
129+
type: "page",
130+
},
131+
]);
132+
});
133+
134+
it("should match both the static and dynamic page", () => {
135+
const pathToMatch = "/page/catchAll/static";
136+
const dynamicRoutes = dynamicRouteMatcher(pathToMatch);
137+
const staticRoutes = staticRouteMatcher(pathToMatch);
138+
expect(dynamicRoutes).toEqual([
139+
{
140+
route: "/page/catchAll/[...slug]",
141+
type: "page",
142+
},
143+
]);
144+
expect(staticRoutes).toEqual([
145+
{
146+
route: "/page/catchAll/static",
147+
type: "page",
148+
},
149+
]);
150+
});
151+
});
152+
});

0 commit comments

Comments
 (0)