2026-07-16 16:42:30 +08:00
|
|
|
import assert from "node:assert/strict";
|
|
|
|
|
import test from "node:test";
|
|
|
|
|
|
|
|
|
|
import { createVectorCropSiblingNode, croppedSvgViewBox, vectorCropRasterSize, vectorCropSiblingSize } from "../src/ui/pages/CanvasWorkspace/canvas/cadCrop.ts";
|
2026-07-16 20:46:31 +08:00
|
|
|
import { cropSelectionFromScreenDrag, cropSelectionScreenBounds, visibleCropSelectionForBounds } from "../src/ui/pages/CanvasWorkspace/canvas/cropViewport.ts";
|
2026-07-16 16:42:30 +08:00
|
|
|
|
|
|
|
|
const source = {
|
|
|
|
|
id: "cad-source",
|
|
|
|
|
type: "image",
|
|
|
|
|
title: "plan (DWG).svg",
|
|
|
|
|
x: 100,
|
|
|
|
|
y: 80,
|
|
|
|
|
width: 1200,
|
|
|
|
|
height: 840,
|
|
|
|
|
content: "/canvas-assets/plan.svg",
|
|
|
|
|
tone: "transparent-image",
|
|
|
|
|
status: "success",
|
|
|
|
|
layerRole: "cad-vector",
|
|
|
|
|
semanticContext: "full drawing context"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
test("creates a CAD crop beside the immutable source", () => {
|
|
|
|
|
const before = structuredClone(source);
|
|
|
|
|
const crop = createVectorCropSiblingNode(source, {
|
|
|
|
|
id: "cad-crop",
|
|
|
|
|
title: "plan crop",
|
|
|
|
|
publicUrl: "/canvas-assets/plan-crop.webp",
|
|
|
|
|
cropWidth: 180,
|
|
|
|
|
cropHeight: 120,
|
|
|
|
|
semanticContext: "cropped room context"
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(source, before);
|
|
|
|
|
assert.equal(crop.x, source.x + source.width + 40);
|
|
|
|
|
assert.equal(crop.y, source.y);
|
|
|
|
|
assert.equal(crop.layerRole, "cad-crop");
|
|
|
|
|
assert.equal(crop.semanticContext, "cropped room context");
|
|
|
|
|
assert.equal(crop.content, "/canvas-assets/plan-crop.webp");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("keeps cropped sibling dimensions readable and bounded", () => {
|
|
|
|
|
assert.deepEqual(vectorCropSiblingSize(120, 60), { width: 220, height: 110 });
|
|
|
|
|
assert.deepEqual(vectorCropSiblingSize(1280, 640), { width: 640, height: 320 });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("renders small vector selections at model-readable resolution", () => {
|
|
|
|
|
assert.deepEqual(vectorCropRasterSize(60, 30), { width: 2048, height: 1024 });
|
|
|
|
|
assert.deepEqual(vectorCropRasterSize(100, 400), { width: 512, height: 2048 });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("maps a normalized crop into a non-zero SVG viewBox without changing its aspect", () => {
|
|
|
|
|
const crop = croppedSvgViewBox(
|
|
|
|
|
{ minX: -240, minY: 80, width: 1800, height: 1200 },
|
|
|
|
|
{ x: 0.25, y: 0.1, width: 0.5, height: 0.3 }
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(crop, { minX: 210, minY: 200, width: 900, height: 360 });
|
|
|
|
|
assert.deepEqual(vectorCropRasterSize(crop.width, crop.height), { width: 2048, height: 819 });
|
|
|
|
|
assert.ok(Math.abs(crop.width / crop.height - 2.5) < Number.EPSILON);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("initializes an oversized image crop inside the visible stage", () => {
|
|
|
|
|
const imageBounds = { left: -400, top: -300, width: 1800, height: 1400 };
|
|
|
|
|
const selection = visibleCropSelectionForBounds(imageBounds, { width: 1200, height: 800 });
|
|
|
|
|
const visibleBounds = cropSelectionScreenBounds(imageBounds, selection);
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(visibleBounds, { left: 24, top: 24, width: 1152, height: 704 });
|
|
|
|
|
assert.ok(selection.x > 0 && selection.y > 0);
|
|
|
|
|
assert.ok(selection.width < 1 && selection.height < 1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("keeps the full crop when the image already fits the safe viewport", () => {
|
|
|
|
|
const imageBounds = { left: 100, top: 80, width: 640, height: 420 };
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(visibleCropSelectionForBounds(imageBounds, { width: 1200, height: 800 }), { x: 0, y: 0, width: 1, height: 1 });
|
|
|
|
|
});
|
2026-07-16 20:46:31 +08:00
|
|
|
|
|
|
|
|
test("clamps a manually drawn crop to the currently visible image region", () => {
|
|
|
|
|
const imageBounds = { left: -200, top: -100, width: 1000, height: 800 };
|
|
|
|
|
const visibleSelection = { x: 0.2, y: 0.125, width: 0.6, height: 0.75 };
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
cropSelectionFromScreenDrag(imageBounds, visibleSelection, { x: 0, y: 0 }, { x: 900, y: 700 }),
|
|
|
|
|
visibleSelection
|
|
|
|
|
);
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
cropSelectionFromScreenDrag(imageBounds, visibleSelection, { x: 100, y: 140 }, { x: 500, y: 460 }),
|
|
|
|
|
{ x: 0.3, y: 0.3, width: 0.4, height: 0.4 }
|
|
|
|
|
);
|
|
|
|
|
});
|