import { MatchHandler, router, Routes } from "https://crux.land/router@0.0.5";
export type { MatchHandler };
class UnhandledRouteError extends Error { routes: Routes; request: Request; constructor(init: { request: Request; routes: Routes }) { const { request, routes } = init;
const method = request.method; const reqPath = new URL(request.url).pathname; const routesNumber = Object.entries(routes).length; const routePlural = routesNumber === 1 ? "route has a handler" : "routes have handlers";
super(`${method} ${reqPath} (${routesNumber} ${routePlural})`);
this.name = this.constructor.name; if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); }
this.routes = routes; this.request = request; }}
export interface MockFetch { fetch: typeof globalThis.fetch; mock: (route: string, handler: MatchHandler) => void; remove: (route: string) => void; reset: () => void;}
export function sandbox(): MockFetch { const routeStore = new Map<string, MatchHandler>();
async function fetch( input: string | Request | URL, init?: RequestInit, ): Promise<Response> { if (input instanceof URL) input = input.toString(); const req = new Request(input, init);
const routes = Object.fromEntries(routeStore.entries());
return await router( routes, (request) => { throw new UnhandledRouteError({ request, routes }); }, (_, error) => { throw error; }, )(req); }
function mock(route: string, handler: MatchHandler) { routeStore.set(route, handler); }
function remove(route: string) { routeStore.delete(route); }
function reset() { routeStore.clear(); }
return { reset, mock, remove, fetch, };}
const globalMockFetch = sandbox();
export const mockedFetch = globalMockFetch.fetch;
export const mock = globalMockFetch.mock;
export const remove = globalMockFetch.remove;
export const reset = globalMockFetch.reset;
const originalFetch = globalThis.fetch;
export const install = (replacement?: typeof fetch) => { globalThis.fetch = replacement ?? mockedFetch;};
export const uninstall = () => { globalThis.fetch = originalFetch; reset();};