Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/routes.test.ts
T

163 lines
7.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { remixConfigRoutes } from "@react-router/remix-config-routes-adapter";
import { defaultDisabledPluginRouteFilePatterns, defaultIgnoredRouteFilePatterns } from "./routes";
import { createRoutesFromFolders } from "./utils/compat/v1-route-convention";
import { resolveRepoPath } from "./utils/testing/repoPaths";
const routes = remixConfigRoutes(async (defineRoutes) => {
return createRoutesFromFolders(defineRoutes, {
appDirectory: resolveRepoPath("apps/web/app"),
ignoredFilePatterns: [...defaultIgnoredRouteFilePatterns, ...defaultDisabledPluginRouteFilePatterns],
});
});
type RouteManifest = Awaited<typeof routes>;
type RouteManifestEntry = RouteManifest[number];
function flattenRoutes(manifest: RouteManifest) {
const flattened: RouteManifestEntry[] = [];
const visit = (entry: RouteManifestEntry) => {
flattened.push(entry);
Object.values(entry.children ?? {}).forEach(visit);
};
Object.values(manifest).forEach(visit);
return flattened;
}
describe("app route discovery", () => {
it("excludes colocated test and spec files from the production route manifest", async () => {
const routeFiles = flattenRoutes(await routes).map((entry) => entry.file);
expect(routeFiles).not.toEqual(expect.arrayContaining([expect.stringMatching(/\.test\.[tj]sx?$/), expect.stringMatching(/\.spec\.[tj]sx?$/)]));
expect(routeFiles).not.toEqual(expect.arrayContaining([expect.stringMatching(/\.server\.[tj]sx?$/)]));
expect(routeFiles).toEqual(expect.arrayContaining(["routes/forgot-password.tsx", "routes/invitation.$id.tsx"]));
expect(routeFiles).toEqual(expect.arrayContaining(["routes/401.tsx", "routes/403.tsx", "routes/404.tsx", "routes/500.tsx"]));
expect(routeFiles).toEqual(expect.arrayContaining(["routes/plan-required.$tenant.tsx", "routes/feature-disabled.tsx"]));
});
it("exposes promoted tenant app routes without legacy /app prefixes", async () => {
const routePaths = flattenRoutes(await routes)
.map((entry) => entry.path)
.filter((path): path is string => typeof path === "string");
expect(routePaths).not.toEqual(expect.arrayContaining(["app/:tenant"]));
expect(routePaths).not.toEqual(expect.arrayContaining([expect.stringMatching(/^app\/:tenant\//)]));
expect(routePaths).toEqual(expect.arrayContaining([":tenant"]));
expect(routePaths).toEqual(expect.arrayContaining(["plan-required/:tenant"]));
});
it("exposes admin routes only through admin-host aliases", async () => {
const flattenedRoutes = flattenRoutes(await routes);
const legacyAdminRoutes = flattenedRoutes.filter((entry) => (entry.id === "routes/admin" || entry.id.startsWith("routes/admin/")) && !entry.id.endsWith(".__host"));
const adminHostRoutes = flattenedRoutes.filter((entry) => entry.id === "routes/admin.__host" || (entry.id.startsWith("routes/admin/") && entry.id.endsWith(".__host")));
const routePaths = flattenedRoutes
.map((entry) => entry.path)
.filter((path): path is string => typeof path === "string");
expect(legacyAdminRoutes).toEqual([]);
expect(adminHostRoutes.length).toBeGreaterThan(0);
expect(routePaths).not.toEqual(expect.arrayContaining(["admin", expect.stringMatching(/^admin\//)]));
});
it("does not expose the removed public pricing marketing routes", async () => {
const flattenedRoutes = flattenRoutes(await routes);
const routeFiles = flattenedRoutes.map((entry) => entry.file);
const routePaths = flattenedRoutes
.map((entry) => entry.path)
.filter((path): path is string => typeof path === "string");
expect(routeFiles).not.toEqual(expect.arrayContaining(["routes/pricing.tsx", "routes/pricing.$session.success.tsx"]));
expect(routePaths).not.toEqual(expect.arrayContaining(["pricing", "pricing/:session/success"]));
expect(routePaths).toEqual(expect.arrayContaining(["subscribe/:tenant"]));
});
it("registers product-core tenant routes and keeps disabled plugin route trees out of the core app", async () => {
const flattenedRoutes = flattenRoutes(await routes);
const routeFiles = flattenedRoutes.map((entry) => entry.file);
const routePaths = flattenedRoutes
.map((entry) => entry.path)
.filter((path): path is string => typeof path === "string");
expect(routeFiles).not.toEqual(
expect.arrayContaining([
expect.stringMatching(/^routes\/admin\/(?:__workflows|ai|chat|crm|email-marketing|help-desk|internal\/ai|knowledge-base|prompts)(?:\.tsx|\/)/),
expect.stringMatching(/^routes\/admin\/portals\.tsx$/),
expect.stringMatching(/^routes\/app\.\$tenant\/(?:__portals|__widgets|chat|internal\/ai|sites)(?:\.tsx|\/)/),
expect.stringMatching(/^routes\/help\//),
expect.stringMatching(/^routes\/internal\/api\/__workflows\//),
expect.stringMatching(/^routes\/mcp\//),
])
);
expect(routeFiles).toEqual(
expect.arrayContaining([
"routes/app.$tenant/crm.tsx",
"routes/app.$tenant/email-marketing.tsx",
"routes/app.$tenant/__workflows/workflow-engine.tsx",
])
);
expect(routePaths).not.toEqual(
expect.arrayContaining([
"ai",
"chat",
"help-desk",
"knowledge-base",
"mcp",
"prompts",
"portals",
"help/:slug/:lang?/articles/:article",
"help/:slug/:lang?/categories/:category",
"help/:slug/:lang?",
"internal/api/workflows",
"internal/api/workflows/run/:id",
"internal/api/workflows/executions/:id",
"internal/api/workflows/executions/:id/continue",
"internal/api/workflows/stream/:executionId",
":tenant/chat",
":tenant/sites",
":tenant/portals",
":tenant/portals/:portal",
":tenant/widgets",
])
);
expect(routePaths).toEqual(expect.arrayContaining(["crm", "email-marketing", "workflow-engine"]));
});
it("does not keep internal workflow API proxy targets registered", async () => {
const flattenedRoutes = flattenRoutes(await routes);
const routeFiles = flattenedRoutes.map((entry) => entry.file);
const routePaths = flattenedRoutes
.map((entry) => entry.path)
.filter((path): path is string => typeof path === "string");
expect(routeFiles).not.toEqual(
expect.arrayContaining([
"routes/internal/api/__workflows/workflows.run.$id.tsx",
"routes/internal/api/__workflows/workflows.executions.$id.tsx",
"routes/internal/api/__workflows/workflows.executions.$id.continue.tsx",
"routes/internal/api/__workflows/workflows.executions.$id.continue.start.tsx",
"routes/internal/api/__workflows/workflows.stream.$executionId.tsx",
])
);
expect(routePaths).not.toEqual(
expect.arrayContaining([
"internal/api/workflows/run/:id",
"internal/api/workflows/executions/:id",
"internal/api/workflows/executions/:id/continue",
"internal/api/workflows/executions/:id/continue/start",
"internal/api/workflows/stream/:executionId",
])
);
});
it("does not expose removed fake/demo admin entity routes", async () => {
const flattenedRoutes = flattenRoutes(await routes);
const routeFiles = flattenedRoutes.map((entry) => entry.file);
const routePaths = flattenedRoutes
.map((entry) => entry.path)
.filter((path): path is string => typeof path === "string");
expect(routeFiles).not.toEqual(expect.arrayContaining(["routes/admin/entities/fake-rows.tsx"]));
expect(routePaths).not.toEqual(expect.arrayContaining(["entities/fake-rows"]));
});
});