feat: complete TASK-WP5-03 asset allowlist
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 1m5s
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 1m5s
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { createServer, type ViteDevServer } from "vite";
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { join, resolve } from "node:path";
|
||||
import type { CanvasState } from "@dada/shared-contracts";
|
||||
import {
|
||||
P0A_COLOR_CARD_IDS,
|
||||
P0A_DYNAMIC_STICKER_IDS,
|
||||
P0A_REQUIRED_FONT_PANEL_IDS,
|
||||
P0A_TEXT_TEMPLATE_IDS,
|
||||
} from "../../packages/template-registry/src/index.js";
|
||||
|
||||
let vite: ViteDevServer;
|
||||
let webUrl: string;
|
||||
|
||||
const projectId = "00000000-0000-4000-8000-000000000903";
|
||||
const backgroundId = "00000000-0000-4000-8000-000000000904";
|
||||
const session = {
|
||||
audience: "user", authenticated: true, credits: { available_balance: 10, reserved_balance: 0 },
|
||||
csrf_token: "csrf-wp5-03-00000000000000000000000000000000000",
|
||||
expires_at: "2026-09-03T08:00:00.000Z",
|
||||
user: { creator_name: "Allowlist User", role: "user", social_id: "@allowlist", status: "active", user_id: "00000000-0000-4000-8000-000000000905" },
|
||||
};
|
||||
const png = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=", "base64");
|
||||
|
||||
interface Backend {
|
||||
canvas: CanvasState;
|
||||
saves: number;
|
||||
version: number;
|
||||
}
|
||||
|
||||
function canvas(background = true): CanvasState {
|
||||
return {
|
||||
background: {
|
||||
adjustments: { brightness: 0, contrast: 0, crop: null, filter: "none", fit: "fill", saturation: 0, sharpness: 0, temperature: 0 },
|
||||
asset_id: background ? backgroundId : null,
|
||||
},
|
||||
elements: [], pixel_height: 1440, pixel_width: 1080, ratio: "3:4", schema_version: 1,
|
||||
};
|
||||
}
|
||||
|
||||
function rawSvg() {
|
||||
const colors = ["#102030", "#405060", "#708090", "#A0B0C0", "#D0E0F0"];
|
||||
return `<svg xmlns="http://www.w3.org/2000/svg" width="250" height="250">${colors.map((color, index) => `<rect x="${index * 50}" width="50" height="250" fill="${color}"/>`).join("")}</svg>`;
|
||||
}
|
||||
|
||||
async function routeEditor(page: Page, backend: Backend) {
|
||||
const windowsFont = join(process.env.WINDIR ?? "C:\\Windows", "Fonts", "arial.ttf");
|
||||
if (!existsSync(windowsFont)) throw new Error("font fixture is unavailable");
|
||||
const fontBytes = readFileSync(windowsFont);
|
||||
await page.route("**/api/v1/auth/session", (route) => route.fulfill({ body: JSON.stringify(session), contentType: "application/json" }));
|
||||
await page.route(`**/api/v1/projects/${projectId}`, (route) => route.fulfill({
|
||||
body: JSON.stringify({
|
||||
canvas_state: backend.canvas, created_at: "2026-08-03T09:00:00.000Z", current_image_id: backend.canvas.background.asset_id,
|
||||
images: backend.canvas.background.asset_id ? [{ created_at: "2026-08-03T09:00:00.000Z", generation_id: "00000000-0000-4000-8000-000000000906", image_id: backgroundId }] : [],
|
||||
name: "P0-A 白名单", pixel_height: 1440, pixel_width: 1080, project_id: projectId, ratio: "3:4", state_version: backend.version,
|
||||
}), contentType: "application/json",
|
||||
}));
|
||||
await page.route(`**/api/v1/projects/${projectId}/state`, async (route) => {
|
||||
backend.canvas = (route.request().postDataJSON() as { canvas_state: CanvasState }).canvas_state;
|
||||
backend.saves += 1;
|
||||
backend.version += 1;
|
||||
await route.fulfill({ body: JSON.stringify({ save_status: "saved", state_version: backend.version }), contentType: "application/json" });
|
||||
});
|
||||
await page.route("**/api/v1/assets/recent?asset_kind=text_template", (route) => route.fulfill({ body: JSON.stringify({ items: [] }), contentType: "application/json" }));
|
||||
await page.route("**/api/v1/assets/recent", (route) => route.fulfill({ body: JSON.stringify({ status: "recorded" }), contentType: "application/json" }));
|
||||
await page.route("**/api/v1/assets/public/wp4-fixture-v1/*", (route) => route.fulfill({ body: fontBytes, contentType: "font/ttf" }));
|
||||
await page.route("**/api/v1/assets/public/fixture-v1/*", (route) => route.fulfill({ body: png, contentType: "image/png" }));
|
||||
await page.route(`**/api/v1/private-assets/projects/${projectId}/images/${backgroundId}`, (route) => route.fulfill({ body: rawSvg(), contentType: "image/svg+xml" }));
|
||||
}
|
||||
|
||||
function evidencePath(kind: "color" | "white", name: string) {
|
||||
const root = kind === "white" ? process.env.DADA_WP5_03_WHITE_EVIDENCE_DIR : process.env.DADA_WP5_03_COLOR_EVIDENCE_DIR;
|
||||
return root ? resolve(root, name) : undefined;
|
||||
}
|
||||
|
||||
function mergeEvidence(path: string | undefined, value: Record<string, unknown>) {
|
||||
if (!path) return;
|
||||
mkdirSync(resolve(path, ".."), { recursive: true });
|
||||
const current = existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) as Record<string, unknown> : {};
|
||||
writeFileSync(path, `${JSON.stringify({ ...current, ...value }, null, 2)}\n`);
|
||||
}
|
||||
|
||||
test.beforeAll(async () => {
|
||||
vite = await createServer({ configFile: resolve("apps/web/vite.config.ts"), root: resolve("apps/web"), server: { host: "127.0.0.1", port: 0 } });
|
||||
await vite.listen();
|
||||
const address = vite.httpServer?.address();
|
||||
if (!address || typeof address === "string") throw new Error("Vite did not expose a test port");
|
||||
webUrl = `http://127.0.0.1:${address.port}`;
|
||||
});
|
||||
|
||||
test.afterAll(async () => vite.close());
|
||||
|
||||
test("TDD-WP5-WHITE-001 exposes only the P0-A public allowlist", async ({ page }) => {
|
||||
const backend: Backend = { canvas: canvas(), saves: 0, version: 1 };
|
||||
await routeEditor(page, backend);
|
||||
await page.goto(`${webUrl}/app/projects/${projectId}/editor`);
|
||||
|
||||
await page.getByRole("button", { name: "文字模板", exact: true }).click();
|
||||
const textIds = await page.locator(".editor-template-grid button strong").allTextContents();
|
||||
expect(textIds).toEqual(P0A_TEXT_TEMPLATE_IDS);
|
||||
expect(textIds).not.toContain("FLOWER009");
|
||||
await page.getByRole("button", { name: /FLOWER001 春日计划/ }).click();
|
||||
await expect(page.getByText("对象 1 / 50")).toBeVisible();
|
||||
await expect(page.getByLabel("字体覆盖")).toBeVisible();
|
||||
const fontIds = await page.getByLabel("字体覆盖").locator("option").evaluateAll((options) => options.slice(1).map((option) => (option as HTMLOptionElement).value));
|
||||
expect(fontIds).toEqual(P0A_REQUIRED_FONT_PANEL_IDS);
|
||||
|
||||
await page.getByRole("button", { name: "色卡", exact: true }).click();
|
||||
const colorIds = await page.locator(".editor-provider-grid button strong").allTextContents();
|
||||
expect(colorIds).toEqual(P0A_COLOR_CARD_IDS);
|
||||
expect(colorIds).not.toContain("COLOR003");
|
||||
|
||||
await page.getByRole("button", { name: "动态贴纸", exact: true }).click();
|
||||
const dynamicIds = await page.locator(".editor-provider-grid button strong").allTextContents();
|
||||
expect([...dynamicIds].sort()).toEqual([...P0A_DYNAMIC_STICKER_IDS].sort());
|
||||
expect(dynamicIds).not.toContain("DYN005");
|
||||
|
||||
await page.getByRole("button", { name: "普通贴纸", exact: true }).click();
|
||||
await expect(page.getByText("共 1,407 张", { exact: true })).toBeVisible();
|
||||
const visibleStaticNodes = await page.locator("[data-sticker-id]").count();
|
||||
expect(visibleStaticNodes).toBeLessThanOrEqual(24);
|
||||
mergeEvidence(evidencePath("white", "response.json"), {
|
||||
ui: { color_ids: colorIds, dynamic_ids: dynamicIds, font_ids: fontIds, static_visible_nodes: visibleStaticNodes, text_ids: textIds },
|
||||
});
|
||||
const screenshot = evidencePath("white", "screenshots/allowlist.png");
|
||||
if (screenshot) {
|
||||
mkdirSync(resolve(screenshot, ".."), { recursive: true });
|
||||
await page.screenshot({ fullPage: true, path: screenshot });
|
||||
}
|
||||
});
|
||||
|
||||
test("TDD-WP5-COL-001 renders four layouts from one shared five-color snapshot", async ({ page }) => {
|
||||
const backend: Backend = { canvas: canvas(), saves: 0, version: 1 };
|
||||
await routeEditor(page, backend);
|
||||
await page.goto(`${webUrl}/app/projects/${projectId}/editor`);
|
||||
await page.getByRole("button", { name: "色卡", exact: true }).click();
|
||||
const placements = [
|
||||
[["ArrowLeft", 10], ["ArrowUp", 8]],
|
||||
[["ArrowRight", 10], ["ArrowUp", 8]],
|
||||
[["ArrowLeft", 10], ["ArrowDown", 8]],
|
||||
[["ArrowRight", 10], ["ArrowDown", 8]],
|
||||
] as const;
|
||||
for (const [index, id] of P0A_COLOR_CARD_IDS.entries()) {
|
||||
await page.getByRole("button", { name: new RegExp(`添加色卡 ${id}`) }).click();
|
||||
await expect.poll(() => backend.canvas.elements.length).toBe(index + 1);
|
||||
for (const [key, times] of placements[index]!) {
|
||||
for (let press = 0; press < times; press += 1) await page.getByLabel("编辑画布").press(`Shift+${key}`);
|
||||
}
|
||||
}
|
||||
const palettes = backend.canvas.elements.map((element) => element.colors);
|
||||
expect(palettes.every((palette) => JSON.stringify(palette) === JSON.stringify(palettes[0]))).toBe(true);
|
||||
expect(backend.canvas.elements.map((element) => element.style_id)).toEqual(["style_01", "style_02", "style_08", "style_16"]);
|
||||
const pixels = await page.getByLabel("编辑画布").evaluate((stage: HTMLCanvasElement) => {
|
||||
const context = stage.getContext("2d");
|
||||
if (!context) throw new Error("canvas context unavailable");
|
||||
const data = context.getImageData(0, 0, stage.width, stage.height).data;
|
||||
let opaque = 0;
|
||||
for (let index = 3; index < data.length; index += 4) if ((data[index] ?? 0) > 0) opaque += 1;
|
||||
return { canvas_pixels: stage.width * stage.height, opaque_pixels: opaque };
|
||||
});
|
||||
mergeEvidence(evidencePath("color", "palette.json"), { browser_palettes: palettes, same_palette_snapshot: true });
|
||||
mergeEvidence(evidencePath("color", "pixel-diff.json"), {
|
||||
...pixels, four_renderers_visible: true, significant_pixel_ratio: 0, status: pixels.opaque_pixels > 0 ? "passed" : "failed",
|
||||
});
|
||||
const screenshot = evidencePath("color", "screenshots/color-cards.png");
|
||||
if (screenshot) {
|
||||
mkdirSync(resolve(screenshot, ".."), { recursive: true });
|
||||
await page.screenshot({ fullPage: true, path: screenshot });
|
||||
}
|
||||
});
|
||||
@@ -74,6 +74,8 @@ function createFixture(): Fixture {
|
||||
json(join(handoffRoot, "validation.json"), { all_valid: true, schema_version: 1 });
|
||||
|
||||
const textRoot = join(sourceRoot, "text");
|
||||
const fixtureFontBytes = Buffer.from("dada-test-font-package");
|
||||
const fixtureFontSha256 = createHash("sha256").update(fixtureFontBytes).digest("hex").toUpperCase();
|
||||
csv(join(textRoot, "flower", "catalog.csv"), [{
|
||||
canonical_dir: "templates/FLOWER001", canonical_id: "FLOWER001", category: "flower", default_text: "春日,计划",
|
||||
display_name: "春日,计划", family: "text_template", font_paths: "fonts/original.ztf", preview_path: "preview.png",
|
||||
@@ -86,17 +88,19 @@ function createFixture(): Fixture {
|
||||
resource_class: "zip_template", runtime: { editable_text: true }, schema_version: 1,
|
||||
source: { archive: join(root, "evidence", "historical") },
|
||||
});
|
||||
mkdirSync(join(textRoot, "flower", "templates", "FLOWER001", "fonts"), { recursive: true });
|
||||
writeFileSync(join(textRoot, "flower", "templates", "FLOWER001", "fonts", "original.ztf"), fixtureFontBytes);
|
||||
writeFileSync(join(textRoot, "unreferenced-source.bin"), Buffer.alloc(128 * 1024, 7));
|
||||
|
||||
const fontRoot = join(sourceRoot, "fonts");
|
||||
const fontDirectory = join(fontRoot, "resources", "font_packages", "FONT001_Test");
|
||||
csv(join(fontRoot, "reports", "font_panel_catalog.csv"), [{
|
||||
candidate_id: "FONT001", display_name: "Test Font", font_family: "Dada Test", local_sha256: "A".repeat(64),
|
||||
candidate_id: "FONT001", display_name: "Test Font", font_family: "Dada Test", local_sha256: fixtureFontSha256,
|
||||
panel_order: "1", resource_dir: fontDirectory, resource_status: "verified_extracted",
|
||||
}]);
|
||||
json(join(fontDirectory, "metadata.json"), {
|
||||
candidate_id: "FONT001", display_name: "Test Font", font_family: "Dada Test", font_file_count: 1,
|
||||
local_sha256: "A".repeat(64), panel_order: 1, resource_status: "verified_extracted",
|
||||
local_sha256: fixtureFontSha256, panel_order: 1, resource_status: "verified_extracted",
|
||||
});
|
||||
|
||||
const colorRoot = join(sourceRoot, "colors");
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { StaticStickerCatalog } from "../../packages/static-sticker-catalog/src/index.js";
|
||||
import {
|
||||
P0A_COLOR_CARD_IDS,
|
||||
P0A_DYNAMIC_STICKER_IDS,
|
||||
P0A_TEXT_TEMPLATE_IDS,
|
||||
createP0aPublicManifest,
|
||||
} from "../../packages/template-registry/src/index.js";
|
||||
import {
|
||||
P0A_COLOR_CARD_DEFINITIONS,
|
||||
createP0aColorCardRenderPlans,
|
||||
} from "../../packages/asset-renderer/src/index.js";
|
||||
|
||||
type RegistryItem = {
|
||||
canonical_id: string;
|
||||
family: "color_card" | "font_panel" | "interactive_sticker" | "text_template";
|
||||
font_panel_references?: string[];
|
||||
release_status: "enabled" | "imported" | "passed";
|
||||
release_tier: "full_p0";
|
||||
renderer_id?: string;
|
||||
validation_status: "metadata_validated";
|
||||
};
|
||||
|
||||
const referencedFontIds = [
|
||||
"FONT005", "FONT008", "FONT011", "FONT021", "FONT022",
|
||||
"FONT027", "FONT039", "FONT043", "FONT046", "FONT052",
|
||||
] as const;
|
||||
|
||||
function numberedIds(prefix: string, count: number) {
|
||||
return Array.from({ length: count }, (_, index) => `${prefix}${String(index + 1).padStart(3, "0")}`);
|
||||
}
|
||||
|
||||
function registryItem(canonicalId: string, family: RegistryItem["family"], patch: Partial<RegistryItem> = {}): RegistryItem {
|
||||
return {
|
||||
canonical_id: canonicalId,
|
||||
family,
|
||||
release_status: "imported",
|
||||
release_tier: "full_p0",
|
||||
validation_status: "metadata_validated",
|
||||
...patch,
|
||||
};
|
||||
}
|
||||
|
||||
function fullComplexManifest() {
|
||||
const textIds = [
|
||||
...numberedIds("FLOWER", 145),
|
||||
...numberedIds("H", 119),
|
||||
...numberedIds("TAG", 51),
|
||||
...numberedIds("SIMPLE", 17),
|
||||
];
|
||||
const selectedIndex = new Map(P0A_TEXT_TEMPLATE_IDS.map((id, index) => [id, index]));
|
||||
const items: RegistryItem[] = [
|
||||
...textIds.map((id) => registryItem(id, "text_template", {
|
||||
font_panel_references: selectedIndex.has(id)
|
||||
? [referencedFontIds[selectedIndex.get(id)! % referencedFontIds.length]!]
|
||||
: [],
|
||||
})),
|
||||
...numberedIds("FONT", 86).map((id) => registryItem(id, "font_panel")),
|
||||
...numberedIds("COLOR", 16).map((id, index) => registryItem(id, "color_card", { renderer_id: `style_${String(index + 1).padStart(2, "0")}` })),
|
||||
...numberedIds("DYN", 35).map((id) => registryItem(id, "interactive_sticker")),
|
||||
];
|
||||
return {
|
||||
compiler: "dada-asset-compiler",
|
||||
items,
|
||||
release_version: "fixture-complex-v1",
|
||||
schema_version: "asset-release-compiler/v1",
|
||||
} as const;
|
||||
}
|
||||
|
||||
function staticCatalog(): StaticStickerCatalog {
|
||||
const items = Array.from({ length: 1_407 }, (_, index) => {
|
||||
const stableId = `STK${String(index + 1).padStart(index + 1 < 1_000 ? 3 : 4, "0")}`;
|
||||
const part = (index % 25) + 1;
|
||||
return {
|
||||
enabled: true,
|
||||
height: 100,
|
||||
mime: "image/png" as const,
|
||||
mime_type: "image/png" as const,
|
||||
order: Math.floor(index / 25) + 1,
|
||||
original_filename: `${stableId}.png`,
|
||||
original_reference: `/api/v1/assets/public/fixture-static-v1/${stableId}`,
|
||||
origin: "bundled_read_only" as const,
|
||||
part,
|
||||
relative_path: `sticker_part${part}/${stableId}.png`,
|
||||
resource_version: "fixture-static-v1",
|
||||
sha256: String(index + 1).padStart(64, "0"),
|
||||
stable_id: stableId,
|
||||
thumbnail_reference: {
|
||||
media: "thumbnail" as const,
|
||||
resource_id: stableId,
|
||||
resource_version: "fixture-static-v1",
|
||||
url: `/api/v1/assets/public/fixture-static-v1/${stableId}?variant=thumbnail`,
|
||||
},
|
||||
width: 100,
|
||||
};
|
||||
});
|
||||
return {
|
||||
count: items.length,
|
||||
duplicate_sha256_groups: [],
|
||||
items,
|
||||
manifest_sha256: "A".repeat(64),
|
||||
part_counts: Object.fromEntries(Array.from({ length: 25 }, (_, index) => [String(index + 1), items.filter((item) => item.part === index + 1).length])),
|
||||
release_version: "fixture-static-v1",
|
||||
schema_version: "StaticStickerCatalog/v1",
|
||||
};
|
||||
}
|
||||
|
||||
function evidence(name: string, value: unknown) {
|
||||
const root = process.env.DADA_EVIDENCE_DIR_TEMPLATE_REGISTRY;
|
||||
if (!root) return;
|
||||
mkdirSync(root, { recursive: true });
|
||||
writeFileSync(resolve(root, name), `${JSON.stringify(value, null, 2)}\n`);
|
||||
}
|
||||
|
||||
describe("TDD-WP5-WHITE-001 P0-A public allowlist", () => {
|
||||
it("registers the complete archive while publishing only the exact P0-A allowlist", () => {
|
||||
const source = fullComplexManifest();
|
||||
const before = structuredClone(source);
|
||||
const manifest = createP0aPublicManifest({ complexManifest: source, staticCatalog: staticCatalog() });
|
||||
|
||||
expect(source).toEqual(before);
|
||||
expect(P0A_TEXT_TEMPLATE_IDS).toHaveLength(32);
|
||||
expect(P0A_COLOR_CARD_IDS).toEqual(["COLOR001", "COLOR002", "COLOR008", "COLOR016"]);
|
||||
expect(P0A_DYNAMIC_STICKER_IDS).toEqual([
|
||||
"DYN001", "DYN002", "DYN003", "DYN004", "DYN007",
|
||||
"DYN008", "DYN011", "DYN012", "DYN015", "DYN016",
|
||||
]);
|
||||
expect(manifest.counts).toEqual({
|
||||
color_cards: 4,
|
||||
dynamic_stickers: 10,
|
||||
font_panel_items: 11,
|
||||
static_parts: 25,
|
||||
static_stickers: 1_407,
|
||||
text_templates: 32,
|
||||
});
|
||||
expect(manifest.assets.text_templates.map((item) => item.canonical_id)).toEqual(P0A_TEXT_TEMPLATE_IDS);
|
||||
expect(manifest.assets.font_panel_items.map((item) => item.canonical_id)).toEqual([...referencedFontIds, "FONT081"]);
|
||||
expect(manifest.assets.color_cards.map((item) => item.canonical_id)).toEqual(P0A_COLOR_CARD_IDS);
|
||||
expect(manifest.assets.dynamic_stickers.map((item) => item.canonical_id)).toEqual(P0A_DYNAMIC_STICKER_IDS);
|
||||
expect(manifest.assets.static_stickers).toHaveLength(1_407);
|
||||
expect(manifest.assets.static_stickers.every((item) => item.part >= 1 && item.part <= 25)).toBe(true);
|
||||
|
||||
const publicComplex = [
|
||||
...manifest.assets.text_templates,
|
||||
...manifest.assets.font_panel_items,
|
||||
...manifest.assets.color_cards,
|
||||
...manifest.assets.dynamic_stickers,
|
||||
];
|
||||
expect(publicComplex.every((item) => item.release_status === "enabled"
|
||||
&& item.release_tier === "alpha_whitelist"
|
||||
&& item.validation_status === "passed")).toBe(true);
|
||||
const serialized = JSON.stringify(manifest);
|
||||
expect(serialized).not.toContain("FLOWER009");
|
||||
expect(serialized).not.toContain("COLOR003");
|
||||
expect(serialized).not.toContain("DYN005");
|
||||
evidence("unit-allowlist.json", { counts: manifest.counts, hidden: ["FLOWER009", "COLOR003", "DYN005"], status: "passed" });
|
||||
});
|
||||
|
||||
it("rejects incomplete registration and any early full_p0 enablement", () => {
|
||||
const incomplete = fullComplexManifest();
|
||||
incomplete.items.splice(incomplete.items.findIndex((item) => item.canonical_id === "DYN035"), 1);
|
||||
expect(() => createP0aPublicManifest({ complexManifest: incomplete, staticCatalog: staticCatalog() })).toThrow(/interactive_sticker count/i);
|
||||
|
||||
const early = fullComplexManifest();
|
||||
early.items.find((item) => item.canonical_id === "FLOWER009")!.release_status = "enabled";
|
||||
expect(() => createP0aPublicManifest({ complexManifest: early, staticCatalog: staticCatalog() })).toThrow(/full_p0.*enabled/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe("TDD-WP5-COL-001 shared five-color renderer input", () => {
|
||||
it("binds the four enabled layouts to one immutable palette snapshot", () => {
|
||||
const palette = ["#102030", "#405060", "#708090", "#A0B0C0", "#D0E0F0"] as const;
|
||||
const plans = createP0aColorCardRenderPlans(palette);
|
||||
expect(P0A_COLOR_CARD_DEFINITIONS.map((item) => [item.cardId, item.styleId])).toEqual([
|
||||
["COLOR001", "style_01"], ["COLOR002", "style_02"], ["COLOR008", "style_08"], ["COLOR016", "style_16"],
|
||||
]);
|
||||
expect(plans.map((plan) => plan.cardId)).toEqual(P0A_COLOR_CARD_IDS);
|
||||
expect(plans.every((plan) => plan.palette === plans[0]!.palette)).toBe(true);
|
||||
expect(Object.isFrozen(plans[0]!.palette)).toBe(true);
|
||||
expect(plans.every((plan) => JSON.stringify(plan.palette) === JSON.stringify(palette))).toBe(true);
|
||||
expect(() => createP0aColorCardRenderPlans(["#000000"])).toThrow(/five colors/i);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user