Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions packages/react-router/__tests__/generatePath-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,4 +234,52 @@ describe("generatePath", () => {
);
});
});

describe("numeric parameters", () => {
it("supports required numeric parameters", () => {
expect(generatePath("/courses/:id", { id: 123 })).toBe("/courses/123");
expect(generatePath("/courses/*", { "*": 123 })).toBe("/courses/123");
});

it("supports multiple required numeric parameters", () => {
expect(
generatePath("/courses/:id/student/:studentId", {
id: 123,
studentId: 456,
}),
).toBe("/courses/123/student/456");
});

it("supports optional numeric parameters", () => {
let path = "/:one?/:two?/:three?";
expect(generatePath(path, { one: 1 })).toBe("/1");
expect(generatePath(path, { one: 1, two: 2 })).toBe("/1/2");
expect(generatePath(path, { one: 1, three: 3 })).toBe("/1/3");
});

it("supports mixed string/number parameters", () => {
expect(
generatePath("/courses/:id/student/:studentId", {
id: 123,
studentId: "matt",
}),
).toBe("/courses/123/student/matt");

expect(
generatePath("/courses/:id/student/:studentId", {
id: "routing",
studentId: 456,
}),
).toBe("/courses/routing/student/456");
});

it("maintains existing string behavior", () => {
expect(generatePath("/courses/:id", { id: "routing" })).toBe(
"/courses/routing",
);
expect(generatePath("/courses/*", { "*": "routing/grades" })).toBe(
"/courses/routing/grades",
);
});
});
});
30 changes: 15 additions & 15 deletions packages/react-router/lib/router/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -801,15 +801,15 @@ type Simplify<T> = { [K in keyof T]: T[K] } & {};
// prettier-ignore
type GeneratePathParams<path extends string> = Simplify<
& ParseParams<path>
& { [key in string]: string | null | undefined }
& { [key in string]: string | number | null | undefined }
>

// prettier-ignore
type ParseParams<path extends string> =
// check if path is just a wildcard
path extends '*' ? { '*': string } :
path extends '*' ? { '*': string | number } :
// look for wildcard at the end of the path
path extends `${infer rest}/*` ? { '*': string } & ParseParams<rest> :
path extends `${infer rest}/*` ? { '*': string | number } & ParseParams<rest> :
// look for params in the absence of wildcards
_ParseParams<path>;

Expand All @@ -820,10 +820,10 @@ type _ParseParams<path extends string> =
_ParseParams<left> & _ParseParams<right> :
// look for optional param in segment
path extends `:${infer param}?${string}` ?
{ [key in RegexMatchPlus<ParamNameChar, param>]?: string | null | undefined } :
{ [key in RegexMatchPlus<ParamNameChar, param>]?: string | number | null | undefined } :
// look for required param in segment
path extends `:${infer param}` ?
{ [key in RegexMatchPlus<ParamNameChar, param>]: string } :
{ [key in RegexMatchPlus<ParamNameChar, param>]: string | number } :
{};

// prettier-ignore
Expand All @@ -842,25 +842,25 @@ type _tests = [
Expect<Equal<PathParam<"/:lang?.xml">, "lang">>,

// ParseParams
Expect<Equal<ParseParams<"/a/b/*">, { "*": string }>>,
Expect<Equal<ParseParams<":a">, { a: string }>>,
Expect<Equal<ParseParams<"/a/:b">, { b: string }>>,
Expect<Equal<ParseParams<"/a/b/*">, { "*": string | number }>>,
Expect<Equal<ParseParams<":a">, { a: string | number }>>,
Expect<Equal<ParseParams<"/a/:b">, { b: string | number }>>,
Expect<Equal<ParseParams<"/a/blahblahblah:b">, {}>>,
Expect<Equal<Simplify<ParseParams<"/:a/:b">>, { a: string; b: string }>>,
Expect<Equal<Simplify<ParseParams<"/:a/:b">>, { a: string | number; b: string | number }>>,
Expect<
Equal<
Simplify<ParseParams<"/:a/b/:c/*">>,
{ a: string; c: string; "*": string }
{ a: string | number; c: string | number; "*": string | number }
>
>,
Expect<Equal<ParseParams<"/:lang.xml">, { lang: string }>>,
Expect<Equal<ParseParams<"/:lang.xml">, { lang: string | number }>>,
Expect<
Equal<ParseParams<"/:lang?.xml">, { lang?: string | null | undefined }>
Equal<ParseParams<"/:lang?.xml">, { lang?: string | number | null | undefined }>
>,
Expect<Equal<Simplify<ParseParams<"/:a/:a">>, { a: string }>>,
Expect<Equal<Simplify<ParseParams<"/:a/:a?">>, { a: string }>>,
Expect<Equal<Simplify<ParseParams<"/:a/:a">>, { a: string | number }>>,
Expect<Equal<Simplify<ParseParams<"/:a/:a?">>, { a: string | number }>>,
Expect<
Equal<Simplify<ParseParams<"/:a?/:a?">>, { a?: string | null | undefined }>
Equal<Simplify<ParseParams<"/:a?/:a?">>, { a?: string | number | null | undefined }>
>,
];

Expand Down
Loading