50 lines
2.8 KiB
TypeScript
50 lines
2.8 KiB
TypeScript
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("修改 图1<point>456 321</point>");
|
||
}
|
||
});
|
||
|
||
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();
|
||
});
|
||
});
|