alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
260 lines (224 loc) • 6.41 kB
text/typescript
import { describe, it } from "vitest";
import { RouterProvider } from "../providers/RouterProvider.ts";
interface TestRoute {
name: string;
path: string;
}
class TestRouterProvider extends RouterProvider<TestRoute> {
public push(route: TestRoute) {
super.push(route);
}
}
const playground = () => {
const router = new TestRouterProvider();
const add = (path: string, name: string) => {
router.push({
path,
name,
});
};
const match = (path: string) => {
const { route, params } = router.match(`${path}?a=b`);
return {
name: route?.name ?? "null",
params: params,
};
};
return {
add,
match,
router,
};
};
describe("RouterProvider", () => {
it("should match routes with static paths, params, and wildcards", ({
expect,
}) => {
const { add, match } = playground();
add("/", "home");
add("/about", "about");
add("/dist/", "dist");
add("/users", "users");
add("/users/:name", "users-by-name");
add("/users/:name/INFO", "users-by-name-info");
add("/users/:name/x/y/z", "users-by-name-x-y-z");
add("/useRs/:name/x/*", "users-by-name-x-not-found");
add("/*", "not-found");
expect(match("/")).toEqual({
name: "home",
params: {},
});
expect(match("/about")).toEqual({
name: "about",
params: {},
});
expect(match("/dist")).toEqual({
name: "dist",
params: {},
});
expect(match("/dist/")).toEqual({
name: "dist",
params: {},
});
expect(match("/users/jack")).toEqual({
name: "users-by-name",
params: {
name: "jack",
},
});
expect(match("/users/jack/info")).toEqual({
name: "users-by-name-info",
params: {
name: "jack",
},
});
expect(match("/users/jack/info/other")).toEqual({
name: "not-found",
params: {
name: "jack",
},
});
expect(match("/users/JACK/x")).toEqual({
name: "users-by-name-x-not-found",
params: {
name: "JACK",
},
});
expect(match("/users/jack/x/y")).toEqual({
name: "users-by-name-x-not-found",
params: {
name: "jack",
},
});
expect(match("/users/jack/x/y/z")).toEqual({
name: "users-by-name-x-y-z",
params: {
name: "jack",
},
});
expect(match("/users/jack/x/abc/def")).toEqual({
name: "users-by-name-x-not-found",
params: {
"*": "abc/def",
name: "jack",
},
});
expect(match("/weird")).toEqual({
name: "not-found",
params: {
"*": "weird",
},
});
});
it("should return null for paths with no matching routes", ({ expect }) => {
const { match } = playground();
expect(match("/")).toEqual({
name: "null",
params: {},
});
expect(match("/abc/def")).toEqual({
name: "null",
params: {},
});
});
it("should throw error for invalid path formats", ({ expect }) => {
const { match } = playground();
expect(() => match("x")).toThrowError();
expect(() => match("(*__*)")).toThrowError();
});
it("should match only wildcard route", ({ expect }) => {
const { add, match } = playground();
add("/*", "wildcard");
expect(match("/")).toEqual({
name: "wildcard",
params: {
"*": "",
},
});
});
it("should throw error when adding invalid path", ({ expect }) => {
const { add } = playground();
expect(() => add("*", "wildcard")).toThrowError();
});
it("should handle routes with params and wildcard", ({ expect }) => {
const { add, match } = playground();
add("/users/*", "users-not-found");
add("/users/:name/x/*", "users-by-name-x-not-found");
expect(match("/users/jack/x/y/z")).toEqual({
name: "users-by-name-x-not-found",
params: {
name: "jack",
"*": "y/z",
},
});
});
it("should return a fresh params object on each match (no shared mutation)", ({
expect,
}) => {
const { add, router } = playground();
add("/users/:id", "user");
const a = router.match("/users/42");
const b = router.match("/users/42");
expect(a.params).not.toBe(b.params);
a.params!.injected = "x";
expect(b.params).toEqual({ id: "42" });
});
it("should cache by pathname only (ignoring query string)", ({ expect }) => {
const { add, router } = playground();
add("/search", "search");
router.match("/search?q=1");
router.match("/search?q=2");
router.match("/search?q=3");
// cache is protected; assert via Map size on the subclass
expect(
(router as unknown as { cache: Map<string, unknown> }).cache.size,
).toBe(1);
});
it("should cap the cache size to avoid unbounded growth", ({ expect }) => {
const { add, router } = playground();
add("/*", "catchall");
(router as unknown as { maxCacheSize: number }).maxCacheSize = 5;
for (let i = 0; i < 20; i++) {
router.match(`/x/${i}`);
}
const size = (router as unknown as { cache: Map<string, unknown> }).cache
.size;
expect(size).toBeLessThanOrEqual(5);
});
it("should invalidate the cache when a new route is pushed", ({ expect }) => {
const { add, router } = playground();
const m1 = router.match("/new");
expect(m1.route).toBeUndefined();
add("/new", "new-route");
const m2 = router.match("/new");
expect(m2.route?.name).toBe("new-route");
});
it("should handle three-way conflicting param names at the same level", ({
expect,
}) => {
const { add, match } = playground();
add("/u/:a", "a-route");
add("/u/:b/x", "b-route");
add("/u/:c/y", "c-route");
expect(match("/u/1")).toEqual({ name: "a-route", params: { a: "1" } });
expect(match("/u/1/x")).toEqual({ name: "b-route", params: { b: "1" } });
expect(match("/u/1/y")).toEqual({ name: "c-route", params: { c: "1" } });
});
it("should handle routes with different param names", ({ expect }) => {
const { add, match } = playground();
add("/users/:id", "home");
add("/users/:userId/hello", "hello");
expect(match("/users/jack")).toEqual({
name: "home",
params: {
id: "jack",
},
});
expect(match("/users/jack/hello")).toEqual({
name: "hello",
params: {
userId: "jack",
},
});
});
});