@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.
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mergeRouteMatchers = mergeRouteMatchers;
4
4
  function mergeRouteMatchers(routeMatchers) {
5
- return (router) => routeMatchers.some((routeMatcher) => routeMatcher(router));
5
+ return (...params) => routeMatchers.some((routeMatcher) => routeMatcher(...params));
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxf/router",
3
- "version": "11.80.4",
3
+ "version": "11.84.0",
4
4
  "description": "UXF Router",
5
5
  "author": "UXFans <dev@uxf.cz>",
6
6
  "homepage": "https://gitlab.com/uxf-npm/router#readme",
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 SimplyNextRouter = Pick<NextRouter, "pathname" | "query" | "isReady">;
37
- export type RouteMatcher = (router: SimplyNextRouter) => boolean;
38
- type CurrentRoute<RouteList extends RoutesDefinition<any>> = {
39
- [K in keyof RouteList]: {
40
- route: K;
41
- params: "schema" extends keyof RouteList[K] ? ExtractSchema<RouteList[K]> | null : null;
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
- getCurrentRoute: (router: SimplyNextRouter) => CurrentRoute<RouteList>;
50
- useActiveRoute: () => ActiveRoute<Locales, RouteList>;
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 ActiveRoute<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = {
64
- route: keyof RouteList;
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 getCurrentRoute = (router) => {
75
- const activeRoute = Object.keys(routes).find((route) => typeof routes[route].path === "string"
76
- ? routes[route].path === router.pathname
77
- : Object.values(routes[route].path).includes(router.pathname));
78
- if (!activeRoute) {
79
- throw new Error("Active route not found.");
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
- const schema = routes[activeRoute].schema;
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
- const router = (0, router_1.useRouter)();
119
- const schema = routes[routeName].schema;
120
- if (!schema) {
121
- throw new Error(`Route '${String(routeName)}' has no schema.`);
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)(router.query, schema),
140
+ (0, superstruct_1.mask)(pageParams, schema),
128
141
  {
129
- push: (params, options) => router.push(routeToUrl(routeName, params, {}), undefined, options),
130
- replace: (params, options) => router.replace(routeToUrl(routeName, params, {}), undefined, options),
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, router_1.useRouter)();
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
- router.isReady ? (0, superstruct_1.mask)(router.query, schema) : null,
161
+ (0, is_not_nil_1.isNotNil)(pageParams) ? (0, superstruct_1.mask)(pageParams, schema) : null,
142
162
  {
143
- push: (params) => router.push(routeToUrl(routeName, params, {})),
144
- replace: (params) => router.replace(routeToUrl(routeName, params, {})),
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 (router) => {
152
- const { route, params } = getCurrentRoute(router);
153
- if (route !== requiredRouteName) {
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 ((params === null || params === void 0 ? void 0 : params[paramName]) !== paramValue) {
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
- isReady: true,
143
- pathname: "/many-parameters/[param1]/form/[param2]",
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
- isReady: true,
148
- pathname: "/many-parameters/[param1]/form/[param2]",
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", queryParam1: "query-1" })({
152
- isReady: true,
153
- pathname: "/many-parameters/[param1]/form/[param2]",
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", queryParam1: "query-1" });
159
- const routeMatcher2 = createRouteMatcher("manyParameters", { param1: "value-2", queryParam1: "query-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
- pathname: "/many-parameters/[param1]/form/[param2]",
163
- query: { param1: "value-1", queryParam1: "query-1", param2: "any" },
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
- pathname: "/many-parameters/[param1]/form/[param2]",
168
- query: { param1: "value-2", queryParam1: "query-2", param2: "any" },
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
- pathname: "/many-parameters/[param1]/form/[param2]",
173
- query: { param1: "value-3", queryParam1: "query-3", param2: "any" },
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 = pathParts.splice(-1);
36
- const pagePath = pathParts.length > 0
37
- ? (0, path_1.join)(options.pagesDir, ...pathParts, `${lastPart[0] || "index"}.ts`)
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, ...pathname.substr(1).split("/"), "page.ts"))) {
43
- // app directory
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 RoutesDefinition<Locales extends string[]> = Record<string, {
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
+ });