import { AffineTransform } from "@noya-app/noya-geometry"; import { describe, expect, it } from "bun:test"; import { transformDraftElements } from "../elementUtils"; describe("transformDraftElements", () => { // Helper function to create test elements const createTestElement = ( id: string, rect: { x: number; y: number; width: number; height: number } ) => ({ id, rect: { ...rect }, }); it("should transform elements", () => { const draftElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 100 }), ]; const snapshotElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 100 }), ]; const transform = AffineTransform.translate(50, 30); transformDraftElements({ draftElements, snapshotElements, transform, }); expect(draftElements[0].rect).toEqual({ x: 50, y: 30, width: 100, height: 100, }); }); it("should enforce minimum element size", () => { const draftElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 100 }), ]; const snapshotElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 100 }), ]; const transform = AffineTransform.scale(0.05, 0.05); transformDraftElements({ draftElements, snapshotElements, transform, minimumElementSize: 10, }); expect(draftElements[0].rect).toEqual({ x: 0, y: 0, width: 10, height: 10, }); }); it("should handle multiple elements", () => { const draftElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 100 }), createTestElement("2", { x: 200, y: 200, width: 50, height: 50 }), ]; const snapshotElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 100 }), createTestElement("2", { x: 200, y: 200, width: 50, height: 50 }), ]; const transform = AffineTransform.scale(2, 2); transformDraftElements({ draftElements, snapshotElements, transform, }); expect(draftElements[0].rect).toEqual({ x: 0, y: 0, width: 200, height: 200, }); expect(draftElements[1].rect).toEqual({ x: 400, y: 400, width: 100, height: 100, }); }); it("should ignore elements not present in snapshot", () => { const draftElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 100 }), createTestElement("2", { x: 200, y: 200, width: 50, height: 50 }), ]; const snapshotElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 100 }), ]; const transform = AffineTransform.scale(2, 2); transformDraftElements({ draftElements, snapshotElements, transform, }); expect(draftElements[0].rect).toEqual({ x: 0, y: 0, width: 200, height: 200, }); // Element 2 should remain unchanged expect(draftElements[1].rect).toEqual({ x: 200, y: 200, width: 50, height: 50, }); }); it("should maintain aspect ratio when scaling", () => { const draftElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 50 }), ]; const snapshotElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 50 }), ]; const transform = AffineTransform.scale(1.5, 2); // Try to change aspect ratio transformDraftElements({ draftElements, snapshotElements, transform, preserveAspectRatio: true, }); // Should maintain 2:1 aspect ratio expect( draftElements[0].rect.width / draftElements[0].rect.height ).toBeCloseTo(2); }); it("should maintain aspect ratio with minimum size constraints", () => { const draftElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 50 }), ]; const snapshotElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 50 }), ]; const transform = AffineTransform.scale(0.1, 0.1); // Try to make it very small transformDraftElements({ draftElements, snapshotElements, transform, minimumElementSize: 20, preserveAspectRatio: true, }); // Should maintain 2:1 aspect ratio while respecting minimum size expect(draftElements[0].rect.width).toBe(40); // 2 * minimum height expect(draftElements[0].rect.height).toBe(20); // minimum height }); it("should maintain aspect ratio with extreme non-uniform scaling", () => { const draftElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 50 }), ]; const snapshotElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 50 }), ]; const transform = AffineTransform.scale(3, 0.5); // Very different scales transformDraftElements({ draftElements, snapshotElements, transform, preserveAspectRatio: true, }); // Should maintain 2:1 aspect ratio despite extreme scaling expect( draftElements[0].rect.width / draftElements[0].rect.height ).toBeCloseTo(2); }); it("should maintain aspect ratio with rotation", () => { const draftElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 50 }), ]; const snapshotElements = [ createTestElement("1", { x: 0, y: 0, width: 100, height: 50 }), ]; // Scale and rotate const transform = AffineTransform.rotate(Math.PI / 4).scale(1.5, 2); transformDraftElements({ draftElements, snapshotElements, transform, preserveAspectRatio: true, }); // Should maintain 2:1 aspect ratio despite rotation expect( draftElements[0].rect.width / draftElements[0].rect.height ).toBeCloseTo(2); }); it("should normalize negative scaling by default", () => { const draftElements = [ createTestElement("1", { x: 100, y: 100, width: 100, height: 50 }), ]; const snapshotElements = [ createTestElement("1", { x: 100, y: 100, width: 100, height: 50 }), ]; const transform = AffineTransform.scale(-1, -1); // Flip both dimensions transformDraftElements({ draftElements, snapshotElements, transform, }); // Position should be adjusted to maintain visual appearance expect(draftElements[0].rect).toEqual({ x: -200, y: -150, width: 100, height: 50, }); }); it("should prevent negative scaling when normalizeNegativeScale is false", () => { const draftElements = [ createTestElement("1", { x: 100, y: 100, width: 100, height: 50 }), ]; const snapshotElements = [ createTestElement("1", { x: 100, y: 100, width: 100, height: 50 }), ]; const transform = AffineTransform.scale(-1, -1); // Flip both dimensions transformDraftElements({ draftElements, snapshotElements, transform, normalizeNegativeScale: false, }); // Should maintain original position and use absolute scale values expect(draftElements[0].rect).toEqual({ x: 100, y: 100, width: 100, height: 50, }); }); it("should handle mixed positive/negative scaling with normalization", () => { const draftElements = [ createTestElement("1", { x: 100, y: 100, width: 100, height: 50 }), ]; const snapshotElements = [ createTestElement("1", { x: 100, y: 100, width: 100, height: 50 }), ]; const transform = AffineTransform.scale(-2, 1.5); // Negative x scale, positive y scale transformDraftElements({ draftElements, snapshotElements, transform, }); // Width should be positive, x position should be adjusted expect(draftElements[0].rect.width).toBe(200); expect(draftElements[0].rect.height).toBe(75); expect(draftElements[0].rect.x).toBe(-400); // Account for both translation and scaling expect(draftElements[0].rect.y).toBe(150); // Account for positive y scaling }); it("should maintain aspect ratio with negative scaling", () => { const draftElements = [ createTestElement("1", { x: 100, y: 100, width: 100, height: 50 }), ]; const snapshotElements = [ createTestElement("1", { x: 100, y: 100, width: 100, height: 50 }), ]; const transform = AffineTransform.scale(-2, -1.5); // Different negative scales transformDraftElements({ draftElements, snapshotElements, transform, preserveAspectRatio: true, }); // Should maintain 2:1 aspect ratio despite negative scaling expect( draftElements[0].rect.width / draftElements[0].rect.height ).toBeCloseTo(2); // Dimensions should be positive expect(draftElements[0].rect.width).toBeGreaterThan(0); expect(draftElements[0].rect.height).toBeGreaterThan(0); }); });