- Updated OpenRouter integration to accept an array of image URLs - Updated ImagePicker to use the `multiple` attribute natively - Added `appendImagesDeduped` for handling client-side image deduplication - Enhanced clipboard pasting to extract multiple images at once - Rendered multiple images in a horizontal thumbnail strip in the AIToolbar - Added tests to cover multi-image logic and AI request mapping
97 lines
3.8 KiB
TypeScript
97 lines
3.8 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
||
import { buildMultimodalMessages } from "@/lib/ai-event-messages";
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// buildMultimodalMessages – behavioral tests
|
||
//
|
||
// Public behavior under test: given a system prompt, an optional text prompt,
|
||
// and an array of base64 image strings, returns a well-formed messages array
|
||
// for the OpenRouter chat API.
|
||
//
|
||
// We test WHAT the function produces (message structure), not HOW it does it.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const SYSTEM_PROMPT = "You are an assistant…";
|
||
const FAKE_PNG = "data:image/png;base64,abc123";
|
||
const FAKE_JPEG = "data:image/jpeg;base64,def456";
|
||
|
||
describe("buildMultimodalMessages – message structure", () => {
|
||
test("first message is always the system prompt", () => {
|
||
const messages = buildMultimodalMessages(SYSTEM_PROMPT, "hello", [FAKE_PNG]);
|
||
expect(messages[0].role).toBe("system");
|
||
expect((messages[0].content as string)).toBe(SYSTEM_PROMPT);
|
||
});
|
||
|
||
test("second message is the user message", () => {
|
||
const messages = buildMultimodalMessages(SYSTEM_PROMPT, "hello", [FAKE_PNG]);
|
||
expect(messages[1].role).toBe("user");
|
||
});
|
||
|
||
test("user message content array starts with the text part", () => {
|
||
const messages = buildMultimodalMessages(SYSTEM_PROMPT, "any prompt", [FAKE_PNG]);
|
||
const userContent = messages[1].content as Array<{ type: string }>;
|
||
expect(userContent[0].type).toBe("text");
|
||
});
|
||
|
||
test("user message content array includes one image_url part per image", () => {
|
||
const messages = buildMultimodalMessages(SYSTEM_PROMPT, "prompt", [FAKE_PNG, FAKE_JPEG]);
|
||
const userContent = messages[1].content as Array<{ type: string }>;
|
||
const imageparts = userContent.filter((p) => p.type === "image_url");
|
||
expect(imageparts).toHaveLength(2);
|
||
});
|
||
|
||
test("each image_url part carries the correct base64 URL", () => {
|
||
const messages = buildMultimodalMessages(SYSTEM_PROMPT, undefined, [FAKE_PNG, FAKE_JPEG]);
|
||
const userContent = messages[1].content as Array<{
|
||
type: string;
|
||
imageUrl?: { url: string };
|
||
}>;
|
||
const imageParts = userContent.filter((p) => p.type === "image_url");
|
||
expect(imageParts[0].imageUrl?.url).toBe(FAKE_PNG);
|
||
expect(imageParts[1].imageUrl?.url).toBe(FAKE_JPEG);
|
||
});
|
||
|
||
test("text part uses a fallback when prompt is undefined", () => {
|
||
const messages = buildMultimodalMessages(SYSTEM_PROMPT, undefined, [FAKE_PNG]);
|
||
const userContent = messages[1].content as Array<{
|
||
type: string;
|
||
text?: string;
|
||
}>;
|
||
const textPart = userContent.find((p) => p.type === "text");
|
||
expect(typeof textPart?.text).toBe("string");
|
||
expect(textPart?.text?.length ?? 0).toBeGreaterThan(0);
|
||
});
|
||
|
||
test("text part carries the provided prompt text when given", () => {
|
||
const prompt = "Extract all events from these flyers";
|
||
const messages = buildMultimodalMessages(SYSTEM_PROMPT, prompt, [FAKE_PNG]);
|
||
const userContent = messages[1].content as Array<{
|
||
type: string;
|
||
text?: string;
|
||
}>;
|
||
const textPart = userContent.find((p) => p.type === "text");
|
||
expect(textPart?.text).toBe(prompt);
|
||
});
|
||
|
||
test("produces exactly 2 messages (system + user)", () => {
|
||
const messages = buildMultimodalMessages(SYSTEM_PROMPT, "x", [FAKE_PNG]);
|
||
expect(messages).toHaveLength(2);
|
||
});
|
||
|
||
test("single image produces content array of length 2 (1 text + 1 image)", () => {
|
||
const messages = buildMultimodalMessages(SYSTEM_PROMPT, "x", [FAKE_PNG]);
|
||
const userContent = messages[1].content as unknown[];
|
||
expect(userContent).toHaveLength(2);
|
||
});
|
||
|
||
test("three images produce content array of length 4 (1 text + 3 images)", () => {
|
||
const messages = buildMultimodalMessages(SYSTEM_PROMPT, "x", [
|
||
FAKE_PNG,
|
||
FAKE_JPEG,
|
||
FAKE_PNG,
|
||
]);
|
||
const userContent = messages[1].content as unknown[];
|
||
expect(userContent).toHaveLength(4);
|
||
});
|
||
});
|