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._x = point.x;
|
||||||
this.#updateMatrices();
|
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
|
// Read-only version of TransformDOMRect
|
||||||
|
|
@ -210,7 +231,45 @@ export class TransformDOMRectReadonly extends TransformDOMRect {
|
||||||
super(init);
|
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) {
|
set x(value: number) {
|
||||||
throw new Error('Cannot modify readonly TransformDOMRect');
|
throw new Error('Cannot modify readonly TransformDOMRect');
|
||||||
}
|
}
|
||||||
|
|
@ -230,4 +289,21 @@ export class TransformDOMRectReadonly extends TransformDOMRect {
|
||||||
set rotation(value: number) {
|
set rotation(value: number) {
|
||||||
throw new Error('Cannot modify readonly TransformDOMRect');
|
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