165 lines
4.4 KiB
TypeScript
165 lines
4.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
buildPackagePreview,
|
|
createPreviewAssetLoader,
|
|
parsePreviewItemIds,
|
|
} from "./catalog-package-preview-model";
|
|
|
|
const page = (id: number, parentId = -1, overrides = {}) => ({
|
|
id,
|
|
parentId,
|
|
caption: `Page ${id}`,
|
|
enabled: "1" as const,
|
|
visible: "1" as const,
|
|
iconImage: 0,
|
|
minRank: 1,
|
|
clubOnly: "0" as const,
|
|
vipOnly: "0" as const,
|
|
orderNum: id,
|
|
pageLayout: "default_3x3",
|
|
...overrides,
|
|
});
|
|
const profile = { playerMode: true, rank: 1, club: false, vip: false };
|
|
describe("package preview access", () => {
|
|
it("honors restrictions on every included ancestor", () => {
|
|
const result = buildPackagePreview(
|
|
{ pages: [page(1, -1, { minRank: 5 }), page(2, 1)], offers: [] },
|
|
profile,
|
|
"",
|
|
);
|
|
expect(result.pages).toEqual([]);
|
|
expect(result.restrictedCount).toBe(2);
|
|
});
|
|
it("handles hidden/disabled ancestors and club membership", () => {
|
|
for (const restriction of [
|
|
{ visible: "0" },
|
|
{ enabled: "0" },
|
|
{ clubOnly: "1" },
|
|
{ vipOnly: "1" },
|
|
]) {
|
|
expect(
|
|
buildPackagePreview(
|
|
{ pages: [page(1, -1, restriction), page(2, 1)], offers: [] },
|
|
profile,
|
|
"",
|
|
).pages,
|
|
).toEqual([]);
|
|
}
|
|
expect(
|
|
buildPackagePreview(
|
|
{ pages: [page(1, -1, { clubOnly: "1" })], offers: [] },
|
|
{ ...profile, vip: true },
|
|
"",
|
|
).pages,
|
|
).toHaveLength(1);
|
|
});
|
|
it("rejects cycles without hanging and reports external subtree boundaries", () => {
|
|
const result = buildPackagePreview(
|
|
{ pages: [page(1, 2), page(2, 1), page(3, 999)], offers: [] },
|
|
profile,
|
|
"",
|
|
);
|
|
expect(result.pages.map((p) => p.id)).toEqual([3]);
|
|
expect(result.invalidCount).toBe(2);
|
|
expect(result.externalParents).toBe(true);
|
|
});
|
|
it("bounds oversized snapshots before traversing", () => {
|
|
const result = buildPackagePreview(
|
|
{ pages: Array.from({ length: 500 }, (_, i) => page(i + 1)), offers: [] },
|
|
profile,
|
|
"",
|
|
);
|
|
expect(result.pages).toHaveLength(200);
|
|
expect(result.truncated).toBe(true);
|
|
});
|
|
it("preserves parent navigation when only a child offer matches", () => {
|
|
const result = buildPackagePreview(
|
|
{
|
|
pages: [page(1), page(2, 1)],
|
|
offers: [
|
|
{
|
|
id: 1,
|
|
pageId: 2,
|
|
catalogName: "Sofa",
|
|
itemIds: "4;5",
|
|
costCredits: 2,
|
|
costPoints: 0,
|
|
pointsType: 0,
|
|
amount: 1,
|
|
},
|
|
],
|
|
},
|
|
profile,
|
|
"sofa",
|
|
);
|
|
expect(result.pages.map((p) => p.id)).toEqual([1, 2]);
|
|
expect(result.matchingPageIds).toEqual([2]);
|
|
});
|
|
it("parses bundle IDs strictly without treating partial numbers as IDs", () => {
|
|
expect(parsePreviewItemIds("2; 3,4;2;abc;5x;-1;0")).toEqual([2, 3, 4]);
|
|
});
|
|
});
|
|
describe("preview asset requests", () => {
|
|
it("ignores older responses after changing selection", async () => {
|
|
const resolves: ((value: number[]) => void)[] = [];
|
|
const loader = createPreviewAssetLoader(
|
|
() => new Promise<number[]>((resolve) => resolves.push(resolve)),
|
|
);
|
|
const seen: number[][] = [];
|
|
const first = loader.run([1], (value) => seen.push(value));
|
|
const second = loader.run([2], (value) => seen.push(value));
|
|
resolves[1]([2]);
|
|
await second;
|
|
resolves[0]([1]);
|
|
await first;
|
|
expect(seen).toEqual([[2]]);
|
|
});
|
|
it("does not publish after unmount/cancellation", async () => {
|
|
let finish = (_value: number[]) => {};
|
|
const loader = createPreviewAssetLoader(
|
|
() =>
|
|
new Promise<number[]>((resolve) => {
|
|
finish = resolve;
|
|
}),
|
|
);
|
|
const seen: number[][] = [];
|
|
const pending = loader.run([1], (value) => seen.push(value));
|
|
loader.cancel();
|
|
finish([1]);
|
|
await pending;
|
|
expect(seen).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("offer membership access", () => {
|
|
it("filters Club offers separately from their public category and permits VIP", () => {
|
|
const snapshot = {
|
|
pages: [page(1)],
|
|
offers: [
|
|
{
|
|
id: 1,
|
|
pageId: 1,
|
|
catalogName: "Club chair",
|
|
clubOnly: "1" as const,
|
|
itemIds: "2",
|
|
costCredits: 1,
|
|
costPoints: 0,
|
|
pointsType: 0,
|
|
amount: 1,
|
|
},
|
|
],
|
|
};
|
|
const guest = buildPackagePreview(snapshot, profile, "");
|
|
expect(guest.pages).toHaveLength(1);
|
|
expect(guest.offers).toHaveLength(0);
|
|
expect(guest.restrictedOfferCounts[1]).toBe(1);
|
|
expect(
|
|
buildPackagePreview(snapshot, { ...profile, vip: true }, "").offers,
|
|
).toHaveLength(1);
|
|
expect(
|
|
buildPackagePreview(snapshot, { ...profile, playerMode: false }, "")
|
|
.offers,
|
|
).toHaveLength(1);
|
|
});
|
|
});
|