You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

296 lines
11 KiB

import { describe, it, expect } from "vitest";
import {
getPresetTemplate,
getAllPresets,
getTemplateContent,
PRESET_TEMPLATES,
SAMPLE_IMAGES,
ContentLevel,
} from "../templates";
describe("templates", () => {
describe("SAMPLE_IMAGES", () => {
it("should have all expected product images", () => {
expect(SAMPLE_IMAGES.appleWatch).toBe("/sample-images/apple-watch.jpg");
expect(SAMPLE_IMAGES.nikeShoe).toBe("/sample-images/nike-shoe.jpg");
expect(SAMPLE_IMAGES.rayban).toBe("/sample-images/rayban.jpg");
});
it("should have all expected model images", () => {
expect(SAMPLE_IMAGES.model).toBe("/sample-images/model.jpg");
expect(SAMPLE_IMAGES.model2).toBe("/sample-images/model-2.jpg");
expect(SAMPLE_IMAGES.model3).toBe("/sample-images/model-3.jpg");
});
it("should have all expected scene images", () => {
expect(SAMPLE_IMAGES.desert).toBe("/sample-images/desert.jpg");
expect(SAMPLE_IMAGES.streetScene).toBe("/sample-images/street-scene.jpg");
expect(SAMPLE_IMAGES.nyStreet).toBe("/sample-images/ny-street.jpg");
});
});
describe("PRESET_TEMPLATES", () => {
it("should have 6 preset templates", () => {
expect(PRESET_TEMPLATES).toHaveLength(6);
});
it("should have all required template IDs", () => {
const templateIds = PRESET_TEMPLATES.map((t) => t.id);
expect(templateIds).toContain("product-shot");
expect(templateIds).toContain("model-product");
expect(templateIds).toContain("color-variations");
expect(templateIds).toContain("background-swap");
expect(templateIds).toContain("style-transfer");
expect(templateIds).toContain("scene-composite");
});
it("each template should have required properties", () => {
PRESET_TEMPLATES.forEach((template) => {
expect(template).toHaveProperty("id");
expect(template).toHaveProperty("name");
expect(template).toHaveProperty("description");
expect(template).toHaveProperty("icon");
expect(template).toHaveProperty("workflow");
});
});
it("each template workflow should have valid structure", () => {
PRESET_TEMPLATES.forEach((template) => {
const { workflow } = template;
expect(workflow.version).toBe(1);
expect(workflow.name).toBeTruthy();
expect(workflow.edgeStyle).toBe("curved");
expect(Array.isArray(workflow.nodes)).toBe(true);
expect(Array.isArray(workflow.edges)).toBe(true);
expect(workflow.nodes.length).toBeGreaterThan(0);
});
});
it("each template should have at least one nanoBanana node", () => {
PRESET_TEMPLATES.forEach((template) => {
const nanoBananaNodes = template.workflow.nodes.filter(
(n) => n.type === "nanoBanana"
);
expect(nanoBananaNodes.length).toBeGreaterThanOrEqual(1);
});
});
it("each template should have at least one output node", () => {
PRESET_TEMPLATES.forEach((template) => {
const outputNodes = template.workflow.nodes.filter(
(n) => n.type === "output"
);
expect(outputNodes.length).toBeGreaterThanOrEqual(1);
});
});
it("edges should reference valid node IDs", () => {
PRESET_TEMPLATES.forEach((template) => {
const nodeIds = new Set(template.workflow.nodes.map((n) => n.id));
template.workflow.edges.forEach((edge) => {
expect(nodeIds.has(edge.source)).toBe(true);
expect(nodeIds.has(edge.target)).toBe(true);
});
});
});
});
describe("getAllPresets", () => {
it("should return all 6 presets", () => {
const presets = getAllPresets();
expect(presets).toHaveLength(6);
});
it("should return only display properties", () => {
const presets = getAllPresets();
presets.forEach((preset) => {
expect(Object.keys(preset)).toEqual(["id", "name", "description", "icon"]);
});
});
it("should not include workflow data", () => {
const presets = getAllPresets();
presets.forEach((preset) => {
expect(preset).not.toHaveProperty("workflow");
});
});
});
describe("getPresetTemplate", () => {
const contentLevels: ContentLevel[] = ["empty", "minimal", "full"];
it("should throw error for invalid template ID", () => {
expect(() => getPresetTemplate("invalid-id", "minimal")).toThrow(
"Template not found: invalid-id"
);
});
it("should return workflow with generated ID", () => {
const workflow = getPresetTemplate("product-shot", "empty");
expect(workflow.id).toMatch(/^wf_\d+_product-shot$/);
});
describe("empty content level", () => {
it("should have empty prompts", () => {
const workflow = getPresetTemplate("product-shot", "empty");
const promptNodes = workflow.nodes.filter((n) => n.type === "prompt");
promptNodes.forEach((node) => {
expect((node.data as { prompt: string }).prompt).toBe("");
});
});
it("should have no pre-loaded images", () => {
const workflow = getPresetTemplate("product-shot", "empty");
const imageNodes = workflow.nodes.filter((n) => n.type === "imageInput");
imageNodes.forEach((node) => {
expect((node.data as { image: string | null }).image).toBeNull();
});
});
});
describe("minimal content level", () => {
it("should have instructional prompts", () => {
const workflow = getPresetTemplate("product-shot", "minimal");
const promptNodes = workflow.nodes.filter((n) => n.type === "prompt");
promptNodes.forEach((node) => {
const prompt = (node.data as { prompt: string }).prompt;
expect(prompt.length).toBeGreaterThan(0);
expect(prompt).toContain("Consider:");
});
});
it("should have no pre-loaded images", () => {
const workflow = getPresetTemplate("product-shot", "minimal");
const imageNodes = workflow.nodes.filter((n) => n.type === "imageInput");
imageNodes.forEach((node) => {
expect((node.data as { image: string | null }).image).toBeNull();
});
});
});
describe("full content level", () => {
it("should have complete prompts", () => {
const workflow = getPresetTemplate("product-shot", "full");
const promptNodes = workflow.nodes.filter((n) => n.type === "prompt");
promptNodes.forEach((node) => {
const prompt = (node.data as { prompt: string }).prompt;
expect(prompt.length).toBeGreaterThan(50);
});
});
it("should have pre-loaded images with local paths", () => {
const workflow = getPresetTemplate("product-shot", "full");
const imageNodes = workflow.nodes.filter((n) => n.type === "imageInput");
const nodesWithImages = imageNodes.filter(
(node) => (node.data as { image: string | null }).image !== null
);
expect(nodesWithImages.length).toBeGreaterThan(0);
nodesWithImages.forEach((node) => {
const image = (node.data as { image: string }).image;
expect(image).toMatch(/^\/sample-images\/.+\.jpg$/);
});
});
it("should have filenames for images", () => {
const workflow = getPresetTemplate("product-shot", "full");
const imageNodes = workflow.nodes.filter((n) => n.type === "imageInput");
const nodesWithImages = imageNodes.filter(
(node) => (node.data as { image: string | null }).image !== null
);
nodesWithImages.forEach((node) => {
const filename = (node.data as { filename: string }).filename;
expect(filename).toMatch(/\.jpg$/);
});
});
});
it("should work for all templates at all content levels", () => {
const templateIds = PRESET_TEMPLATES.map((t) => t.id);
templateIds.forEach((templateId) => {
contentLevels.forEach((level) => {
expect(() => getPresetTemplate(templateId, level)).not.toThrow();
const workflow = getPresetTemplate(templateId, level);
expect(workflow.nodes.length).toBeGreaterThan(0);
expect(workflow.edges.length).toBeGreaterThan(0);
});
});
});
});
describe("getTemplateContent", () => {
it("should return content for valid template and level", () => {
const content = getTemplateContent("product-shot", "full");
expect(content).not.toBeNull();
expect(content).toHaveProperty("prompts");
expect(content).toHaveProperty("images");
});
it("should return null for invalid template", () => {
const content = getTemplateContent("invalid-id", "full");
expect(content).toBeNull();
});
it("should have prompts object", () => {
const content = getTemplateContent("product-shot", "minimal");
expect(content?.prompts).toBeDefined();
expect(typeof content?.prompts).toBe("object");
});
it("should have images object for full level", () => {
const content = getTemplateContent("product-shot", "full");
expect(content?.images).toBeDefined();
expect(Object.keys(content?.images || {}).length).toBeGreaterThan(0);
});
it("should have empty images object for empty/minimal levels", () => {
const emptyContent = getTemplateContent("product-shot", "empty");
const minimalContent = getTemplateContent("product-shot", "minimal");
expect(Object.keys(emptyContent?.images || {}).length).toBe(0);
expect(Object.keys(minimalContent?.images || {}).length).toBe(0);
});
});
describe("template-specific tests", () => {
describe("product-shot template", () => {
it("should have 2 image inputs (product and scene)", () => {
const workflow = getPresetTemplate("product-shot", "empty");
const imageInputs = workflow.nodes.filter((n) => n.type === "imageInput");
expect(imageInputs.length).toBe(2);
});
it("full level should have nike shoe and desert images", () => {
const workflow = getPresetTemplate("product-shot", "full");
const images = workflow.nodes
.filter((n) => n.type === "imageInput")
.map((n) => (n.data as { image: string | null }).image)
.filter(Boolean);
expect(images).toContain(SAMPLE_IMAGES.nikeShoe);
expect(images).toContain(SAMPLE_IMAGES.desert);
});
});
describe("model-product template", () => {
it("should have 3 image inputs (model, product, scene)", () => {
const workflow = getPresetTemplate("model-product", "empty");
const imageInputs = workflow.nodes.filter((n) => n.type === "imageInput");
expect(imageInputs.length).toBe(3);
});
});
describe("color-variations template", () => {
it("should have 3 image inputs (product and color refs)", () => {
const workflow = getPresetTemplate("color-variations", "empty");
const imageInputs = workflow.nodes.filter((n) => n.type === "imageInput");
expect(imageInputs.length).toBe(3);
});
});
describe("style-transfer template", () => {
it("should have 2 image inputs (style and content)", () => {
const workflow = getPresetTemplate("style-transfer", "empty");
const imageInputs = workflow.nodes.filter((n) => n.type === "imageInput");
expect(imageInputs.length).toBe(2);
});
});
});
});