- 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
104 lines
4.0 KiB
TypeScript
104 lines
4.0 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
||
import {
|
||
appendImagesDeduped,
|
||
imageFileKey,
|
||
} from "@/lib/multi-image";
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Multi-image helpers – behavioral tests
|
||
//
|
||
// These functions are pure; they own the "append + dedup" contract.
|
||
// Tests describe what the system DOES, not how it's implemented.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
describe("imageFileKey – stable identity for dedup", () => {
|
||
test("returns a string combining name and size", () => {
|
||
const file = new File(["hello"], "flyer.png", { type: "image/png" });
|
||
const key = imageFileKey(file);
|
||
expect(typeof key).toBe("string");
|
||
expect(key).toContain("flyer.png");
|
||
expect(key).toContain(String(file.size));
|
||
});
|
||
|
||
test("two files with the same name and size produce the same key", () => {
|
||
const a = new File(["hello"], "a.png", { type: "image/png" });
|
||
const b = new File(["hello"], "a.png", { type: "image/png" });
|
||
expect(imageFileKey(a)).toBe(imageFileKey(b));
|
||
});
|
||
|
||
test("two files with the same name but different content produce different keys", () => {
|
||
const a = new File(["hello"], "a.png", { type: "image/png" });
|
||
const b = new File(["hello world"], "a.png", { type: "image/png" });
|
||
expect(imageFileKey(a)).not.toBe(imageFileKey(b));
|
||
});
|
||
|
||
test("two files with different names but same content produce different keys", () => {
|
||
const a = new File(["hello"], "a.png", { type: "image/png" });
|
||
const b = new File(["hello"], "b.png", { type: "image/png" });
|
||
expect(imageFileKey(a)).not.toBe(imageFileKey(b));
|
||
});
|
||
});
|
||
|
||
describe("appendImagesDeduped – append with deduplication", () => {
|
||
const makeFile = (name: string, content = "data") =>
|
||
new File([content], name, { type: "image/png" });
|
||
|
||
test("appends new files to an empty list", () => {
|
||
const result = appendImagesDeduped([], [makeFile("a.png")]);
|
||
expect(result).toHaveLength(1);
|
||
expect(result[0].name).toBe("a.png");
|
||
});
|
||
|
||
test("appends new files to an existing list", () => {
|
||
const existing = [makeFile("a.png")];
|
||
const incoming = [makeFile("b.png")];
|
||
const result = appendImagesDeduped(existing, incoming);
|
||
expect(result).toHaveLength(2);
|
||
expect(result.map((f) => f.name)).toEqual(["a.png", "b.png"]);
|
||
});
|
||
|
||
test("silently ignores incoming files that are exact duplicates (same name+size)", () => {
|
||
const existing = [makeFile("a.png")];
|
||
const incoming = [makeFile("a.png")]; // identical name + content = same size
|
||
const result = appendImagesDeduped(existing, incoming);
|
||
expect(result).toHaveLength(1);
|
||
expect(result[0].name).toBe("a.png");
|
||
});
|
||
|
||
test("appends non-duplicate files even when some duplicates are in the batch", () => {
|
||
const existing = [makeFile("a.png")];
|
||
const incoming = [makeFile("a.png"), makeFile("b.png")];
|
||
const result = appendImagesDeduped(existing, incoming);
|
||
expect(result).toHaveLength(2);
|
||
expect(result.map((f) => f.name)).toEqual(["a.png", "b.png"]);
|
||
});
|
||
|
||
test("preserves existing list order — new files are appended at the end", () => {
|
||
const existing = [makeFile("first.png"), makeFile("second.png")];
|
||
const incoming = [makeFile("third.png")];
|
||
const result = appendImagesDeduped(existing, incoming);
|
||
expect(result.map((f) => f.name)).toEqual([
|
||
"first.png",
|
||
"second.png",
|
||
"third.png",
|
||
]);
|
||
});
|
||
|
||
test("returns a new array (does not mutate the existing list)", () => {
|
||
const existing = [makeFile("a.png")];
|
||
const incoming = [makeFile("b.png")];
|
||
const result = appendImagesDeduped(existing, incoming);
|
||
expect(result).not.toBe(existing); // new reference
|
||
expect(existing).toHaveLength(1); // original untouched
|
||
});
|
||
|
||
test("handles multiple incoming files with internal duplicates", () => {
|
||
// Two identical files in the same incoming batch
|
||
const existing: File[] = [];
|
||
const incoming = [makeFile("a.png"), makeFile("a.png"), makeFile("b.png")];
|
||
const result = appendImagesDeduped(existing, incoming);
|
||
expect(result).toHaveLength(2);
|
||
expect(result.map((f) => f.name)).toEqual(["a.png", "b.png"]);
|
||
});
|
||
});
|