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.
345 lines
11 KiB
345 lines
11 KiB
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
import {
|
|
createDefaultNodeData,
|
|
defaultNodeDimensions,
|
|
GROUP_COLORS,
|
|
GROUP_COLOR_ORDER,
|
|
} from "../nodeDefaults";
|
|
|
|
// Mock localStorage for loadGenerateImageDefaults
|
|
const localStorageMock = (() => {
|
|
let store: Record<string, string> = {};
|
|
return {
|
|
getItem: vi.fn((key: string) => store[key] ?? null),
|
|
setItem: vi.fn((key: string, value: string) => {
|
|
store[key] = value;
|
|
}),
|
|
clear: vi.fn(() => {
|
|
store = {};
|
|
}),
|
|
};
|
|
})();
|
|
|
|
Object.defineProperty(globalThis, "localStorage", {
|
|
value: localStorageMock,
|
|
writable: true,
|
|
});
|
|
|
|
describe("nodeDefaults utilities", () => {
|
|
beforeEach(() => {
|
|
localStorageMock.clear();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe("defaultNodeDimensions", () => {
|
|
it("has all expected node types", () => {
|
|
const expectedTypes = [
|
|
"imageInput",
|
|
"annotation",
|
|
"prompt",
|
|
"array",
|
|
"nanoBanana",
|
|
"generateVideo",
|
|
"llmGenerate",
|
|
"glbViewer",
|
|
"output",
|
|
];
|
|
|
|
expectedTypes.forEach((type) => {
|
|
expect(defaultNodeDimensions[type as keyof typeof defaultNodeDimensions]).toBeDefined();
|
|
});
|
|
});
|
|
|
|
it("has width and height for each node type", () => {
|
|
Object.values(defaultNodeDimensions).forEach((dims) => {
|
|
expect(dims).toHaveProperty("width");
|
|
expect(dims).toHaveProperty("height");
|
|
expect(typeof dims.width).toBe("number");
|
|
expect(typeof dims.height).toBe("number");
|
|
expect(dims.width).toBeGreaterThan(0);
|
|
expect(dims.height).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("GROUP_COLORS", () => {
|
|
it("has all expected color keys", () => {
|
|
const expectedColors = ["neutral", "blue", "green", "purple", "orange", "red"];
|
|
|
|
expectedColors.forEach((color) => {
|
|
expect(GROUP_COLORS[color as keyof typeof GROUP_COLORS]).toBeDefined();
|
|
});
|
|
});
|
|
|
|
it("has valid hex color values", () => {
|
|
Object.values(GROUP_COLORS).forEach((color) => {
|
|
expect(color).toMatch(/^#[0-9a-fA-F]{6}$/);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("GROUP_COLOR_ORDER", () => {
|
|
it("has all colors from GROUP_COLORS", () => {
|
|
GROUP_COLOR_ORDER.forEach((color) => {
|
|
expect(GROUP_COLORS[color]).toBeDefined();
|
|
});
|
|
});
|
|
|
|
it("has same length as GROUP_COLORS", () => {
|
|
expect(GROUP_COLOR_ORDER.length).toBe(Object.keys(GROUP_COLORS).length);
|
|
});
|
|
|
|
it("starts with neutral", () => {
|
|
expect(GROUP_COLOR_ORDER[0]).toBe("neutral");
|
|
});
|
|
});
|
|
|
|
describe("createDefaultNodeData", () => {
|
|
it("creates correct structure for imageInput", () => {
|
|
const data = createDefaultNodeData("imageInput");
|
|
|
|
expect(data).toHaveProperty("image", null);
|
|
expect(data).toHaveProperty("filename", null);
|
|
expect(data).toHaveProperty("dimensions", null);
|
|
});
|
|
|
|
it("creates correct structure for annotation", () => {
|
|
const data = createDefaultNodeData("annotation");
|
|
|
|
expect(data).toHaveProperty("sourceImage", null);
|
|
expect(data).toHaveProperty("annotations");
|
|
expect(Array.isArray((data as any).annotations)).toBe(true);
|
|
expect(data).toHaveProperty("outputImage", null);
|
|
});
|
|
|
|
it("creates correct structure for prompt", () => {
|
|
const data = createDefaultNodeData("prompt");
|
|
|
|
expect(data).toHaveProperty("prompt", "");
|
|
});
|
|
|
|
it("creates correct structure for array", () => {
|
|
const data = createDefaultNodeData("array");
|
|
|
|
expect(data).toHaveProperty("inputText", null);
|
|
expect(data).toHaveProperty("splitMode", "delimiter");
|
|
expect(data).toHaveProperty("delimiter", "*");
|
|
expect(data).toHaveProperty("regexPattern", "");
|
|
expect(data).toHaveProperty("trimItems", true);
|
|
expect(data).toHaveProperty("removeEmpty", true);
|
|
expect(data).toHaveProperty("selectedOutputIndex", null);
|
|
expect(data).toHaveProperty("outputItems");
|
|
expect(Array.isArray((data as any).outputItems)).toBe(true);
|
|
expect(data).toHaveProperty("outputText", "[]");
|
|
expect(data).toHaveProperty("error", null);
|
|
});
|
|
|
|
it("creates correct structure for nanoBanana", () => {
|
|
const data = createDefaultNodeData("nanoBanana");
|
|
|
|
expect(data).toHaveProperty("inputImages");
|
|
expect(data).toHaveProperty("inputPrompt", null);
|
|
expect(data).toHaveProperty("outputImage", null);
|
|
expect(data).toHaveProperty("aspectRatio");
|
|
expect(data).toHaveProperty("resolution");
|
|
expect(data).toHaveProperty("model");
|
|
expect(data).toHaveProperty("selectedModel");
|
|
expect(data).toHaveProperty("batchSize", 1);
|
|
expect(data).toHaveProperty("useGoogleSearch");
|
|
expect(data).toHaveProperty("status", "idle");
|
|
expect(data).toHaveProperty("error", null);
|
|
expect(data).toHaveProperty("imageHistory");
|
|
expect(data).toHaveProperty("selectedHistoryIndex", 0);
|
|
});
|
|
|
|
it("creates correct structure for generateVideo", () => {
|
|
const data = createDefaultNodeData("generateVideo");
|
|
|
|
expect(data).toHaveProperty("inputImages");
|
|
expect(data).toHaveProperty("inputPrompt", null);
|
|
expect(data).toHaveProperty("outputVideo", null);
|
|
expect(data).toHaveProperty("selectedModel");
|
|
expect(data).toHaveProperty("durationSeconds", 5);
|
|
expect(data).toHaveProperty("resolution", "720p");
|
|
expect(data).toHaveProperty("status", "idle");
|
|
expect(data).toHaveProperty("error", null);
|
|
expect(data).toHaveProperty("videoHistory");
|
|
expect(data).toHaveProperty("selectedVideoHistoryIndex", 0);
|
|
});
|
|
|
|
it("creates correct structure for llmGenerate", () => {
|
|
const data = createDefaultNodeData("llmGenerate");
|
|
|
|
expect(data).toHaveProperty("inputPrompt", null);
|
|
expect(data).toHaveProperty("inputImages");
|
|
expect(data).toHaveProperty("outputText", null);
|
|
expect(data).toHaveProperty("provider", "newapiwg");
|
|
expect(data).toHaveProperty("model");
|
|
expect(data).toHaveProperty("temperature");
|
|
expect(data).toHaveProperty("maxTokens");
|
|
expect(data).toHaveProperty("status", "idle");
|
|
expect(data).toHaveProperty("error", null);
|
|
});
|
|
|
|
it("creates correct structure for glbViewer", () => {
|
|
const data = createDefaultNodeData("glbViewer");
|
|
|
|
expect(data).toHaveProperty("glbUrl", null);
|
|
expect(data).toHaveProperty("filename", null);
|
|
expect(data).toHaveProperty("capturedImage", null);
|
|
});
|
|
|
|
it("creates correct structure for output", () => {
|
|
const data = createDefaultNodeData("output");
|
|
|
|
expect(data).toHaveProperty("image", null);
|
|
});
|
|
|
|
it("uses stored defaults for nanoBanana when available", () => {
|
|
const customSettings = {
|
|
aspectRatio: "16:9",
|
|
resolution: "2K",
|
|
model: "nano-banana",
|
|
useGoogleSearch: true,
|
|
};
|
|
localStorageMock.setItem(
|
|
"node-banana-nanoBanana-defaults",
|
|
JSON.stringify(customSettings)
|
|
);
|
|
|
|
const data = createDefaultNodeData("nanoBanana");
|
|
|
|
expect((data as any).aspectRatio).toBe("16:9");
|
|
expect((data as any).resolution).toBe("2K");
|
|
expect((data as any).model).toBe("nano-banana");
|
|
expect((data as any).useGoogleSearch).toBe(true);
|
|
});
|
|
|
|
it("uses node defaults selectedModel for nanoBanana when set", () => {
|
|
const nodeDefaultsConfig = {
|
|
generateImage: {
|
|
selectedModel: { provider: "fal", modelId: "flux-pro", displayName: "Flux Pro" },
|
|
aspectRatio: "4:3",
|
|
useGoogleSearch: true,
|
|
},
|
|
};
|
|
localStorageMock.setItem(
|
|
"node-banana-node-defaults",
|
|
JSON.stringify(nodeDefaultsConfig)
|
|
);
|
|
|
|
const data = createDefaultNodeData("nanoBanana");
|
|
|
|
expect((data as any).selectedModel).toEqual({
|
|
provider: "fal",
|
|
modelId: "flux-pro",
|
|
displayName: "Flux Pro",
|
|
});
|
|
expect((data as any).aspectRatio).toBe("4:3");
|
|
expect((data as any).useGoogleSearch).toBe(true);
|
|
});
|
|
|
|
it("uses gpt-image-2-all for nanoBanana when no node default is set", () => {
|
|
const data = createDefaultNodeData("nanoBanana");
|
|
|
|
expect((data as any).selectedModel).toEqual({
|
|
provider: "newapiwg",
|
|
modelId: "gpt-image-2-all",
|
|
displayName: "gpt-image-2-all",
|
|
capabilities: ["text-to-image", "image-to-image"],
|
|
});
|
|
});
|
|
|
|
it("uses node defaults selectedModel for generateVideo when set", () => {
|
|
const nodeDefaultsConfig = {
|
|
generateVideo: {
|
|
selectedModel: { provider: "replicate", modelId: "kling-video", displayName: "Kling Video" },
|
|
durationSeconds: 8,
|
|
resolution: "1080p",
|
|
soundEnabled: false,
|
|
},
|
|
};
|
|
localStorageMock.setItem(
|
|
"node-banana-node-defaults",
|
|
JSON.stringify(nodeDefaultsConfig)
|
|
);
|
|
|
|
const data = createDefaultNodeData("generateVideo");
|
|
|
|
expect((data as any).selectedModel).toEqual({
|
|
provider: "replicate",
|
|
modelId: "kling-video",
|
|
displayName: "Kling Video",
|
|
});
|
|
expect((data as any).durationSeconds).toBe(8);
|
|
expect((data as any).resolution).toBe("1080p");
|
|
expect((data as any).soundEnabled).toBe(false);
|
|
});
|
|
|
|
it("uses NewApiWG selectedModel for generateVideo when not set", () => {
|
|
const data = createDefaultNodeData("generateVideo");
|
|
|
|
expect((data as any).selectedModel).toEqual({
|
|
provider: "newapiwg",
|
|
modelId: "viduq3-turbo",
|
|
displayName: "viduq3-turbo",
|
|
capabilities: ["text-to-video", "image-to-video"],
|
|
});
|
|
expect((data as any).durationSeconds).toBe(5);
|
|
expect((data as any).resolution).toBe("720p");
|
|
expect((data as any).soundEnabled).toBeUndefined();
|
|
});
|
|
|
|
it("uses node defaults for llmGenerate when set", () => {
|
|
const nodeDefaultsConfig = {
|
|
llm: {
|
|
provider: "openai",
|
|
model: "gpt-4.1-mini",
|
|
temperature: 0.3,
|
|
maxTokens: 4096,
|
|
},
|
|
};
|
|
localStorageMock.setItem(
|
|
"node-banana-node-defaults",
|
|
JSON.stringify(nodeDefaultsConfig)
|
|
);
|
|
|
|
const data = createDefaultNodeData("llmGenerate");
|
|
|
|
expect((data as any).provider).toBe("openai");
|
|
expect((data as any).model).toBe("gpt-4.1-mini");
|
|
expect((data as any).temperature).toBe(0.3);
|
|
expect((data as any).maxTokens).toBe(4096);
|
|
});
|
|
|
|
it("falls back to NewApiWG llmGenerate defaults when not set", () => {
|
|
const data = createDefaultNodeData("llmGenerate");
|
|
|
|
expect((data as any).provider).toBe("newapiwg");
|
|
expect((data as any).model).toBe("doubao-seed-2-0-lite-260428");
|
|
expect((data as any).temperature).toBe(0.7);
|
|
expect((data as any).maxTokens).toBe(8192);
|
|
});
|
|
|
|
it("partially overrides llmGenerate defaults with node defaults", () => {
|
|
const nodeDefaultsConfig = {
|
|
llm: {
|
|
temperature: 0.9,
|
|
},
|
|
};
|
|
localStorageMock.setItem(
|
|
"node-banana-node-defaults",
|
|
JSON.stringify(nodeDefaultsConfig)
|
|
);
|
|
|
|
const data = createDefaultNodeData("llmGenerate");
|
|
|
|
// Should use node default for temperature
|
|
expect((data as any).temperature).toBe(0.9);
|
|
// Should fall back to NewApiWG defaults for others
|
|
expect((data as any).provider).toBe("newapiwg");
|
|
expect((data as any).model).toBe("doubao-seed-2-0-lite-260428");
|
|
expect((data as any).maxTokens).toBe(8192);
|
|
});
|
|
});
|
|
});
|
|
|