feat(catalog): add saved packages, preview and bulk price editing
This commit is contained in:
1 parent
867113d5d4
commit
047cd9f3dd
48 files changed
+7712
No files matched your search
@@ -0,0 +1,164 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user