add transform rect tests

This commit is contained in:
Orion Reed 2024-12-07 01:06:41 -05:00
parent 8aae9bf462
commit 3ed7354296
3 changed files with 224 additions and 225 deletions

View File

@ -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);
});
});

View File

@ -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 });
});
});
});

View File

@ -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');
}
}