247 lines
14 KiB
TypeScript
247 lines
14 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import complexAssetCatalog from "../../apps/web/src/generated/complex-assets.json";
|
|
import { P0A_DYNAMIC_STICKERS } from "../../apps/web/src/dynamic-provider.js";
|
|
import { DYNAMIC_RENDER_MODELS } from "../../apps/web/src/dynamic-render-models.js";
|
|
import { P0A_COLOR_CARDS } from "../../apps/web/src/palette-provider.js";
|
|
import { P0A_FONT_OPTIONS, P0A_TEXT_TEMPLATES } from "../../apps/web/src/text-assets.js";
|
|
import { boundedTextTemplateWidth, ninePatchSlices, textTemplateBoxLayout, textTextureCell } from "../../apps/web/src/editor-stage.js";
|
|
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";
|
|
|
|
function containsAbsolutePath(value: unknown): boolean {
|
|
if (typeof value === "string") return /^[A-Z]:[\\/]/i.test(value);
|
|
if (Array.isArray(value)) return value.some(containsAbsolutePath);
|
|
return value !== null && typeof value === "object" && Object.values(value).some(containsAbsolutePath);
|
|
}
|
|
|
|
describe("POSTV1-ASSET-ALL-16 complete asset catalog", () => {
|
|
it("publishes every normalized complex asset instead of the original alpha subset", () => {
|
|
expect(P0A_TEXT_TEMPLATE_IDS).toHaveLength(332);
|
|
expect(P0A_TEXT_TEMPLATE_IDS[0]).toBe("FLOWER001");
|
|
expect(P0A_TEXT_TEMPLATE_IDS.at(-1)).toBe("SIMPLE017");
|
|
|
|
expect(P0A_REQUIRED_FONT_PANEL_IDS).toHaveLength(86);
|
|
expect(P0A_REQUIRED_FONT_PANEL_IDS[0]).toBe("FONT001");
|
|
expect(P0A_REQUIRED_FONT_PANEL_IDS.at(-1)).toBe("FONT086");
|
|
|
|
expect(P0A_COLOR_CARD_IDS).toHaveLength(16);
|
|
expect(P0A_COLOR_CARD_IDS[0]).toBe("COLOR001");
|
|
expect(P0A_COLOR_CARD_IDS.at(-1)).toBe("COLOR016");
|
|
|
|
expect(P0A_DYNAMIC_STICKER_IDS).toHaveLength(35);
|
|
expect(P0A_DYNAMIC_STICKER_IDS[0]).toBe("DYN001");
|
|
expect(P0A_DYNAMIC_STICKER_IDS.at(-1)).toBe("DYN035");
|
|
});
|
|
|
|
it("generates a sanitized browser catalog for every text, font, and dynamic definition", () => {
|
|
expect(complexAssetCatalog.schema_version).toBe("DadaComplexBrowserCatalog/v2");
|
|
expect(complexAssetCatalog.text_resource_revision).toMatch(/^[a-f0-9]{16}$/);
|
|
expect(complexAssetCatalog.text_templates).toHaveLength(332);
|
|
expect(complexAssetCatalog.font_panel_items).toHaveLength(86);
|
|
expect(complexAssetCatalog.dynamic_stickers).toHaveLength(35);
|
|
expect(complexAssetCatalog.text_templates.filter((item) => item.preview_asset_id)).toHaveLength(261);
|
|
expect(complexAssetCatalog.text_templates.every((item) => item.available)).toBe(true);
|
|
expect(complexAssetCatalog.text_templates.every((item) => item.render_model.text_layers.length > 0)).toBe(true);
|
|
expect(complexAssetCatalog.text_templates.filter((item) => item.resource_class === "zip_template")).toHaveLength(330);
|
|
expect(complexAssetCatalog.text_templates.reduce((count, item) => count + item.render_model.image_layers.length, 0)).toBe(316);
|
|
expect(complexAssetCatalog.text_templates.reduce((count, item) => count + item.render_model.particle_layers.length, 0)).toBe(9);
|
|
expect(complexAssetCatalog.text_templates.reduce((count, item) => count + item.render_model.text_layers.length, 0)).toBe(490);
|
|
expect(complexAssetCatalog.text_templates.filter((item) => item.render_model.text_layers.some((layer) => "fill_pattern_asset_id" in layer))).toHaveLength(14);
|
|
expect(containsAbsolutePath(complexAssetCatalog)).toBe(false);
|
|
});
|
|
|
|
it("keeps the captured template font, default text, style, and decoration layers", () => {
|
|
const byId = new Map(complexAssetCatalog.text_templates.map((item) => [item.template_id, item]));
|
|
const flower001 = byId.get("FLOWER001")!;
|
|
expect(flower001.default_font_id).toMatch(/^TEXT-FONT-FLOWER001-/);
|
|
expect(flower001.font_match_status).toBe("template_package");
|
|
expect(flower001.render_model.image_layers).toHaveLength(1);
|
|
expect(flower001.render_model.text_layers[0]).toMatchObject({
|
|
fill_color: "#FFC5D4",
|
|
stroke_color: "#FF69A6",
|
|
stroke_width: 5,
|
|
});
|
|
|
|
const flower008 = byId.get("FLOWER008")!;
|
|
expect(flower008.default_text).toBe("Vlog.");
|
|
expect(flower008.default_font_id).toMatch(/^TEXT-FONT-FLOWER008-/);
|
|
|
|
const heading001 = byId.get("H001")!;
|
|
expect(heading001.render_model.image_layers).toHaveLength(5);
|
|
expect(heading001.default_font_id).toMatch(/^TEXT-FONT-H001-/);
|
|
|
|
const materialText = byId.get("FLOWER048")!;
|
|
expect(materialText.render_model.text_layers[0]).toHaveProperty("fill_pattern_asset_id");
|
|
|
|
expect(byId.get("FLOWER037")!.render_model.text_layers[0]).toMatchObject({
|
|
fill_gradient: { bottom: "#FC686F", top: "#FFEB7B" },
|
|
});
|
|
expect(byId.get("FLOWER077")!.render_model.text_layers[0]).toMatchObject({ skew_x: 0, skew_y: 11 });
|
|
expect(byId.get("FLOWER125")!.render_model.text_layers[0]).toMatchObject({
|
|
fill_texture_layout: { columns: 1, id_list: [0, 0, 0, 0, 0, 0], rows: 1 },
|
|
glyph_colors: ["#FC7878", "#F0F054", "#F28FDE", "#47BAFF", "#FFB80F", "#4ADB91"],
|
|
});
|
|
|
|
const underlinedText = byId.get("FLOWER121")!;
|
|
expect(underlinedText.render_model.image_layers).toHaveLength(1);
|
|
|
|
const particleText = byId.get("H013")!;
|
|
expect(particleText.render_model.particle_layers).toHaveLength(3);
|
|
|
|
const ninePatchLayers = complexAssetCatalog.text_templates.flatMap((item) => item.render_model.image_layers)
|
|
.filter((layer) => "nine_patch" in layer);
|
|
expect(ninePatchLayers.length).toBeGreaterThanOrEqual(100);
|
|
expect(byId.get("FLOWER023")!.render_model.image_layers[0]).toMatchObject({
|
|
nine_patch: { bottom: 16, left: 53, right: 25, source_height: 63, source_width: 159, top: 12 },
|
|
});
|
|
const motherBackground = byId.get("FLOWER077")!.render_model.image_layers[0]!;
|
|
expect(motherBackground.nine_patch).toMatchObject({
|
|
bottom: 0.407, left: 19.108, right: 19.514, source_height: 224, source_width: 445, top: 8.944,
|
|
});
|
|
expect(motherBackground.nine_patch!.left + motherBackground.nine_patch!.right).toBeLessThan(motherBackground.width);
|
|
expect(motherBackground.nine_patch!.top + motherBackground.nine_patch!.bottom).toBeLessThan(motherBackground.height);
|
|
expect(byId.get("H001")!.render_model.image_layers).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ anchor_x: 0 }),
|
|
expect.objectContaining({ anchor_x: 1 }),
|
|
]));
|
|
expect(byId.get("H079")!.render_model.text_layers.slice(0, 3).every((layer) => Math.abs(layer.rotation + 10) < 0.001)).toBe(true);
|
|
expect(byId.get("H005")!.render_model.text_layers[1]).toMatchObject({ anchor_y: 0 });
|
|
expect(byId.get("H005")!.render_model.text_layers[0]).toMatchObject({ vertical_align: "middle" });
|
|
expect(byId.get("H005")!.render_model.text_layers[1]).toMatchObject({ vertical_align: "top" });
|
|
expect(byId.get("H004")!.render_model.text_layers[0]).toMatchObject({ align: "center" });
|
|
expect(byId.get("H009")!.render_model.image_layers.find((layer) => layer.asset_id === "TEXT-IMAGE-H009-003"))
|
|
.toMatchObject({ anchor_x: 0 });
|
|
|
|
const heading016Text = byId.get("H016")!.render_model.text_layers;
|
|
expect(heading016Text).toHaveLength(3);
|
|
expect(heading016Text.every((layer) => layer.vertical_align === "bottom")).toBe(true);
|
|
|
|
const heading041 = byId.get("H041")!;
|
|
const heading041Text = heading041.render_model.text_layers[0]!;
|
|
const heading041Underline = heading041.render_model.image_layers.find((layer) => layer.asset_id === "TEXT-IMAGE-H041-004")!;
|
|
expect(heading041Text.stroke_width).toBe(0);
|
|
expect(heading041Text.letter_spacing).toBeCloseTo(2.714, 3);
|
|
expect(heading041Underline.y).toBeGreaterThan(heading041Text.y);
|
|
expect(byId.get("FLOWER024")!.render_model.image_layers[0]).toMatchObject({ alpha: 0.4000000059604645 });
|
|
});
|
|
|
|
it("preserves source path, coordinate, and auto-layout semantics for fidelity regressions", () => {
|
|
const byId = new Map(complexAssetCatalog.text_templates.map((item) => [item.template_id, item]));
|
|
|
|
const summer = byId.get("H015")!.render_model.text_layers[0] as unknown as {
|
|
fill_texture_layout?: { columns: number; id_list: number[]; rows: number };
|
|
text_path?: { circle: boolean; points: Array<{ x: number; y: number }>; verbs: number[] };
|
|
};
|
|
expect(summer.text_path).toMatchObject({ circle: false, verbs: [0, 4, 4] });
|
|
expect(summer.text_path?.points).toHaveLength(7);
|
|
expect(summer.fill_texture_layout).toEqual({ columns: 2, id_list: [0, 1, 2, 3], rows: 2 });
|
|
|
|
const doubleSeventh = byId.get("H017")!.render_model.text_layers[0] as unknown as {
|
|
text_path?: { circle: boolean; points: Array<{ x: number; y: number }>; verbs: number[] };
|
|
};
|
|
expect(doubleSeventh.text_path).toMatchObject({ circle: true, verbs: [0, 3, 3, 3, 3, 5] });
|
|
expect(doubleSeventh.text_path?.points).toHaveLength(9);
|
|
|
|
const relax = byId.get("H022")!.render_model;
|
|
expect([...relax.text_layers, ...relax.particle_layers].every((layer) => layer.scale_y === 1)).toBe(true);
|
|
expect(relax.text_layers[0]!.y).toBeLessThan(relax.text_layers.at(-1)!.y);
|
|
expect(relax.text_layers.at(-1)!.font_id).toBe("TEXT-FONT-H022-03");
|
|
expect(relax.text_layers.slice(0, 3).map((layer) => layer.content_linked)).toEqual([true, true, true]);
|
|
|
|
const sightseeing = byId.get("H023")!.render_model;
|
|
expect(sightseeing.half_size.width).toBeGreaterThan(80);
|
|
expect(sightseeing.half_size.height).toBeGreaterThan(50);
|
|
expect(sightseeing.text_layers.every((layer) => layer.width > 1 && layer.height > 1)).toBe(true);
|
|
expect(sightseeing.particle_layers.every((layer) => layer.width > 1 && layer.height > 1)).toBe(true);
|
|
expect(sightseeing.text_layers.map((layer) => layer.font_id)).toEqual([
|
|
"TEXT-FONT-H023-03", "TEXT-FONT-H023-03", "TEXT-FONT-H023-03", "TEXT-FONT-H023-01", "TEXT-FONT-H023-02",
|
|
]);
|
|
expect(sightseeing.text_layers.slice(0, 3).map((layer) => layer.content_linked)).toEqual([true, true, true]);
|
|
|
|
const mother = byId.get("FLOWER077")!.render_model;
|
|
const motherText = mother.text_layers[0]!;
|
|
const motherBackground = mother.image_layers[0]!;
|
|
expect(motherText.width).toBeLessThan(motherBackground.width);
|
|
expect(Math.abs(motherText.x - motherBackground.x)).toBeLessThan(motherBackground.width * 0.05);
|
|
});
|
|
|
|
it("maps each glyph to the source texture-atlas cell", () => {
|
|
const layout = { columns: 2, idList: [0, 1, 2, 3], rows: 2 };
|
|
expect(textTextureCell(layout, 0)).toEqual({ column: 0, row: 0 });
|
|
expect(textTextureCell(layout, 1)).toEqual({ column: 1, row: 0 });
|
|
expect(textTextureCell(layout, 2)).toEqual({ column: 0, row: 1 });
|
|
expect(textTextureCell(layout, 3)).toEqual({ column: 1, row: 1 });
|
|
expect(textTextureCell(layout, 7)).toEqual({ column: 1, row: 1 });
|
|
});
|
|
|
|
it("positions template text inside the captured fixed box instead of its measured glyph width", () => {
|
|
const shortText = textTemplateBoxLayout({
|
|
align: "center", anchorX: 0, anchorY: 1, boxHeight: 70, boxWidth: 517,
|
|
fontSize: 70, lineCount: 1, lineHeight: 1, verticalAlign: "bottom",
|
|
});
|
|
const longText = textTemplateBoxLayout({
|
|
align: "center", anchorX: 0, anchorY: 1, boxHeight: 70, boxWidth: 517,
|
|
fontSize: 70, lineCount: 1, lineHeight: 1, verticalAlign: "bottom",
|
|
});
|
|
|
|
expect(shortText.x).toBe(258.5);
|
|
expect(longText.x).toBe(shortText.x);
|
|
expect(shortText.firstY).toBe(-35);
|
|
expect(boundedTextTemplateWidth(640, 180)).toBe(180);
|
|
expect(boundedTextTemplateWidth(120, 180)).toBe(120);
|
|
});
|
|
|
|
it("honors source top, middle, and bottom vertical alignment inside fixed text boxes", () => {
|
|
const layout = (verticalAlign: "top" | "middle" | "bottom") => textTemplateBoxLayout({
|
|
align: "left", anchorX: 0, anchorY: 0, boxHeight: 120, boxWidth: 200,
|
|
fontSize: 40, lineCount: 1, lineHeight: 1, verticalAlign,
|
|
});
|
|
|
|
expect(layout("top").firstY).toBe(20);
|
|
expect(layout("middle").firstY).toBe(60);
|
|
expect(layout("bottom").firstY).toBe(100);
|
|
});
|
|
|
|
it("preserves nine-patch edges while stretching only the decoration center", () => {
|
|
const slices = ninePatchSlices(231, 63, {
|
|
bottom: 16, left: 53, right: 25, sourceHeight: 63, sourceWidth: 159, top: 12,
|
|
});
|
|
expect(slices).toHaveLength(9);
|
|
expect(slices[0]).toEqual({ destination: { height: 12, width: 53, x: 0, y: 0 }, source: { height: 12, width: 53, x: 0, y: 0 } });
|
|
expect(slices[4]).toEqual({ destination: { height: 35, width: 153, x: 53, y: 12 }, source: { height: 35, width: 81, x: 53, y: 12 } });
|
|
expect(slices[8]).toEqual({ destination: { height: 16, width: 25, x: 206, y: 47 }, source: { height: 16, width: 25, x: 134, y: 47 } });
|
|
});
|
|
|
|
it("scales nine-patch source slices to the deployed bitmap without changing layout edges", () => {
|
|
const slices = ninePatchSlices(231, 63, {
|
|
bottom: 16, left: 53, right: 25, sourceHeight: 63, sourceWidth: 159, top: 12,
|
|
}, { height: 199, width: 509 });
|
|
|
|
expect(slices[0]!.destination).toEqual({ height: 12, width: 53, x: 0, y: 0 });
|
|
expect(slices[4]!.destination).toEqual({ height: 35, width: 153, x: 53, y: 12 });
|
|
expect(slices[8]!.destination).toEqual({ height: 16, width: 25, x: 206, y: 47 });
|
|
expect(slices[4]!.source.x).toBeCloseTo(169.667, 3);
|
|
expect(slices[4]!.source.y).toBeCloseTo(37.905, 3);
|
|
expect(slices[4]!.source.width).toBeCloseTo(259.302, 3);
|
|
expect(slices[4]!.source.height).toBeCloseTo(110.556, 3);
|
|
expect(slices[8]!.source.x).toBeCloseTo(428.969, 3);
|
|
expect(slices[8]!.source.y).toBeCloseTo(148.46, 2);
|
|
expect(slices[8]!.source.width).toBeCloseTo(80.031, 3);
|
|
expect(slices[8]!.source.height).toBeCloseTo(50.54, 2);
|
|
});
|
|
|
|
it("exposes every generated definition to the editor", () => {
|
|
expect(P0A_TEXT_TEMPLATES).toHaveLength(332);
|
|
expect(P0A_TEXT_TEMPLATES.every((item) => item.available && item.fontUrl)).toBe(true);
|
|
expect(P0A_FONT_OPTIONS).toHaveLength(86);
|
|
expect(P0A_COLOR_CARDS).toHaveLength(16);
|
|
expect(P0A_DYNAMIC_STICKERS).toHaveLength(35);
|
|
expect(Object.keys(DYNAMIC_RENDER_MODELS)).toHaveLength(35);
|
|
});
|
|
});
|