diff --git a/__tests__/matcher.ts b/__tests__/matcher.ts index 3f74472d..b9279b18 100644 --- a/__tests__/matcher.ts +++ b/__tests__/matcher.ts @@ -1,31 +1,22 @@ +import { randomUUID } from "crypto"; import { expect, test } from "vitest"; import { decodeLocation } from "../src/history"; import { getMatcher, match } from "../src/matcher"; -const getMatcherEqual = (name: string, route: string, expected: E) => - expect(getMatcher(name, route)).toStrictEqual({ name, ...expected }); - -const matchers = [ - getMatcher("Groups", "/groups"), - getMatcher("Group", "/groups/:groupId"), - getMatcher("MyGroup", "/groups/mine"), - getMatcher("UsersArea", "/groups/:groupId/users/*"), - getMatcher("Users", "/groups/:groupId/users"), - getMatcher("Project", "/projects/:projectId/:env{live|sandbox}"), -].sort((a, b) => b.ranking - a.ranking); // we sort the matchers since match doesn't do it at each call - -const matchEqual = (path: string, expected: E) => - expect(match(decodeLocation(path), matchers)).toStrictEqual(expected); +const getMatcherEqual = (route: string, expected: E) => { + const name = randomUUID(); + return expect(getMatcher(name, route)).toStrictEqual({ name, ...expected }); +}; test("getMatcher returns a proper matcher structure for paths without params", () => { - getMatcherEqual("Groups", "/groups", { + getMatcherEqual("/groups", { isArea: false, ranking: 9, path: ["groups"], search: undefined, }); - getMatcherEqual("MyGroup", "/groups/mine", { + getMatcherEqual("/groups/mine", { isArea: false, ranking: 18, path: ["groups", "mine"], @@ -34,21 +25,21 @@ test("getMatcher returns a proper matcher structure for paths without params", ( }); test("getMatcher returns a proper matcher structure for paths with params (in path only)", () => { - getMatcherEqual("Group", "/group/:groupId", { + getMatcherEqual("/group/:groupId", { isArea: false, ranking: 16, path: ["group", { name: "groupId" }], search: undefined, }); - getMatcherEqual("Users", "/groups/:groupId/users", { + getMatcherEqual("/groups/:groupId/users", { isArea: false, ranking: 25, path: ["groups", { name: "groupId" }, "users"], search: undefined, }); - getMatcherEqual("Project", "/projects/:projectId/:env{live|sandbox}", { + getMatcherEqual("/projects/:projectId/:env{live|sandbox}", { isArea: false, ranking: 24, path: [ @@ -61,14 +52,17 @@ test("getMatcher returns a proper matcher structure for paths with params (in pa }); test("getMatcher returns a proper matcher structure for paths with params (in path and search)", () => { - getMatcherEqual("Group", "/group/:groupId?:foo&:bar[]#:baz", { + getMatcherEqual("/group/:groupId?:foo&:bar[]#:baz", { isArea: false, ranking: 16, path: ["group", { name: "groupId" }], - search: { foo: { multiple: false }, bar: { multiple: true } }, + search: { + foo: { multiple: false }, + bar: { multiple: true }, + }, }); - getMatcherEqual("Group", "/group/:groupId?:foo{a|b}&:bar{c|d}[]#:baz{e|f}", { + getMatcherEqual("/group/:groupId?:foo{a|b}&:bar{c|d}[]#:baz{e|f}", { isArea: false, ranking: 16, path: ["group", { name: "groupId" }], @@ -80,28 +74,72 @@ test("getMatcher returns a proper matcher structure for paths with params (in pa }); test("getMatcher decrements the ranking by 1 if the path is an area", () => { - getMatcherEqual("UsersArea", "/groups/:groupId/users/*", { + getMatcherEqual("/groups/:groupId/users/*", { isArea: true, ranking: 24, path: ["groups", { name: "groupId" }, "users"], search: undefined, }); - getMatcherEqual("UsersArea", "/groups/:groupId/users/*?:foo&:bar[]", { + getMatcherEqual("/groups/:groupId/users/*?:foo&:bar[]", { isArea: true, ranking: 24, path: ["groups", { name: "groupId" }, "users"], - search: { foo: { multiple: false }, bar: { multiple: true } }, + search: { + foo: { multiple: false }, + bar: { multiple: true }, + }, + }); + + getMatcherEqual("/groups/:groupId/users", { + isArea: false, + ranking: 25, + path: ["groups", { name: "groupId" }, "users"], + search: undefined, }); - getMatcherEqual("Users", "/groups/:groupId/users", { + getMatcherEqual("/groups/:groupId/users", { isArea: false, ranking: 25, path: ["groups", { name: "groupId" }, "users"], search: undefined, }); + + getMatcherEqual( + "/groups?:orderBy{asc|desc}&:status{disabled|enabled|pending}[]", + { + isArea: false, + ranking: 9, + path: ["groups"], + search: { + orderBy: { + multiple: false, + values: ["asc", "desc"], + }, + status: { + multiple: true, + values: ["disabled", "enabled", "pending"], + }, + }, + }, + ); }); +const matchers = [ + getMatcher( + "Groups", + "/groups?:orderBy{asc|desc}&:status{disabled|enabled|pending}[]", + ), + getMatcher("Group", "/groups/:groupId"), + getMatcher("MyGroup", "/groups/mine"), + getMatcher("UsersArea", "/groups/:groupId/users/*"), + getMatcher("Users", "/groups/:groupId/users"), + getMatcher("Project", "/projects/:projectId/:env{live|sandbox}"), +].sort((a, b) => b.ranking - a.ranking); // we sort the matchers since match doesn't do it at each call + +const matchEqual = (path: string, expected: E) => + expect(match(decodeLocation(path), matchers)).toStrictEqual(expected); + test("match extract route params and matches against a matcher", () => { matchEqual("/groups", { name: "Groups", @@ -128,6 +166,36 @@ test("match extract route params and matches against a matcher", () => { params: { groupId: "github" }, }); + matchEqual("/groups?orderBy=asc", { + name: "Groups", + params: { orderBy: "asc" }, + }); + + matchEqual("/groups?orderBy=asc&orderBy=desc", { + name: "Groups", + params: { orderBy: "asc" }, + }); + + matchEqual("/groups?orderBy=invalid", { + name: "Groups", + params: {}, + }); + + matchEqual("/groups?orderBy=invalid&orderBy=desc", { + name: "Groups", + params: { orderBy: "desc" }, + }); + + matchEqual("/groups?status=disabled&status=pending", { + name: "Groups", + params: { status: ["disabled", "pending"] }, + }); + + matchEqual("/groups?status=invalid&status=pending", { + name: "Groups", + params: { status: ["pending"] }, + }); + matchEqual("/projects/swan/live", { name: "Project", params: { projectId: "swan", env: "live" }, @@ -137,5 +205,5 @@ test("match extract route params and matches against a matcher", () => { test("match returns undefined in case of no route match", () => { matchEqual("/repositories/:repositoryId", undefined); matchEqual("/bills/:billId", undefined); - matchEqual("/projects/swan/unexpected", undefined); + matchEqual("/projects/swan/invalid", undefined); }); diff --git a/src/matcher.ts b/src/matcher.ts index 599e391d..5718fa48 100644 --- a/src/matcher.ts +++ b/src/matcher.ts @@ -132,7 +132,11 @@ export const extractLocationParams = ( } const locationValue = - typeof locationPart === "string" ? locationPart : locationPart[0]; + typeof locationPart === "string" + ? locationPart + : values == null + ? locationPart[0] + : locationPart.filter((item) => values.includes(item))[0]; if ( locationValue != null &&