160 lines
4.5 KiB
TypeScript
160 lines
4.5 KiB
TypeScript
import { renderToStaticMarkup } from "react-dom/server";
|
|
import { describe, expect, it } from "vitest";
|
|
import { PERMS } from "@/lib/permission-slugs";
|
|
import {
|
|
fail,
|
|
type HousekeepingCapabilityContext,
|
|
ok,
|
|
} from "../../../foundation/contracts";
|
|
import { EconomyCatalogPage } from "./catalog";
|
|
import { EconomyCommercePage } from "./commerce";
|
|
import { EconomyHistoryPage } from "./history";
|
|
import { EconomyItemsPage } from "./items";
|
|
import { EconomyRewardsPage } from "./rewards";
|
|
import { EconomyValuePage } from "./value";
|
|
|
|
function context(granted: readonly string[]): HousekeepingCapabilityContext {
|
|
const permissions = new Set(granted);
|
|
return {
|
|
actor: { id: 42, username: "operator", rank: 7 },
|
|
isSuperAdmin: false,
|
|
has: (slug) => permissions.has(slug),
|
|
hasAny: (...slugs) => slugs.some((slug) => permissions.has(slug)),
|
|
hasAll: (...slugs) => slugs.every((slug) => permissions.has(slug)),
|
|
};
|
|
}
|
|
|
|
const cases = [
|
|
["catalog", EconomyCatalogPage],
|
|
["items", EconomyItemsPage],
|
|
["commerce", EconomyCommercePage],
|
|
["history", EconomyHistoryPage],
|
|
["value", EconomyValuePage],
|
|
["rewards", EconomyRewardsPage],
|
|
] as const;
|
|
|
|
describe.each(cases)("Economy %s page", (kind, Component) => {
|
|
it("renders loading, forbidden, empty, and canonical ready states", () => {
|
|
const render = (result?: unknown) =>
|
|
renderToStaticMarkup(
|
|
<Component
|
|
context={context(Object.values(PERMS))}
|
|
result={result as never}
|
|
routeId={null}
|
|
/>,
|
|
);
|
|
expect(render()).toContain('data-housekeeping-state="loading"');
|
|
expect(
|
|
render(fail("FORBIDDEN", "errors.housekeeping.forbidden", "denied")),
|
|
).toContain('data-housekeeping-state="forbidden"');
|
|
expect(
|
|
render(
|
|
ok({ kind, items: [], total: 0, partialDependencies: [] }, "empty"),
|
|
),
|
|
).toContain('data-housekeeping-state="empty"');
|
|
const ready = render(
|
|
ok(
|
|
{
|
|
kind,
|
|
items: [
|
|
{
|
|
id: "18446744073709551615",
|
|
title: "Canonical economy item",
|
|
status: "active",
|
|
href: `/ase-next/economy/${kind}`,
|
|
},
|
|
],
|
|
total: 1,
|
|
partialDependencies: [],
|
|
},
|
|
"ready",
|
|
),
|
|
);
|
|
expect(ready).toContain('data-housekeeping-state="ready"');
|
|
expect(ready).toContain("Canonical economy item");
|
|
expect(ready).not.toContain("/admin");
|
|
});
|
|
});
|
|
|
|
describe("Economy mutation forms", () => {
|
|
it("renders edit controls only with the exact mutation capability", () => {
|
|
const result = ok(
|
|
{
|
|
kind: "commerce" as const,
|
|
items: [{ id: "1", title: "Package", status: "active" }],
|
|
total: 1,
|
|
partialDependencies: [],
|
|
},
|
|
"shop",
|
|
);
|
|
const readOnly = renderToStaticMarkup(
|
|
<EconomyCommercePage
|
|
context={context([PERMS.SHOP_VIEW])}
|
|
result={result}
|
|
routeId="economy.commerce.shop"
|
|
/>,
|
|
);
|
|
const editor = renderToStaticMarkup(
|
|
<EconomyCommercePage
|
|
context={context([PERMS.SHOP_VIEW, PERMS.SHOP_EDIT])}
|
|
result={result}
|
|
routeId="economy.commerce.shop"
|
|
/>,
|
|
);
|
|
expect(readOnly).not.toContain("economy.commerce.shop-article.change");
|
|
expect(editor).toContain("economy.commerce.shop-article.change");
|
|
expect(editor).toContain("<form");
|
|
});
|
|
|
|
it("renders validator-shaped catalog, voucher, value, and soundtrack fields", () => {
|
|
const all = context(Object.values(PERMS));
|
|
const empty = (kind: "catalog" | "commerce" | "value" | "rewards") =>
|
|
ok({ kind, items: [], total: 0, partialDependencies: [] }, kind);
|
|
const catalog = renderToStaticMarkup(
|
|
<EconomyCatalogPage
|
|
context={all}
|
|
result={empty("catalog")}
|
|
routeId="economy.catalog.detail"
|
|
/>,
|
|
);
|
|
for (const field of ["id", "caption", "parentId", "pageLayout"]) {
|
|
expect(catalog).toContain(`name="${field}"`);
|
|
}
|
|
const commerce = renderToStaticMarkup(
|
|
<EconomyCommercePage
|
|
context={all}
|
|
result={empty("commerce")}
|
|
routeId="economy.commerce.vouchers"
|
|
/>,
|
|
);
|
|
for (const field of ["code", "amount", "maxUses", "expiresAt"]) {
|
|
expect(commerce).toContain(`name="${field}"`);
|
|
}
|
|
const value = renderToStaticMarkup(
|
|
<EconomyValuePage
|
|
context={all}
|
|
result={empty("value")}
|
|
routeId="economy.value.rare-values"
|
|
/>,
|
|
);
|
|
for (const field of [
|
|
"categoryId",
|
|
"name",
|
|
"currencyValue",
|
|
"currencyType",
|
|
]) {
|
|
expect(value).toContain(`name="${field}"`);
|
|
}
|
|
const rewards = renderToStaticMarkup(
|
|
<EconomyRewardsPage
|
|
context={all}
|
|
result={empty("rewards")}
|
|
routeId="economy.rewards.sounds"
|
|
/>,
|
|
);
|
|
for (const field of ["id", "name", "author", "track", "length"]) {
|
|
expect(rewards).toContain(`name="${field}"`);
|
|
}
|
|
});
|
|
});
|