From 41cb5f2dfff376c66039ddc60b12568b3ebf80a9 Mon Sep 17 00:00:00 2001 From: domakas Date: Fri, 12 Apr 2024 13:00:46 +0300 Subject: [PATCH 1/2] fix: [#1161] Implement DOMReact and DOMReactReadOnly interfaces --- packages/happy-dom/src/index.ts | 2 + .../happy-dom/src/nodes/element/DOMRect.ts | 62 +++++---- .../src/nodes/element/DOMRectReadOnly.ts | 84 ++++++++++++ .../happy-dom/src/window/BrowserWindow.ts | 2 + .../test/nodes/element/DOMRect.test.ts | 123 ++++++++++++++++++ .../nodes/element/DOMRectReadOnly.test.ts | 91 +++++++++++++ 6 files changed, 341 insertions(+), 23 deletions(-) create mode 100644 packages/happy-dom/src/nodes/element/DOMRectReadOnly.ts create mode 100644 packages/happy-dom/test/nodes/element/DOMRect.test.ts create mode 100644 packages/happy-dom/test/nodes/element/DOMRectReadOnly.test.ts diff --git a/packages/happy-dom/src/index.ts b/packages/happy-dom/src/index.ts index 74d7c43e7..8f984ea3b 100644 --- a/packages/happy-dom/src/index.ts +++ b/packages/happy-dom/src/index.ts @@ -70,6 +70,7 @@ import DocumentFragment from './nodes/document-fragment/DocumentFragment.js'; import DocumentType from './nodes/document-type/DocumentType.js'; import Document from './nodes/document/Document.js'; import DOMRect from './nodes/element/DOMRect.js'; +import DOMRectReadOnly from './nodes/element/DOMRectReadOnly.js'; import Element from './nodes/element/Element.js'; import HTMLCollection from './nodes/element/HTMLCollection.js'; import HTMLAnchorElement from './nodes/html-anchor-element/HTMLAnchorElement.js'; @@ -211,6 +212,7 @@ export { DOMException, DOMParser, DOMRect, + DOMRectReadOnly, DataTransfer, DataTransferItem, DataTransferItemList, diff --git a/packages/happy-dom/src/nodes/element/DOMRect.ts b/packages/happy-dom/src/nodes/element/DOMRect.ts index 0305962a0..27befc350 100644 --- a/packages/happy-dom/src/nodes/element/DOMRect.ts +++ b/packages/happy-dom/src/nodes/element/DOMRect.ts @@ -1,30 +1,46 @@ +import DOMRectReadOnly, { IDOMRectInit } from './DOMRectReadOnly.js'; + +/* eslint-disable jsdoc/require-jsdoc */ + /** * Bounding rect object. * * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMRect */ -export default class DOMRect { - public x = 0; - public y = 0; - public width = 0; - public height = 0; - public top = 0; - public right = 0; - public bottom = 0; - public left = 0; - - /** - * Constructor. - * - * @param [x] X position. - * @param [y] Y position. - * @param [width] Width. - * @param [height] Height. - */ - constructor(x?, y?, width?, height?) { - this.x = x || 0; - this.y = y || 0; - this.width = width || 0; - this.height = height || 0; +export default class DOMRect extends DOMRectReadOnly { + public set x(value: number) { + this._x = value; + } + + public get x(): number { + return this._x; + } + + public set y(value: number) { + this._y = value; + } + + public get y(): number { + return this._y; + } + + public set width(value: number) { + this._width = value; + } + + public get width(): number { + return this._width; + } + + public set height(value: number) { + this._height = value; + } + + public get height(): number { + return this._height; + } + + public static fromRect(other: IDOMRectInit): DOMRect { + return new DOMRect(other.x, other.y, other.width, other.height); } } diff --git a/packages/happy-dom/src/nodes/element/DOMRectReadOnly.ts b/packages/happy-dom/src/nodes/element/DOMRectReadOnly.ts new file mode 100644 index 000000000..5336e222b --- /dev/null +++ b/packages/happy-dom/src/nodes/element/DOMRectReadOnly.ts @@ -0,0 +1,84 @@ +/* eslint-disable jsdoc/require-jsdoc */ + +/** + * Bounding rect readonly object. + * + * @see https://drafts.fxtf.org/geometry/#DOMRect + */ +export default class DOMRectReadOnly implements IDOMRectInit { + protected _x: number = 0; + protected _y: number = 0; + protected _width: number = 0; + protected _height: number = 0; + + /** + * Constructor. + * + * @param [x] X position. + * @param [y] Y position. + * @param [width] Width. + * @param [height] Height. + */ + constructor(x?: number, y?: number, width?: number, height?: number) { + this._x = x || 0; + this._y = y || 0; + this._width = width || 0; + this._height = height || 0; + } + + public get x(): number { + return this._x; + } + + public get y(): number { + return this._y; + } + + public get width(): number { + return this._width; + } + + public get height(): number { + return this._height; + } + + public get top(): number { + return Math.min(this._y, this._y + this._height); + } + + public get right(): number { + return Math.max(this._x, this._x + this._width); + } + + public get bottom(): number { + return Math.max(this._y, this._y + this._height); + } + + public get left(): number { + return Math.min(this._x, this._x + this._width); + } + + public toJSON(): object { + return { + x: this.x, + y: this.y, + width: this.width, + height: this.height, + top: this.top, + right: this.right, + bottom: this.bottom, + left: this.left + }; + } + + public static fromRect(other: IDOMRectInit): DOMRectReadOnly { + return new DOMRectReadOnly(other.x, other.y, other.width, other.height); + } +} + +export interface IDOMRectInit { + readonly x: number; + readonly y: number; + readonly width: number; + readonly height: number; +} diff --git a/packages/happy-dom/src/window/BrowserWindow.ts b/packages/happy-dom/src/window/BrowserWindow.ts index b3573f391..70a6e827e 100644 --- a/packages/happy-dom/src/window/BrowserWindow.ts +++ b/packages/happy-dom/src/window/BrowserWindow.ts @@ -97,6 +97,7 @@ import Plugin from '../navigator/Plugin.js'; import PluginArray from '../navigator/PluginArray.js'; import Fetch from '../fetch/Fetch.js'; import DOMRect from '../nodes/element/DOMRect.js'; +import DOMRectReadOnly from '../nodes/element/DOMRectReadOnly.js'; import VMGlobalPropertyScript from './VMGlobalPropertyScript.js'; import VM from 'vm'; import { Buffer } from 'buffer'; @@ -372,6 +373,7 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal public readonly PluginArray = PluginArray; public readonly FileList = FileList; public readonly DOMRect = DOMRect; + public readonly DOMRectReadOnly = DOMRectReadOnly; public readonly RadioNodeList = RadioNodeList; public readonly ValidityState = ValidityState; public readonly Headers = Headers; diff --git a/packages/happy-dom/test/nodes/element/DOMRect.test.ts b/packages/happy-dom/test/nodes/element/DOMRect.test.ts new file mode 100644 index 000000000..52ea3aea5 --- /dev/null +++ b/packages/happy-dom/test/nodes/element/DOMRect.test.ts @@ -0,0 +1,123 @@ +import { afterEach, describe, it, expect, vi } from 'vitest'; +import DOMRect from '../../../src/nodes/element/DOMRect'; + +describe('DOMRect', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('set x()', () => { + it('Sets rect x property.', () => { + const rect = new DOMRect(1, 2, 3, 4); + rect.x = 2; + expect(rect.x).toBe(2); + }); + }); + + describe('get x()', () => { + it('Returns rect x property.', () => { + const rect = new DOMRect(1, 2, 3, 4); + expect(rect.x).toBe(1); + }); + }); + + describe('set y()', () => { + it('Sets rect y property.', () => { + const rect = new DOMRect(1, 2, 3, 4); + rect.y = 3; + expect(rect.y).toBe(3); + }); + }); + + describe('get y()', () => { + it('Returns rect y property.', () => { + const rect = new DOMRect(1, 2, 3, 4); + expect(rect.y).toBe(2); + }); + }); + + describe('set width()', () => { + it('Sets rect y property.', () => { + const rect = new DOMRect(1, 2, 3, 4); + rect.width = 4; + expect(rect.width).toBe(4); + }); + }); + + describe('get width()', () => { + it('Returns rect y property.', () => { + const rect = new DOMRect(1, 2, 3, 4); + expect(rect.width).toBe(3); + }); + }); + + describe('set height()', () => { + it('Sets rect height property.', () => { + const rect = new DOMRect(1, 2, 3, 4); + rect.height = 5; + expect(rect.height).toBe(5); + }); + }); + + describe('get height()', () => { + it('Returns rect height property.', () => { + const rect = new DOMRect(1, 2, 3, 4); + expect(rect.height).toBe(4); + }); + }); + + describe('get top()', () => { + it('Returns rect top property.', () => { + const rect = new DOMRect(1, 2, 3, 4); + expect(rect.top).toBe(2); + }); + }); + + describe('get right()', () => { + it('Returns rect right property.', () => { + const rect = new DOMRect(1, 2, 3, 4); + expect(rect.right).toBe(4); + }); + }); + + describe('get bottom()', () => { + it('Returns rect bottom property.', () => { + const rect = new DOMRect(1, 2, 3, 4); + expect(rect.bottom).toBe(6); + }); + }); + + describe('get left()', () => { + it('Returns rect left property.', () => { + const rect = new DOMRect(1, 2, 3, 4); + expect(rect.left).toBe(1); + }); + }); + + describe('fromRect()', () => { + it('Creates DOMRect instance', () => { + const rect = DOMRect.fromRect({ x: 1, y: 2, width: 3, height: 4 }); + expect(rect instanceof DOMRect).toBe(true); + expect(rect.x).toBe(1); + expect(rect.y).toBe(2); + expect(rect.width).toBe(3); + expect(rect.height).toBe(4); + }); + }); + + describe('toJSON()', () => { + it('Returns rect as JSON.', () => { + const rect = new DOMRect(1, 2, 3, 4); + expect(rect.toJSON()).toEqual({ + x: 1, + y: 2, + width: 3, + height: 4, + top: 2, + right: 4, + bottom: 6, + left: 1 + }); + }); + }); +}); diff --git a/packages/happy-dom/test/nodes/element/DOMRectReadOnly.test.ts b/packages/happy-dom/test/nodes/element/DOMRectReadOnly.test.ts new file mode 100644 index 000000000..35ae41be7 --- /dev/null +++ b/packages/happy-dom/test/nodes/element/DOMRectReadOnly.test.ts @@ -0,0 +1,91 @@ +import { afterEach, describe, it, expect, vi } from 'vitest'; +import DOMRectReadOnly from '../../../src/nodes/element/DOMRectReadOnly'; + +describe('DOMRectReadOnly', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('get x()', () => { + it('Returns rect x property.', () => { + const rect = new DOMRectReadOnly(1, 2, 3, 4); + expect(rect.x).toBe(1); + }); + }); + + describe('get y()', () => { + it('Returns rect y property.', () => { + const rect = new DOMRectReadOnly(1, 2, 3, 4); + expect(rect.y).toBe(2); + }); + }); + + describe('get width()', () => { + it('Returns rect y property.', () => { + const rect = new DOMRectReadOnly(1, 2, 3, 4); + expect(rect.width).toBe(3); + }); + }); + + describe('get height()', () => { + it('Returns rect height property.', () => { + const rect = new DOMRectReadOnly(1, 2, 3, 4); + expect(rect.height).toBe(4); + }); + }); + + describe('get top()', () => { + it('Returns rect top property.', () => { + const rect = new DOMRectReadOnly(1, 2, 3, 4); + expect(rect.top).toBe(2); + }); + }); + + describe('get right()', () => { + it('Returns rect right property.', () => { + const rect = new DOMRectReadOnly(1, 2, 3, 4); + expect(rect.right).toBe(4); + }); + }); + + describe('get bottom()', () => { + it('Returns rect bottom property.', () => { + const rect = new DOMRectReadOnly(1, 2, 3, 4); + expect(rect.bottom).toBe(6); + }); + }); + + describe('get left()', () => { + it('Returns rect left property.', () => { + const rect = new DOMRectReadOnly(1, 2, 3, 4); + expect(rect.left).toBe(1); + }); + }); + + describe('fromRect()', () => { + it('Creates DOMRectReadOnly instance', () => { + const rect = DOMRectReadOnly.fromRect({ x: 1, y: 2, width: 3, height: 4 }); + expect(rect instanceof DOMRectReadOnly).toBe(true); + expect(rect.x).toBe(1); + expect(rect.y).toBe(2); + expect(rect.width).toBe(3); + expect(rect.height).toBe(4); + }); + }); + + describe('toJSON()', () => { + it('Returns rect as JSON.', () => { + const rect = new DOMRectReadOnly(1, 2, 3, 4); + expect(rect.toJSON()).toEqual({ + x: 1, + y: 2, + width: 3, + height: 4, + top: 2, + right: 4, + bottom: 6, + left: 1 + }); + }); + }); +}); From c30265628d2b0bbe89230bd9fa147bd0b2effcf2 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Mon, 6 May 2024 23:43:16 +0200 Subject: [PATCH 2/2] chore: [#1161] Fixes review findings --- .../happy-dom/src/nodes/element/DOMRect.ts | 17 ++++----- .../src/nodes/element/DOMRectReadOnly.ts | 36 ++++++++++--------- .../test/nodes/element/DOMRect.test.ts | 33 +++++++++++++++++ .../nodes/element/DOMRectReadOnly.test.ts | 33 +++++++++++++++++ 4 files changed, 94 insertions(+), 25 deletions(-) diff --git a/packages/happy-dom/src/nodes/element/DOMRect.ts b/packages/happy-dom/src/nodes/element/DOMRect.ts index 27befc350..86d433a6c 100644 --- a/packages/happy-dom/src/nodes/element/DOMRect.ts +++ b/packages/happy-dom/src/nodes/element/DOMRect.ts @@ -1,4 +1,5 @@ import DOMRectReadOnly, { IDOMRectInit } from './DOMRectReadOnly.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; /* eslint-disable jsdoc/require-jsdoc */ @@ -9,35 +10,35 @@ import DOMRectReadOnly, { IDOMRectInit } from './DOMRectReadOnly.js'; */ export default class DOMRect extends DOMRectReadOnly { public set x(value: number) { - this._x = value; + this[PropertySymbol.x] = value; } public get x(): number { - return this._x; + return this[PropertySymbol.x]; } public set y(value: number) { - this._y = value; + this[PropertySymbol.y] = value; } public get y(): number { - return this._y; + return this[PropertySymbol.y]; } public set width(value: number) { - this._width = value; + this[PropertySymbol.width] = value; } public get width(): number { - return this._width; + return this[PropertySymbol.width]; } public set height(value: number) { - this._height = value; + this[PropertySymbol.height] = value; } public get height(): number { - return this._height; + return this[PropertySymbol.height]; } public static fromRect(other: IDOMRectInit): DOMRect { diff --git a/packages/happy-dom/src/nodes/element/DOMRectReadOnly.ts b/packages/happy-dom/src/nodes/element/DOMRectReadOnly.ts index 5336e222b..65c8bc334 100644 --- a/packages/happy-dom/src/nodes/element/DOMRectReadOnly.ts +++ b/packages/happy-dom/src/nodes/element/DOMRectReadOnly.ts @@ -1,3 +1,5 @@ +import * as PropertySymbol from '../../PropertySymbol.js'; + /* eslint-disable jsdoc/require-jsdoc */ /** @@ -6,10 +8,10 @@ * @see https://drafts.fxtf.org/geometry/#DOMRect */ export default class DOMRectReadOnly implements IDOMRectInit { - protected _x: number = 0; - protected _y: number = 0; - protected _width: number = 0; - protected _height: number = 0; + protected [PropertySymbol.x]: number = 0; + protected [PropertySymbol.y]: number = 0; + protected [PropertySymbol.width]: number = 0; + protected [PropertySymbol.height]: number = 0; /** * Constructor. @@ -19,43 +21,43 @@ export default class DOMRectReadOnly implements IDOMRectInit { * @param [width] Width. * @param [height] Height. */ - constructor(x?: number, y?: number, width?: number, height?: number) { - this._x = x || 0; - this._y = y || 0; - this._width = width || 0; - this._height = height || 0; + constructor(x?: number | null, y?: number | null, width?: number | null, height?: number | null) { + this[PropertySymbol.x] = x !== undefined && x !== null ? Number(x) : 0; + this[PropertySymbol.y] = y !== undefined && y !== null ? Number(y) : 0; + this[PropertySymbol.width] = width !== undefined && width !== null ? Number(width) : 0; + this[PropertySymbol.height] = height !== undefined && height !== null ? Number(height) : 0; } public get x(): number { - return this._x; + return this[PropertySymbol.x]; } public get y(): number { - return this._y; + return this[PropertySymbol.y]; } public get width(): number { - return this._width; + return this[PropertySymbol.width]; } public get height(): number { - return this._height; + return this[PropertySymbol.height]; } public get top(): number { - return Math.min(this._y, this._y + this._height); + return Math.min(this[PropertySymbol.y], this[PropertySymbol.y] + this[PropertySymbol.height]); } public get right(): number { - return Math.max(this._x, this._x + this._width); + return Math.max(this[PropertySymbol.x], this[PropertySymbol.x] + this[PropertySymbol.width]); } public get bottom(): number { - return Math.max(this._y, this._y + this._height); + return Math.max(this[PropertySymbol.y], this[PropertySymbol.y] + this[PropertySymbol.height]); } public get left(): number { - return Math.min(this._x, this._x + this._width); + return Math.min(this[PropertySymbol.x], this[PropertySymbol.x] + this[PropertySymbol.width]); } public toJSON(): object { diff --git a/packages/happy-dom/test/nodes/element/DOMRect.test.ts b/packages/happy-dom/test/nodes/element/DOMRect.test.ts index 52ea3aea5..b8a0b46c5 100644 --- a/packages/happy-dom/test/nodes/element/DOMRect.test.ts +++ b/packages/happy-dom/test/nodes/element/DOMRect.test.ts @@ -6,6 +6,39 @@ describe('DOMRect', () => { vi.restoreAllMocks(); }); + describe('constructor()', () => { + it('Sets properties.', () => { + const rect = new DOMRect(1, 2, 3, 4); + expect(rect.x).toBe(1); + expect(rect.y).toBe(2); + expect(rect.width).toBe(3); + expect(rect.height).toBe(4); + + const rect2 = new DOMRect(null, null, null, 4); + expect(rect2.x).toBe(0); + expect(rect2.y).toBe(0); + expect(rect2.width).toBe(0); + expect(rect2.height).toBe(4); + + const rect3 = new DOMRect(); + expect(rect3.x).toBe(0); + expect(rect3.y).toBe(0); + expect(rect3.width).toBe(0); + expect(rect3.height).toBe(0); + + const rect4 = new DOMRect( + ('nan'), + ('nan'), + ('nan'), + ('nan') + ); + expect(isNaN(rect4.x)).toBe(true); + expect(isNaN(rect4.y)).toBe(true); + expect(isNaN(rect4.width)).toBe(true); + expect(isNaN(rect4.height)).toBe(true); + }); + }); + describe('set x()', () => { it('Sets rect x property.', () => { const rect = new DOMRect(1, 2, 3, 4); diff --git a/packages/happy-dom/test/nodes/element/DOMRectReadOnly.test.ts b/packages/happy-dom/test/nodes/element/DOMRectReadOnly.test.ts index 35ae41be7..c179eedaf 100644 --- a/packages/happy-dom/test/nodes/element/DOMRectReadOnly.test.ts +++ b/packages/happy-dom/test/nodes/element/DOMRectReadOnly.test.ts @@ -6,6 +6,39 @@ describe('DOMRectReadOnly', () => { vi.restoreAllMocks(); }); + describe('constructor()', () => { + it('Sets properties.', () => { + const rect = new DOMRectReadOnly(1, 2, 3, 4); + expect(rect.x).toBe(1); + expect(rect.y).toBe(2); + expect(rect.width).toBe(3); + expect(rect.height).toBe(4); + + const rect2 = new DOMRectReadOnly(null, null, null, 4); + expect(rect2.x).toBe(0); + expect(rect2.y).toBe(0); + expect(rect2.width).toBe(0); + expect(rect2.height).toBe(4); + + const rect3 = new DOMRectReadOnly(); + expect(rect3.x).toBe(0); + expect(rect3.y).toBe(0); + expect(rect3.width).toBe(0); + expect(rect3.height).toBe(0); + + const rect4 = new DOMRectReadOnly( + ('nan'), + ('nan'), + ('nan'), + ('nan') + ); + expect(isNaN(rect4.x)).toBe(true); + expect(isNaN(rect4.y)).toBe(true); + expect(isNaN(rect4.width)).toBe(true); + expect(isNaN(rect4.height)).toBe(true); + }); + }); + describe('get x()', () => { it('Returns rect x property.', () => { const rect = new DOMRectReadOnly(1, 2, 3, 4);