one
Version:
One is a new React Framework that makes Vite serve both native and web.
115 lines (114 loc) • 3.99 kB
JavaScript
import React from "react";
import TestRenderer, { act } from "react-test-renderer";
import { describe, expect, it } from "vitest";
import { usePathname } from "../hooks.mjs";
import { Root } from "../Root.mjs";
import { Slot } from "../views/Navigator.mjs";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
globalThis.requestAnimationFrame ??= cb => setTimeout(() => cb(0), 0);
globalThis.cancelAnimationFrame ??= id => clearTimeout(id);
const routerRoot = "app";
function makeRoutes(modules) {
globalThis["__vxrnVersion"] = (globalThis["__vxrnVersion"] ?? 0) + 1;
const routes = {};
for (const [file, module] of Object.entries(modules)) {
routes[`/${routerRoot}/${file}`] = module;
}
return routes;
}
const slotLayout = {
default: () => /* @__PURE__ */jsx(Slot, {})
};
const leaf = label => ({
default: () => label
});
function routeTreeWithGate(gate) {
const Gate = gate;
return makeRoutes({
"_layout.tsx": slotLayout,
"(app)/_layout.tsx": {
default: () => /* @__PURE__ */jsx(Gate, {
children: /* @__PURE__ */jsx(Slot, {})
})
},
"(app)/index.tsx": leaf("app-index"),
"(app)/project/_layout.tsx": slotLayout,
"(app)/project/[projectId]/_layout.tsx": slotLayout,
"(app)/project/[projectId]/[sessionId]/_layout.tsx": slotLayout,
"(app)/project/[projectId]/[sessionId]/index.tsx": leaf("session"),
"(site)/_layout.tsx": slotLayout,
"(site)/pricing.tsx": leaf("pricing")
});
}
function renderAt(path, routes) {
let renderer;
act(() => {
renderer = TestRenderer.create(/* @__PURE__ */jsx(Root, {
path,
routes,
routerRoot,
isClient: false
}));
});
return renderer;
}
function bodyText(renderer) {
const collect = node => typeof node === "string" ? node : Array.isArray(node) ? node.map(collect).join("") : node ? collect(node.children ?? []) : "";
return collect(renderer.toJSON());
}
describe("a layout that renders something other than its <Slot />", () => {
it("does not move the pathname while the URL stands still", () => {
const pathnames = [];
let setBlocked;
function Probe() {
const pathname = usePathname();
if (pathnames[pathnames.length - 1] !== pathname) pathnames.push(pathname);
return null;
}
function Gate({
children
}) {
const [blocked, set] = React.useState(true);
setBlocked = set;
return /* @__PURE__ */jsxs(Fragment, {
children: [/* @__PURE__ */jsx(Probe, {}), blocked ? "placeholder" : children]
});
}
const renderer = renderAt("/project/p1/main", routeTreeWithGate(Gate));
expect(bodyText(renderer)).toBe("placeholder");
act(() => setBlocked(false));
expect(bodyText(renderer)).toBe("session");
act(() => setBlocked(true));
expect(bodyText(renderer)).toBe("placeholder");
act(() => setBlocked(false));
expect(pathnames).toEqual(["/project/p1/main"]);
});
it("settles when the gate itself reads the pathname", async () => {
let renders = 0;
let requestBlock;
function Gate({
children
}) {
const pathname = usePathname();
const [blockRequested, setBlockRequested] = React.useState(false);
requestBlock = () => setBlockRequested(true);
renders++;
if (renders > 100) {
throw new Error(`gate rendered ${renders} times without settling`);
}
const blocked = blockRequested && pathname.startsWith("/project/");
return /* @__PURE__ */jsx(Fragment, {
children: blocked ? "blocked" : children
});
}
const renderer = renderAt("/project/p1/main", routeTreeWithGate(Gate));
await act(async () => {});
expect(bodyText(renderer)).toBe("session");
await act(async () => requestBlock());
await act(async () => {});
expect(bodyText(renderer)).toBe("blocked");
expect(renders).toBeLessThan(20);
});
});
//# sourceMappingURL=conditionalSlotPathname.test.mjs.map