@uxf/router 11.80.4 → 11.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/merge-route-matchers.js +1 -1
- package/package.json +1 -1
- package/router.d.ts +16 -15
- package/router.js +66 -41
- package/router.test.js +20 -26
- package/routes-check/routes-check.js +8 -5
- package/routes-check/routes-check.test.js +1 -0
- package/types.d.ts +3 -2
- package/utils/path-to-regex.d.ts +1 -0
- package/utils/path-to-regex.js +45 -0
- package/utils/path-to-regex.test.d.ts +1 -0
- package/utils/path-to-regex.test.js +109 -0
package/merge-route-matchers.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.mergeRouteMatchers = mergeRouteMatchers;
|
|
4
4
|
function mergeRouteMatchers(routeMatchers) {
|
|
5
|
-
return (
|
|
5
|
+
return (...params) => routeMatchers.some((routeMatcher) => routeMatcher(...params));
|
|
6
6
|
}
|
package/package.json
CHANGED
package/router.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { LinkProps } from "next/link";
|
|
2
|
-
import { NextRouter } from "next/router";
|
|
3
2
|
import { Infer, Struct } from "superstruct";
|
|
4
3
|
import { SitemapGeneratorOptions, SitemapGeneratorType, SitemapRouteResolvers } from "./sitemap-generator";
|
|
5
|
-
import { QueryParams, RoutesDefinition } from "./types";
|
|
4
|
+
import { QueryParams, RouteDefinition, RoutesDefinition } from "./types";
|
|
6
5
|
interface TransitionOptions {
|
|
7
6
|
shallow?: boolean;
|
|
8
7
|
locale?: string | false;
|
|
@@ -33,25 +32,27 @@ type QueryParamsResult<Nullable extends boolean, T extends keyof RouteList, Loca
|
|
|
33
32
|
replace: (params: Infer<NonNullable<RouteList[T]["schema"]>>, options?: TransitionOptions) => Promise<boolean>;
|
|
34
33
|
}
|
|
35
34
|
];
|
|
36
|
-
type
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
};
|
|
43
|
-
}[keyof RouteList];
|
|
35
|
+
export type RouteMatcher = (pathname: string, pathParams?: Record<string, string | string[]>) => boolean;
|
|
36
|
+
type RouteInfo = {
|
|
37
|
+
pathname: string;
|
|
38
|
+
routeName: string;
|
|
39
|
+
routeDefinition: RouteDefinition<any>;
|
|
40
|
+
};
|
|
44
41
|
type Router<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = {
|
|
45
42
|
route: RouteFunction<Locales, RouteList>;
|
|
46
43
|
routeToUrl: RouteToUrlFunction<Locales, RouteList>;
|
|
47
44
|
createSitemapGenerator: (resolvers: SitemapRouteResolvers<Locales, RouteList>, options?: SitemapGeneratorOptions) => SitemapGeneratorType;
|
|
48
45
|
routes: RouteList;
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
getRouteInfo: (pathname: string) => RouteInfo | null;
|
|
47
|
+
useRouteInfo: () => RouteInfo | null;
|
|
51
48
|
/**
|
|
52
49
|
* @deprecated use useQueryParamsStatic or useQueryParams instead
|
|
53
50
|
*/
|
|
54
51
|
useQueryParamsDeprecated: <T extends keyof RouteList>() => QueryParams<RouteList, T>;
|
|
52
|
+
/**
|
|
53
|
+
* Returns path params merged with search params
|
|
54
|
+
*/
|
|
55
|
+
usePageParams: () => Record<string, string | string[]> | null;
|
|
55
56
|
useQueryParams: <T extends keyof RouteList>(routeName: T) => QueryParamsResult<false, T, Locales, RouteList>;
|
|
56
57
|
useQueryParamsStatic: <T extends keyof RouteList>(routeName: T) => QueryParamsResult<true, T, Locales, RouteList>;
|
|
57
58
|
createRouteMatcher: (...args: FunctionParametersGeneratorWithPartialParams<Locales, RouteList>) => RouteMatcher;
|
|
@@ -60,9 +61,9 @@ type RouterOptions<L extends string[]> = {
|
|
|
60
61
|
baseUrl?: string;
|
|
61
62
|
locales?: L;
|
|
62
63
|
};
|
|
63
|
-
type
|
|
64
|
-
|
|
65
|
-
locale: Locales[number] | null;
|
|
64
|
+
export type ExtendedRouteDefinition<Locales extends string[]> = RouteDefinition<Locales> & {
|
|
65
|
+
regex: RegExp[];
|
|
66
66
|
};
|
|
67
|
+
export type ExtendedRoutesDefinition<Locales extends string[]> = Record<string, ExtendedRouteDefinition<Locales>>;
|
|
67
68
|
export declare function createRouter<Locales extends string[], RouteList extends RoutesDefinition<Locales>>(routes: RouteList, routerOptions: RouterOptions<Locales>): Router<Locales, RouteList>;
|
|
68
69
|
export {};
|
package/router.js
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createRouter = createRouter;
|
|
4
4
|
const empty_object_1 = require("@uxf/core/constants/empty-object");
|
|
5
|
+
const is_nil_1 = require("@uxf/core/utils/is-nil");
|
|
6
|
+
const is_not_nil_1 = require("@uxf/core/utils/is-not-nil");
|
|
5
7
|
const throw_error_1 = require("@uxf/core/utils/throw-error");
|
|
8
|
+
const navigation_1 = require("next/navigation");
|
|
6
9
|
const router_1 = require("next/router");
|
|
7
10
|
const qs_1 = require("qs");
|
|
8
11
|
const superstruct_1 = require("superstruct");
|
|
9
12
|
const sitemap_generator_1 = require("./sitemap-generator");
|
|
13
|
+
const path_to_regex_1 = require("./utils/path-to-regex");
|
|
10
14
|
/**
|
|
11
15
|
* Arguments can be:
|
|
12
16
|
*
|
|
@@ -16,6 +20,15 @@ function decodeArgs(args) {
|
|
|
16
20
|
return { route: args[0], params: args[1], options: args[2] };
|
|
17
21
|
}
|
|
18
22
|
function createRouter(routes, routerOptions) {
|
|
23
|
+
const routesWithRegex = Object.entries(routes).reduce((acc, [routeName, routeDefinition]) => ({
|
|
24
|
+
...acc,
|
|
25
|
+
[routeName]: {
|
|
26
|
+
...routeDefinition,
|
|
27
|
+
regex: typeof routeDefinition.path === "string"
|
|
28
|
+
? [(0, path_to_regex_1.pathToRegex)(routeDefinition.path)]
|
|
29
|
+
: Object.values(routeDefinition.path).map((path) => (0, path_to_regex_1.pathToRegex)(path)),
|
|
30
|
+
},
|
|
31
|
+
}), {});
|
|
19
32
|
const routeToUrl = (...args) => {
|
|
20
33
|
var _a;
|
|
21
34
|
const { route, params, options } = decodeArgs(args);
|
|
@@ -71,16 +84,28 @@ function createRouter(routes, routerOptions) {
|
|
|
71
84
|
}
|
|
72
85
|
return (options === null || options === void 0 ? void 0 : options.shouldBeAbsolute) ? `${routerOptions.baseUrl}${pathname}` : pathname;
|
|
73
86
|
};
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
87
|
+
const getRouteInfo = (pathname) => {
|
|
88
|
+
const entry = Object.entries(routesWithRegex).find(([, routeDefinition]) => routeDefinition.regex.some((regex) => regex.test(pathname)));
|
|
89
|
+
if (!entry) {
|
|
90
|
+
throw new Error(`Active route not found (${pathname}).`);
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
pathname,
|
|
94
|
+
routeName: entry[0],
|
|
95
|
+
routeDefinition: entry[1],
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
const useRouteInfo = () => {
|
|
99
|
+
const pathname = (0, navigation_1.usePathname)();
|
|
100
|
+
return (0, is_not_nil_1.isNotNil)(pathname) ? getRouteInfo(pathname) : null;
|
|
101
|
+
};
|
|
102
|
+
const usePageParams = () => {
|
|
103
|
+
const searchParams = (0, navigation_1.useSearchParams)();
|
|
104
|
+
const params = (0, navigation_1.useParams)();
|
|
105
|
+
if ((0, is_nil_1.isNil)(searchParams) || (0, is_nil_1.isNil)(params)) {
|
|
106
|
+
return null;
|
|
80
107
|
}
|
|
81
|
-
|
|
82
|
-
const params = router.isReady && schema ? (0, superstruct_1.mask)(router.query, schema) : null;
|
|
83
|
-
return { route: activeRoute, params };
|
|
108
|
+
return Array.from(searchParams.entries()).reduce((acc, [key, value]) => ({ ...acc, [key]: value }), params);
|
|
84
109
|
};
|
|
85
110
|
return {
|
|
86
111
|
route(...args) {
|
|
@@ -95,69 +120,69 @@ function createRouter(routes, routerOptions) {
|
|
|
95
120
|
};
|
|
96
121
|
},
|
|
97
122
|
routeToUrl,
|
|
98
|
-
useActiveRoute() {
|
|
99
|
-
var _a;
|
|
100
|
-
const router = (0, router_1.useRouter)();
|
|
101
|
-
const activeRoute = Object.keys(routes).find((route) => typeof routes[route].path === "string"
|
|
102
|
-
? routes[route].path === router.pathname
|
|
103
|
-
: Object.values(routes[route].path).includes(router.pathname));
|
|
104
|
-
if (!activeRoute) {
|
|
105
|
-
throw new Error("Active route not found.");
|
|
106
|
-
}
|
|
107
|
-
return {
|
|
108
|
-
route: activeRoute,
|
|
109
|
-
locale: ((_a = router.locale) !== null && _a !== void 0 ? _a : null),
|
|
110
|
-
};
|
|
111
|
-
},
|
|
112
123
|
createSitemapGenerator(resolvers, options) {
|
|
113
124
|
return new sitemap_generator_1.SitemapGenerator(resolvers, options);
|
|
114
125
|
},
|
|
115
126
|
routes,
|
|
127
|
+
getRouteInfo,
|
|
128
|
+
useRouteInfo,
|
|
129
|
+
usePageParams,
|
|
116
130
|
useQueryParamsDeprecated: () => (0, router_1.useRouter)().query,
|
|
117
131
|
useQueryParams(routeName) {
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
if (!router.isReady) {
|
|
132
|
+
var _a;
|
|
133
|
+
const router = (0, navigation_1.useRouter)();
|
|
134
|
+
const pageParams = usePageParams();
|
|
135
|
+
if ((0, is_nil_1.isNil)(pageParams)) {
|
|
124
136
|
throw new Error("Router is not ready. Use useQueryParamsStatic instead of useQueryParams.");
|
|
125
137
|
}
|
|
138
|
+
const schema = (_a = routes[routeName].schema) !== null && _a !== void 0 ? _a : (0, throw_error_1.throwError)(`Route '${String(routeName)}' has no schema.`);
|
|
126
139
|
return [
|
|
127
|
-
(0, superstruct_1.mask)(
|
|
140
|
+
(0, superstruct_1.mask)(pageParams, schema),
|
|
128
141
|
{
|
|
129
|
-
push: (params, options) =>
|
|
130
|
-
|
|
142
|
+
push: (params, options) => {
|
|
143
|
+
router.push(routeToUrl(routeName, params, {}), options);
|
|
144
|
+
return Promise.resolve(true); // remove me
|
|
145
|
+
},
|
|
146
|
+
replace: (params, options) => {
|
|
147
|
+
router.replace(routeToUrl(routeName, params, {}), options);
|
|
148
|
+
return Promise.resolve(true); // remove me
|
|
149
|
+
},
|
|
131
150
|
},
|
|
132
151
|
];
|
|
133
152
|
},
|
|
134
153
|
useQueryParamsStatic(routeName) {
|
|
135
|
-
const router = (0,
|
|
154
|
+
const router = (0, navigation_1.useRouter)();
|
|
155
|
+
const pageParams = usePageParams();
|
|
136
156
|
const schema = routes[routeName].schema;
|
|
137
157
|
if (!schema) {
|
|
138
158
|
throw new Error(`Route '${String(routeName)}' has no schema.`);
|
|
139
159
|
}
|
|
140
160
|
return [
|
|
141
|
-
|
|
161
|
+
(0, is_not_nil_1.isNotNil)(pageParams) ? (0, superstruct_1.mask)(pageParams, schema) : null,
|
|
142
162
|
{
|
|
143
|
-
push: (params) =>
|
|
144
|
-
|
|
163
|
+
push: (params) => {
|
|
164
|
+
router.push(routeToUrl(routeName, params, {}));
|
|
165
|
+
return Promise.resolve(true); // remove me
|
|
166
|
+
},
|
|
167
|
+
replace: (params) => {
|
|
168
|
+
router.replace(routeToUrl(routeName, params, {}));
|
|
169
|
+
return Promise.resolve(true); // remove me
|
|
170
|
+
},
|
|
145
171
|
},
|
|
146
172
|
];
|
|
147
173
|
},
|
|
148
|
-
getCurrentRoute,
|
|
149
174
|
createRouteMatcher(...args) {
|
|
150
175
|
const [requiredRouteName, requiredParams] = args;
|
|
151
|
-
return (
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
176
|
+
return (pathname, pageParams) => {
|
|
177
|
+
const routeInfo = getRouteInfo(pathname);
|
|
178
|
+
if (routeInfo.routeName !== requiredRouteName) {
|
|
154
179
|
return false;
|
|
155
180
|
}
|
|
156
181
|
if (!requiredParams || Object.keys(requiredParams).length === 0) {
|
|
157
182
|
return true;
|
|
158
183
|
}
|
|
159
184
|
for (const [paramName, paramValue] of Object.entries(requiredParams)) {
|
|
160
|
-
if ((
|
|
185
|
+
if ((pageParams === null || pageParams === void 0 ? void 0 : pageParams[paramName]) !== paramValue) {
|
|
161
186
|
return false;
|
|
162
187
|
}
|
|
163
188
|
}
|
package/router.test.js
CHANGED
|
@@ -138,39 +138,33 @@ test("routeToUrl", () => {
|
|
|
138
138
|
DATA.map(({ actual, expected }) => expect(actual).toBe(expected));
|
|
139
139
|
});
|
|
140
140
|
test("create route matcher", () => {
|
|
141
|
-
expect(createRouteMatcher("manyParameters", { param1: "value-1" })({
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
query: { param1: "value-1", param2: "any", queryParam1: "any", queryParam2: "any" },
|
|
141
|
+
expect(createRouteMatcher("manyParameters", { param1: "value-1" })("/many-parameters/value-1/form/any", {
|
|
142
|
+
param1: "value-1",
|
|
143
|
+
param2: "any",
|
|
145
144
|
})).toBe(true);
|
|
146
|
-
expect(createRouteMatcher("manyParameters", { param1: "value-1" })({
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
query: { param1: "any", param2: "any" },
|
|
145
|
+
expect(createRouteMatcher("manyParameters", { param1: "value-1" })("/many-parameters/any/form/any", {
|
|
146
|
+
param1: "any",
|
|
147
|
+
param2: "any",
|
|
150
148
|
})).toBe(false);
|
|
151
|
-
expect(createRouteMatcher("manyParameters", { param1: "value-1"
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
query: { param1: "value-1", param2: "any", queryParam1: "query-1" },
|
|
149
|
+
expect(createRouteMatcher("manyParameters", { param1: "value-1" })("/many-parameters/value-1/form/any", {
|
|
150
|
+
param1: "value-1",
|
|
151
|
+
param2: "any",
|
|
155
152
|
})).toBe(true);
|
|
156
153
|
});
|
|
157
154
|
test("merge route matchers", () => {
|
|
158
|
-
const routeMatcher1 = createRouteMatcher("manyParameters", { param1: "value-1"
|
|
159
|
-
const routeMatcher2 = createRouteMatcher("manyParameters", { param1: "value-2"
|
|
155
|
+
const routeMatcher1 = createRouteMatcher("manyParameters", { param1: "value-1" });
|
|
156
|
+
const routeMatcher2 = createRouteMatcher("manyParameters", { param1: "value-2" });
|
|
160
157
|
const routeMatcher = (0, merge_route_matchers_1.mergeRouteMatchers)([routeMatcher1, routeMatcher2]);
|
|
161
|
-
expect(routeMatcher({
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
isReady: true,
|
|
158
|
+
expect(routeMatcher("/many-parameters/value-1/form/any", {
|
|
159
|
+
param1: "value-1",
|
|
160
|
+
param2: "any",
|
|
165
161
|
})).toBe(true);
|
|
166
|
-
expect(routeMatcher({
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
isReady: true,
|
|
162
|
+
expect(routeMatcher("/many-parameters/value-2/form/any", {
|
|
163
|
+
param1: "value-2",
|
|
164
|
+
param2: "any",
|
|
170
165
|
})).toBe(true);
|
|
171
|
-
expect(routeMatcher({
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
isReady: true,
|
|
166
|
+
expect(routeMatcher("/many-parameters/value-3/form/any", {
|
|
167
|
+
param1: "value-3",
|
|
168
|
+
param2: "any",
|
|
175
169
|
})).toBe(false);
|
|
176
170
|
});
|
|
@@ -31,16 +31,19 @@ function routesCheck(routes, options) {
|
|
|
31
31
|
if (fileExists((0, path_1.join)(options.pagesDir, ...pathParts, "index.ts"))) {
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
+
const pagesPathParts = [...pathParts]; // clone
|
|
34
35
|
// TODO @vejvis - proč tu je tahle podmínka?
|
|
35
|
-
const lastPart =
|
|
36
|
-
const pagePath =
|
|
37
|
-
? (0, path_1.join)(options.pagesDir, ...
|
|
36
|
+
const lastPart = pagesPathParts.splice(-1);
|
|
37
|
+
const pagePath = pagesPathParts.length > 0
|
|
38
|
+
? (0, path_1.join)(options.pagesDir, ...pagesPathParts, `${lastPart[0] || "index"}.ts`)
|
|
38
39
|
: (0, path_1.join)(options.pagesDir, `${lastPart[0] || "index"}.tsx`);
|
|
39
40
|
if (fileExists(pagePath)) {
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
42
|
-
if (fileExists((0, path_1.join)(options.appDir, ...
|
|
43
|
-
|
|
43
|
+
if (fileExists((0, path_1.join)(options.appDir, ...pathParts, "page.ts"))) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (fileExists((0, path_1.join)(options.appDir, ...pathParts, "route.ts"))) {
|
|
44
47
|
return;
|
|
45
48
|
}
|
|
46
49
|
throw new Error("Invalid");
|
|
@@ -12,6 +12,7 @@ describe("routesCheck", () => {
|
|
|
12
12
|
route5: { path: "/product-2/[id]" }, // path parameter, index file
|
|
13
13
|
app1: { path: "/app-directory" },
|
|
14
14
|
app2: { path: "/app-directory/[param]" },
|
|
15
|
+
app3: { path: "/api" },
|
|
15
16
|
}, {
|
|
16
17
|
shouldProcessExit: false,
|
|
17
18
|
appDir: (0, path_1.join)(__dirname, "__test__", "app"),
|
package/types.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { GetServerSideProps, GetStaticProps, PreviewData as NextPreviewData } from "next";
|
|
2
2
|
import { Infer, Struct } from "superstruct";
|
|
3
|
-
export type
|
|
3
|
+
export type RouteDefinition<Locales extends string[]> = {
|
|
4
4
|
path: string | Record<Locales[number], string>;
|
|
5
5
|
schema?: Struct<any, any> | null;
|
|
6
|
-
}
|
|
6
|
+
};
|
|
7
|
+
export type RoutesDefinition<Locales extends string[]> = Record<string, RouteDefinition<Locales>>;
|
|
7
8
|
export type QueryParams<RouteList extends RoutesDefinition<any>, Route extends keyof RouteList> = RouteList[Route]["schema"] extends null ? never : {
|
|
8
9
|
[key in keyof Infer<NonNullable<RouteList[Route]["schema"]>>]: string | string[] | undefined;
|
|
9
10
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function pathToRegex(path: string): RegExp;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pathToRegex = pathToRegex;
|
|
4
|
+
function pathToRegex(path) {
|
|
5
|
+
const paramNames = [];
|
|
6
|
+
// Replace Next.js route patterns with named placeholders
|
|
7
|
+
let pattern = path
|
|
8
|
+
// Optional catch-all: [[...param]] -> __OPTIONAL_CATCHALL_param__
|
|
9
|
+
.replace(/\/\[\[\.\.\.([^\]]+)\]\]/g, (_, param) => {
|
|
10
|
+
paramNames.push(param);
|
|
11
|
+
return `__OPTIONAL_CATCHALL_${param}__`;
|
|
12
|
+
})
|
|
13
|
+
.replace(/^\[\[\.\.\.([^\]]+)\]\]$/g, (_, param) => {
|
|
14
|
+
paramNames.push(param);
|
|
15
|
+
return `__ROOT_OPTIONAL_CATCHALL_${param}__`;
|
|
16
|
+
})
|
|
17
|
+
.replace(/^\[\[\.\.\.([^\]]+)\]\]/g, (_, param) => {
|
|
18
|
+
paramNames.push(param);
|
|
19
|
+
return `__ROOT_OPTIONAL_CATCHALL_${param}__`;
|
|
20
|
+
})
|
|
21
|
+
// Catch-all: [...param] -> __CATCHALL_param__
|
|
22
|
+
.replace(/\/\[\.\.\.([^\]]+)\]/g, (_, param) => {
|
|
23
|
+
paramNames.push(param);
|
|
24
|
+
return `__CATCHALL_${param}__`;
|
|
25
|
+
})
|
|
26
|
+
// Dynamic: [param] -> __DYNAMIC_param__
|
|
27
|
+
.replace(/\[([^\]]+)\]/g, (_, param) => {
|
|
28
|
+
paramNames.push(param);
|
|
29
|
+
return `__DYNAMIC_${param}__`;
|
|
30
|
+
});
|
|
31
|
+
// Escape special regex characters
|
|
32
|
+
pattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
33
|
+
// Replace placeholders with named regex patterns
|
|
34
|
+
paramNames.forEach((param) => {
|
|
35
|
+
// Sanitize parameter name for use in named capture groups (only alphanumeric and underscore allowed)
|
|
36
|
+
const sanitizedParam = param.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
37
|
+
pattern = pattern
|
|
38
|
+
.replace(`__OPTIONAL_CATCHALL_${param}__`, `(?:/(?<${sanitizedParam}>.*))?`)
|
|
39
|
+
.replace(`__ROOT_OPTIONAL_CATCHALL_${param}__`, `(?<${sanitizedParam}>.*)`)
|
|
40
|
+
.replace(`__CATCHALL_${param}__`, `/(?<${sanitizedParam}>.+)`)
|
|
41
|
+
.replace(`__DYNAMIC_${param}__`, `(?<${sanitizedParam}>[^/]+)`);
|
|
42
|
+
});
|
|
43
|
+
// Ensure the pattern matches the exact path (no extra segments)
|
|
44
|
+
return new RegExp("^" + pattern + "$");
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const path_to_regex_1 = require("./path-to-regex");
|
|
4
|
+
describe("pathToRegex", () => {
|
|
5
|
+
describe("static routes", () => {
|
|
6
|
+
it("should match exact static paths", () => {
|
|
7
|
+
const regex = (0, path_to_regex_1.pathToRegex)("/product/detail");
|
|
8
|
+
expect(regex.test("/product/detail")).toBe(true);
|
|
9
|
+
expect(regex.test("/product/detail/extra")).toBe(false);
|
|
10
|
+
expect(regex.test("/product")).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
it("should handle root path", () => {
|
|
13
|
+
const regex = (0, path_to_regex_1.pathToRegex)("/");
|
|
14
|
+
expect(regex.test("/")).toBe(true);
|
|
15
|
+
expect(regex.test("/anything")).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
describe("dynamic routes [param]", () => {
|
|
19
|
+
it("should match single dynamic segment", () => {
|
|
20
|
+
const regex = (0, path_to_regex_1.pathToRegex)("/product/[id]");
|
|
21
|
+
expect(regex.test("/product/1")).toBe(true);
|
|
22
|
+
expect(regex.test("/product/abc")).toBe(true);
|
|
23
|
+
expect(regex.test("/product/123-xyz")).toBe(true);
|
|
24
|
+
expect(regex.test("/product/")).toBe(false);
|
|
25
|
+
expect(regex.test("/product/1/extra")).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
it("should match dynamic route with detail", () => {
|
|
28
|
+
const regex = (0, path_to_regex_1.pathToRegex)("/product/[id]/detail");
|
|
29
|
+
expect(regex.test("/product/1/detail")).toBe(true);
|
|
30
|
+
expect(regex.test("/product/abc/detail")).toBe(true);
|
|
31
|
+
expect(regex.test("/product/1/detail/extra")).toBe(false);
|
|
32
|
+
expect(regex.test("/product/detail")).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
it("should handle multiple dynamic segments", () => {
|
|
35
|
+
const regex = (0, path_to_regex_1.pathToRegex)("/[category]/[id]/edit");
|
|
36
|
+
expect(regex.test("/electronics/123/edit")).toBe(true);
|
|
37
|
+
expect(regex.test("/books/abc/edit")).toBe(true);
|
|
38
|
+
expect(regex.test("/electronics/123/edit/more")).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe("catch-all routes [...slug]", () => {
|
|
42
|
+
it("should match catch-all segments", () => {
|
|
43
|
+
const regex = (0, path_to_regex_1.pathToRegex)("/docs/[...slug]");
|
|
44
|
+
expect(regex.test("/docs/getting-started")).toBe(true);
|
|
45
|
+
expect(regex.test("/docs/api/reference")).toBe(true);
|
|
46
|
+
expect(regex.test("/docs/api/reference/auth")).toBe(true);
|
|
47
|
+
expect(regex.test("/docs")).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
it("should require at least one segment for catch-all", () => {
|
|
50
|
+
const regex = (0, path_to_regex_1.pathToRegex)("/blog/[...slug]");
|
|
51
|
+
expect(regex.test("/blog/2023")).toBe(true);
|
|
52
|
+
expect(regex.test("/blog/2023/12/post")).toBe(true);
|
|
53
|
+
expect(regex.test("/blog/")).toBe(false);
|
|
54
|
+
expect(regex.test("/blog")).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe("optional catch-all routes [[...slug]]", () => {
|
|
58
|
+
it("should match optional catch-all segments", () => {
|
|
59
|
+
const regex = (0, path_to_regex_1.pathToRegex)("/shop/[[...categories]]");
|
|
60
|
+
expect(regex.test("/shop")).toBe(true);
|
|
61
|
+
expect(regex.test("/shop/")).toBe(true);
|
|
62
|
+
expect(regex.test("/shop/electronics")).toBe(true);
|
|
63
|
+
expect(regex.test("/shop/electronics/phones")).toBe(true);
|
|
64
|
+
expect(regex.test("/shop/electronics/phones/iphone")).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
it("should handle optional catch-all at root", () => {
|
|
67
|
+
const regex = (0, path_to_regex_1.pathToRegex)("/[[...slug]]");
|
|
68
|
+
expect(regex.test("/")).toBe(true);
|
|
69
|
+
expect(regex.test("/about")).toBe(true);
|
|
70
|
+
expect(regex.test("/about/team")).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe("complex combinations", () => {
|
|
74
|
+
it("should handle dynamic + catch-all", () => {
|
|
75
|
+
const regex = (0, path_to_regex_1.pathToRegex)("/user/[id]/posts/[...slug]");
|
|
76
|
+
expect(regex.test("/user/123/posts/my-first-post")).toBe(true);
|
|
77
|
+
expect(regex.test("/user/abc/posts/category/tech/post-1")).toBe(true);
|
|
78
|
+
expect(regex.test("/user/123/posts")).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
it("should handle dynamic + optional catch-all", () => {
|
|
81
|
+
const regex = (0, path_to_regex_1.pathToRegex)("/user/[id]/files/[[...path]]");
|
|
82
|
+
expect(regex.test("/user/123/files")).toBe(true);
|
|
83
|
+
expect(regex.test("/user/123/files/documents")).toBe(true);
|
|
84
|
+
expect(regex.test("/user/123/files/documents/pdf/report.pdf")).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe("edge cases", () => {
|
|
88
|
+
it("should handle special regex characters in static parts", () => {
|
|
89
|
+
const regex = (0, path_to_regex_1.pathToRegex)("/api/v1.0/users");
|
|
90
|
+
expect(regex.test("/api/v1.0/users")).toBe(true);
|
|
91
|
+
expect(regex.test("/api/v1x0/users")).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
it("should handle paths with hyphens and underscores", () => {
|
|
94
|
+
const regex = (0, path_to_regex_1.pathToRegex)("/api-v1/user_profile/[user-id]");
|
|
95
|
+
expect(regex.test("/api-v1/user_profile/123-abc")).toBe(true);
|
|
96
|
+
expect(regex.test("/api-v1/user_profile/user_123")).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe("regex checks", () => {
|
|
100
|
+
const PATHS = [
|
|
101
|
+
"/",
|
|
102
|
+
"/product/[id]",
|
|
103
|
+
"/product/[id]/detail",
|
|
104
|
+
"/user/files/[...path]",
|
|
105
|
+
"/user/files/[[...path]]",
|
|
106
|
+
];
|
|
107
|
+
expect(PATHS.reduce((acc, curr) => ({ ...acc, [curr]: (0, path_to_regex_1.pathToRegex)(curr).source }), {})).toMatchSnapshot();
|
|
108
|
+
});
|
|
109
|
+
});
|