import { describe, expect, it } from "vitest"; import { canvasAnnotationPoint, constrainCanvasView, fitCanvasImage, FIT_CANVAS_VIEW, zoomCanvasAt } from "@/lib/client/seedream-viewport"; import { buildSeedreamInteractivePrompt } from "@/lib/seedream/creation"; describe("Seedream canvas view geometry", () => { const viewport = { width: 800, height: 500 }; it.each([{ width: 1000, height: 2000 }, { width: 2000, height: 1000 }])("fits a $width × $height image without stretching or inner scrollbars", (image) => { const fitted = fitCanvasImage(image, viewport); expect(fitted.width).toBeLessThanOrEqual(780); expect(fitted.height).toBeLessThanOrEqual(480); expect(fitted.width / fitted.height).toBeCloseTo(image.width / image.height); }); it("keeps the cursor over the same image point during zoom", () => { const fitted = fitCanvasImage({ width: 1000, height: 1000 }, viewport); const before = { zoom: 2, x: 30, y: -20 }; const anchor = { x: 100, y: 80 }; const after = zoomCanvasAt(before, 3, anchor, fitted, viewport); expect((anchor.x - after.x) / after.zoom).toBeCloseTo((anchor.x - before.x) / before.zoom); expect((anchor.y - after.y) / after.zoom).toBeCloseTo((anchor.y - before.y) / before.zoom); }); it("constrains pan at image edges and restores a centered fit", () => { const fitted = { width: 240, height: 480 }; expect(constrainCanvasView({ zoom: 3, x: 900, y: -9999 }, fitted, viewport)).toEqual({ zoom: 3, x: 0, y: -470 }); expect(constrainCanvasView(FIT_CANVAS_VIEW, fitted, viewport)).toEqual(FIT_CANVAS_VIEW); expect(constrainCanvasView({ zoom: 99, x: 0, y: 0 }, fitted, viewport).zoom).toBe(8); expect(constrainCanvasView({ zoom: 0.01, x: 0, y: 0 }, fitted, viewport).zoom).toBe(0.25); }); it("maps zoomed and panned box, point and brush coordinates back to the original image", () => { const normalized = [{ x: 100, y: 200 }, { x: 456, y: 321 }, { x: 700, y: 800 }]; for (const bounds of [ { left: 100, top: 50, width: 400, height: 600 }, { left: -720, top: -380, width: 1600, height: 2400 }, { left: 320, top: 120, width: 800, height: 1200 } ]) { const points = normalized.map((point) => canvasAnnotationPoint({ x: bounds.left + bounds.width * point.x / 1000, y: bounds.top + bounds.height * point.y / 1000 }, bounds)); expect(points).toEqual(normalized); expect(buildSeedreamInteractivePrompt("修改 @标注1", [{ id: "point", index: 1, kind: "point", point: points[1]! }])).toBe("修改 图1456 321"); } }); it("clamps drawing outside the image and ignores an unloaded image", () => { expect(canvasAnnotationPoint({ x: -1, y: 999 }, { left: 0, top: 0, width: 100, height: 100 })).toEqual({ x: 0, y: 1000 }); expect(canvasAnnotationPoint({ x: 0, y: 0 }, { left: 0, top: 0, width: 0, height: 0 })).toBeNull(); }); });