add transform rect tests
This commit is contained in:
parent
8aae9bf462
commit
3ed7354296
|
|
@ -0,0 +1,147 @@
|
|||
import { expect, test, describe } from 'bun:test';
|
||||
import { TransformDOMRect, TransformDOMRectReadonly } from '../common/TransformDOMRect';
|
||||
import { Point } from '../common/types';
|
||||
|
||||
// Helper for comparing points with floating point values
|
||||
const expectPointClose = (actual: Point, expected: Point) => {
|
||||
expect(actual.x).toBeCloseTo(expected.x);
|
||||
expect(actual.y).toBeCloseTo(expected.y);
|
||||
};
|
||||
|
||||
describe('TransformDOMRect', () => {
|
||||
test('constructor initializes with default values', () => {
|
||||
const rect = new TransformDOMRect();
|
||||
expect(rect.x).toBe(0);
|
||||
expect(rect.y).toBe(0);
|
||||
expect(rect.width).toBe(0);
|
||||
expect(rect.height).toBe(0);
|
||||
expect(rect.rotation).toBe(0);
|
||||
});
|
||||
|
||||
test('constructor initializes with provided values', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
x: 10,
|
||||
y: 20,
|
||||
width: 100,
|
||||
height: 50,
|
||||
rotation: Math.PI / 4,
|
||||
});
|
||||
expect(rect.x).toBe(10);
|
||||
expect(rect.y).toBe(20);
|
||||
expect(rect.width).toBe(100);
|
||||
expect(rect.height).toBe(50);
|
||||
expect(rect.rotation).toBe(Math.PI / 4);
|
||||
});
|
||||
|
||||
test('DOMRect properties are calculated correctly', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
x: 10,
|
||||
y: 20,
|
||||
width: 100,
|
||||
height: 50,
|
||||
});
|
||||
expect(rect.left).toBe(10);
|
||||
expect(rect.top).toBe(20);
|
||||
expect(rect.right).toBe(110);
|
||||
expect(rect.bottom).toBe(70);
|
||||
});
|
||||
|
||||
test('vertices returns correct local space corners', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
width: 100,
|
||||
height: 50,
|
||||
});
|
||||
|
||||
const vertices = rect.vertices();
|
||||
expectPointClose(vertices[0], { x: 0, y: 0 });
|
||||
expectPointClose(vertices[1], { x: 100, y: 0 });
|
||||
expectPointClose(vertices[2], { x: 100, y: 50 });
|
||||
expectPointClose(vertices[3], { x: 0, y: 50 });
|
||||
});
|
||||
|
||||
test('coordinate space conversion with rotation', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
x: 10,
|
||||
y: 20,
|
||||
width: 100,
|
||||
height: 50,
|
||||
rotation: Math.PI / 2, // 90 degrees
|
||||
});
|
||||
|
||||
const parentPoint = { x: 10, y: 20 };
|
||||
const localPoint = rect.toLocalSpace(parentPoint);
|
||||
const backToParent = rect.toParentSpace(localPoint);
|
||||
|
||||
expectPointClose(backToParent, parentPoint);
|
||||
});
|
||||
|
||||
test('getBounds returns correct bounding box after rotation', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 50,
|
||||
rotation: Math.PI / 2, // 90 degrees
|
||||
});
|
||||
|
||||
const bounds = rect.getBounds();
|
||||
expect(bounds.width).toBeCloseTo(50);
|
||||
expect(bounds.height).toBeCloseTo(100);
|
||||
});
|
||||
|
||||
test('setters update matrices correctly', () => {
|
||||
const rect = new TransformDOMRect();
|
||||
rect.x = 10;
|
||||
rect.y = 20;
|
||||
rect.width = 100;
|
||||
rect.height = 50;
|
||||
rect.rotation = Math.PI / 4;
|
||||
|
||||
const point = { x: 0, y: 0 };
|
||||
const transformed = rect.toParentSpace(point);
|
||||
const backToLocal = rect.toLocalSpace(transformed);
|
||||
|
||||
expectPointClose(backToLocal, point);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TransformDOMRectReadonly', () => {
|
||||
test('prevents modifications through setters', () => {
|
||||
const rect = new TransformDOMRectReadonly({
|
||||
x: 10,
|
||||
y: 20,
|
||||
width: 100,
|
||||
height: 50,
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
rect.x = 20;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
rect.y = 30;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
rect.width = 200;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
rect.height = 100;
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
rect.rotation = Math.PI;
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('allows reading properties', () => {
|
||||
const rect = new TransformDOMRectReadonly({
|
||||
x: 10,
|
||||
y: 20,
|
||||
width: 100,
|
||||
height: 50,
|
||||
});
|
||||
|
||||
expect(rect.x).toBe(10);
|
||||
expect(rect.y).toBe(20);
|
||||
expect(rect.width).toBe(100);
|
||||
expect(rect.height).toBe(50);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
import { expect, test, describe } from 'bun:test';
|
||||
import { TransformDOMRect } from '../common/TransformDOMRect';
|
||||
import { Vector } from '../common/Vector';
|
||||
|
||||
// Helper for comparing points with floating point values
|
||||
const expectPointClose = (actual: { x: number; y: number }, expected: { x: number; y: number }) => {
|
||||
expect(actual.x).toBeCloseTo(expected.x);
|
||||
expect(actual.y).toBeCloseTo(expected.y);
|
||||
};
|
||||
|
||||
describe('RotatedDOMRect', () => {
|
||||
describe('constructor', () => {
|
||||
test('initializes with default values', () => {
|
||||
const rect = new TransformDOMRect();
|
||||
expect(rect.x).toBe(0);
|
||||
expect(rect.y).toBe(0);
|
||||
expect(rect.width).toBe(0);
|
||||
expect(rect.height).toBe(0);
|
||||
expect(rect.rotation).toBe(0);
|
||||
});
|
||||
|
||||
test('initializes with custom values', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
x: 10,
|
||||
y: 20,
|
||||
width: 100,
|
||||
height: 50,
|
||||
rotation: Math.PI / 4,
|
||||
});
|
||||
expect(rect.x).toBe(10);
|
||||
expect(rect.y).toBe(20);
|
||||
expect(rect.width).toBe(100);
|
||||
expect(rect.height).toBe(50);
|
||||
expect(rect.rotation).toBe(Math.PI / 4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('corner calculations', () => {
|
||||
test('calculates corners for unrotated rectangle', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 50,
|
||||
rotation: 0,
|
||||
});
|
||||
|
||||
expectPointClose(rect.topLeft, { x: -50, y: -25 });
|
||||
expectPointClose(rect.topRight, { x: 50, y: -25 });
|
||||
expectPointClose(rect.bottomLeft, { x: -50, y: 25 });
|
||||
expectPointClose(rect.bottomRight, { x: 50, y: 25 });
|
||||
});
|
||||
|
||||
test('calculates corners for 90-degree rotated rectangle', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
rotation: Math.PI / 2,
|
||||
});
|
||||
|
||||
expectPointClose(rect.topLeft, { x: 50, y: -50 });
|
||||
expectPointClose(rect.topRight, { x: 50, y: 50 });
|
||||
expectPointClose(rect.bottomLeft, { x: -50, y: -50 });
|
||||
expectPointClose(rect.bottomRight, { x: -50, y: 50 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('bounds', () => {
|
||||
test('calculates bounds for unrotated rectangle', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 50,
|
||||
rotation: 0,
|
||||
});
|
||||
|
||||
expect(rect.getBounds()).toEqual({
|
||||
x: -50,
|
||||
y: -25,
|
||||
width: 100,
|
||||
height: 50,
|
||||
});
|
||||
});
|
||||
|
||||
test('calculates bounds for 45-degree rotated rectangle', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 50,
|
||||
rotation: Math.PI / 4,
|
||||
});
|
||||
|
||||
const bounds = rect.getBounds();
|
||||
const cos45 = Math.cos(Math.PI / 4);
|
||||
const sin45 = Math.sin(Math.PI / 4);
|
||||
const expectedWidth = Math.abs(100 * cos45) + Math.abs(50 * sin45);
|
||||
const expectedHeight = Math.abs(100 * sin45) + Math.abs(50 * cos45);
|
||||
|
||||
expect(bounds.width).toBeCloseTo(expectedWidth);
|
||||
expect(bounds.height).toBeCloseTo(expectedHeight);
|
||||
expect(bounds.x).toBeCloseTo(-expectedWidth / 2);
|
||||
expect(bounds.y).toBeCloseTo(-expectedHeight / 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setters', () => {
|
||||
test('updates corners when center is modified', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
width: 100,
|
||||
height: 50,
|
||||
});
|
||||
|
||||
rect.center = { x: 100, y: 100 };
|
||||
expectPointClose(rect.topLeft, { x: 50, y: 75 });
|
||||
expectPointClose(rect.bottomRight, { x: 150, y: 125 });
|
||||
});
|
||||
|
||||
test('updates dimensions and rotation when setting topRight', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 50,
|
||||
});
|
||||
|
||||
expect(rect.width).toBe(100);
|
||||
expect(rect.height).toBe(50);
|
||||
expect(rect.rotation).toBe(0);
|
||||
expectPointClose(rect.bottomLeft, { x: -50, y: 25 });
|
||||
expectPointClose(rect.bottomRight, { x: 50, y: 25 });
|
||||
expectPointClose(rect.center, { x: 0, y: 0 });
|
||||
expectPointClose(rect.topLeft, { x: -50, y: -25 });
|
||||
|
||||
expectPointClose(rect.topRight, { x: 50, y: -25 });
|
||||
rect.topRight = { x: 100, y: -50 };
|
||||
expect(rect.width).toBe(150);
|
||||
});
|
||||
});
|
||||
|
||||
describe('corner setters', () => {
|
||||
test('updates dimensions when setting bottomLeft', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 50,
|
||||
});
|
||||
|
||||
// Store original topRight position as it should remain fixed
|
||||
const originalTopRight = { ...rect.topRight }; // (50, -25)
|
||||
|
||||
// Set new bottomLeft position
|
||||
rect.bottomLeft = { x: -100, y: 100 };
|
||||
|
||||
// Verify topRight hasn't moved
|
||||
expectPointClose(rect.topRight, originalTopRight);
|
||||
|
||||
// Verify bottomLeft is at new position
|
||||
expectPointClose(rect.bottomLeft, { x: -100, y: 100 });
|
||||
|
||||
// Verify center is halfway between bottomLeft and topRight
|
||||
expectPointClose(rect.center, {
|
||||
x: (-100 + 50) / 2, // -25
|
||||
y: (100 + -25) / 2, // 37.5
|
||||
});
|
||||
|
||||
// Verify new dimensions
|
||||
expect(rect.width).toBeCloseTo(150); // abs(-100 - 50) = 150
|
||||
expect(rect.height).toBeCloseTo(125); // abs(100 - -25) = 125
|
||||
});
|
||||
|
||||
test('maintains rectangle properties when setting corners', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 50,
|
||||
rotation: Math.PI / 6,
|
||||
});
|
||||
|
||||
rect.bottomRight = { x: 75, y: 75 };
|
||||
|
||||
// After setting bottomRight, topLeft and bottomRight should be equidistant from center
|
||||
const distanceToTopLeft = Vector.distance(rect.center, rect.topLeft);
|
||||
const distanceToBottomRight = Vector.distance(rect.center, rect.bottomRight);
|
||||
expect(distanceToTopLeft).toBeCloseTo(distanceToBottomRight);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
test('handles zero dimensions', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
x: 10,
|
||||
y: 10,
|
||||
width: 0,
|
||||
height: 0,
|
||||
rotation: Math.PI / 4,
|
||||
});
|
||||
|
||||
expect(rect.getBounds()).toEqual({
|
||||
x: 10,
|
||||
y: 10,
|
||||
width: 0,
|
||||
height: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test('handles 360-degree rotation', () => {
|
||||
const rect = new TransformDOMRect({
|
||||
width: 100,
|
||||
height: 50,
|
||||
rotation: Math.PI * 2,
|
||||
});
|
||||
|
||||
// Should be equivalent to rotation: 0
|
||||
expectPointClose(rect.topLeft, { x: -50, y: -25 });
|
||||
expectPointClose(rect.topRight, { x: 50, y: -25 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -202,6 +202,27 @@ export class TransformDOMRect implements DOMRect {
|
|||
this._x = point.x;
|
||||
this.#updateMatrices();
|
||||
}
|
||||
|
||||
getBounds(): DOMRectInit {
|
||||
// Transform all vertices to parent space
|
||||
const transformedVertices = this.vertices().map((v) => this.toParentSpace(v));
|
||||
|
||||
// Find min and max points
|
||||
const xs = transformedVertices.map((v) => v.x);
|
||||
const ys = transformedVertices.map((v) => v.y);
|
||||
|
||||
const minX = Math.min(...xs);
|
||||
const maxX = Math.max(...xs);
|
||||
const minY = Math.min(...ys);
|
||||
const maxY = Math.max(...ys);
|
||||
|
||||
return {
|
||||
x: minX,
|
||||
y: minY,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Read-only version of TransformDOMRect
|
||||
|
|
@ -210,7 +231,45 @@ export class TransformDOMRectReadonly extends TransformDOMRect {
|
|||
super(init);
|
||||
}
|
||||
|
||||
// Override setters to prevent modification
|
||||
// Explicit getter overrides
|
||||
get x(): number {
|
||||
return super.x;
|
||||
}
|
||||
|
||||
get y(): number {
|
||||
return super.y;
|
||||
}
|
||||
|
||||
get width(): number {
|
||||
return super.width;
|
||||
}
|
||||
|
||||
get height(): number {
|
||||
return super.height;
|
||||
}
|
||||
|
||||
get rotation(): number {
|
||||
return super.rotation;
|
||||
}
|
||||
|
||||
// DOMRect property getters
|
||||
get left(): number {
|
||||
return super.left;
|
||||
}
|
||||
|
||||
get top(): number {
|
||||
return super.top;
|
||||
}
|
||||
|
||||
get right(): number {
|
||||
return super.right;
|
||||
}
|
||||
|
||||
get bottom(): number {
|
||||
return super.bottom;
|
||||
}
|
||||
|
||||
// Override all setters to prevent modification
|
||||
set x(value: number) {
|
||||
throw new Error('Cannot modify readonly TransformDOMRect');
|
||||
}
|
||||
|
|
@ -230,4 +289,21 @@ export class TransformDOMRectReadonly extends TransformDOMRect {
|
|||
set rotation(value: number) {
|
||||
throw new Error('Cannot modify readonly TransformDOMRect');
|
||||
}
|
||||
|
||||
// Override vertex setter methods
|
||||
setTopLeft(point: Point): void {
|
||||
throw new Error('Cannot modify readonly TransformDOMRect');
|
||||
}
|
||||
|
||||
setTopRight(point: Point): void {
|
||||
throw new Error('Cannot modify readonly TransformDOMRect');
|
||||
}
|
||||
|
||||
setBottomRight(point: Point): void {
|
||||
throw new Error('Cannot modify readonly TransformDOMRect');
|
||||
}
|
||||
|
||||
setBottomLeft(point: Point): void {
|
||||
throw new Error('Cannot modify readonly TransformDOMRect');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue