diff --git a/packages/happy-dom/src/browser/Browser.ts b/packages/happy-dom/src/browser/Browser.ts index 0df1f0407..39bafbb9b 100644 --- a/packages/happy-dom/src/browser/Browser.ts +++ b/packages/happy-dom/src/browser/Browser.ts @@ -24,7 +24,7 @@ export default class Browser implements IBrowser { */ constructor(options?: { settings?: IOptionalBrowserSettings; console?: Console }) { this.console = options?.console || null; - this.settings = BrowserSettingsFactory.getSettings(options?.settings); + this.settings = BrowserSettingsFactory.createSettings(options?.settings); this.contexts = [new BrowserContext(this)]; } diff --git a/packages/happy-dom/src/browser/BrowserSettingsFactory.ts b/packages/happy-dom/src/browser/BrowserSettingsFactory.ts index 7ed799ab2..ce6a1b516 100644 --- a/packages/happy-dom/src/browser/BrowserSettingsFactory.ts +++ b/packages/happy-dom/src/browser/BrowserSettingsFactory.ts @@ -13,7 +13,7 @@ export default class BrowserSettingsFactory { * @param [freezeObject] "true" to freeze the object. * @returns Settings. */ - public static getSettings(settings?: IOptionalBrowserSettings): IBrowserSettings { + public static createSettings(settings?: IOptionalBrowserSettings): IBrowserSettings { return { ...DefaultBrowserSettings, ...settings, diff --git a/packages/happy-dom/src/browser/detached-browser/DetachedBrowser.ts b/packages/happy-dom/src/browser/detached-browser/DetachedBrowser.ts index ce28ebced..faf744b80 100644 --- a/packages/happy-dom/src/browser/detached-browser/DetachedBrowser.ts +++ b/packages/happy-dom/src/browser/detached-browser/DetachedBrowser.ts @@ -38,7 +38,7 @@ export default class DetachedBrowser implements IBrowser { ) { this.windowClass = windowClass; this.console = options?.console || null; - this.settings = BrowserSettingsFactory.getSettings(options?.settings); + this.settings = BrowserSettingsFactory.createSettings(options?.settings); this.contexts = []; this.contexts.push(new DetachedBrowserContext(this)); } diff --git a/packages/happy-dom/src/browser/utilities/BrowserFrameFactory.ts b/packages/happy-dom/src/browser/utilities/BrowserFrameFactory.ts index b273febab..2fb4bd18e 100644 --- a/packages/happy-dom/src/browser/utilities/BrowserFrameFactory.ts +++ b/packages/happy-dom/src/browser/utilities/BrowserFrameFactory.ts @@ -12,7 +12,7 @@ export default class BrowserFrameFactory { * @param parentFrame Parent frame. * @returns Frame. */ - public static newChildFrame(parentFrame: IBrowserFrame): IBrowserFrame { + public static createChildFrame(parentFrame: IBrowserFrame): IBrowserFrame { const frame = new ( IBrowserFrame>parentFrame.constructor)( parentFrame.page ); diff --git a/packages/happy-dom/src/nodes/element/Dataset.ts b/packages/happy-dom/src/nodes/element/DatasetFactory.ts similarity index 57% rename from packages/happy-dom/src/nodes/element/Dataset.ts rename to packages/happy-dom/src/nodes/element/DatasetFactory.ts index e842421e3..74939fab2 100644 --- a/packages/happy-dom/src/nodes/element/Dataset.ts +++ b/packages/happy-dom/src/nodes/element/DatasetFactory.ts @@ -1,42 +1,39 @@ import Element from './Element.js'; import * as PropertySymbol from '../../PropertySymbol.js'; import HTMLElementNamedNodeMap from '../html-element/HTMLElementNamedNodeMap.js'; +import DatasetUtility from './DatasetUtility.js'; +import IDataset from './IDataset.js'; /** - * Storage type for a dataset proxy. - */ -type DatasetRecord = Record; - -/** - * Dataset helper proxy. + * Dataset factory. * * Reference: * https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset */ -export default class Dataset { - public readonly proxy: DatasetRecord; - +export default class DatasetFactory { /** * @param element The parent element. */ - constructor(element: Element) { + public static createDataset(element: Element): IDataset { // Build the initial dataset record from all data attributes. - const dataset: DatasetRecord = {}; + const dataset: IDataset = {}; for (let i = 0, max = element[PropertySymbol.attributes].length; i < max; i++) { const attribute = element[PropertySymbol.attributes][i]; if (attribute[PropertySymbol.name].startsWith('data-')) { - const key = Dataset.kebabToCamelCase(attribute[PropertySymbol.name].replace('data-', '')); + const key = DatasetUtility.kebabToCamelCase( + attribute[PropertySymbol.name].replace('data-', '') + ); dataset[key] = attribute[PropertySymbol.value]; } } // Documentation for Proxy: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy - this.proxy = new Proxy(dataset, { - get(dataset: DatasetRecord, key: string): string { + return new Proxy(dataset, { + get(dataset: IDataset, key: string): string { const attribute = element[PropertySymbol.attributes].getNamedItem( - 'data-' + Dataset.camelCaseToKebab(key) + 'data-' + DatasetUtility.camelCaseToKebab(key) ); if (attribute) { return (dataset[key] = attribute[PropertySymbol.value]); @@ -44,18 +41,18 @@ export default class Dataset { delete dataset[key]; return undefined; }, - set(dataset: DatasetRecord, key: string, value: string): boolean { - element.setAttribute('data-' + Dataset.camelCaseToKebab(key), value); + set(dataset: IDataset, key: string, value: string): boolean { + element.setAttribute('data-' + DatasetUtility.camelCaseToKebab(key), value); dataset[key] = value; return true; }, - deleteProperty(dataset: DatasetRecord, key: string): boolean { + deleteProperty(dataset: IDataset, key: string): boolean { (element[PropertySymbol.attributes])[ PropertySymbol.removeNamedItem - ]('data-' + Dataset.camelCaseToKebab(key)); + ]('data-' + DatasetUtility.camelCaseToKebab(key)); return delete dataset[key]; }, - ownKeys(dataset: DatasetRecord): string[] { + ownKeys(dataset: IDataset): string[] { // According to Mozilla we have to update the dataset object (target) to contain the same keys as what we return: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys // "The result List must contain the keys of all non-configurable own properties of the target object." @@ -64,7 +61,7 @@ export default class Dataset { for (let i = 0, max = element[PropertySymbol.attributes].length; i < max; i++) { const attribute = element[PropertySymbol.attributes][i]; if (attribute[PropertySymbol.name].startsWith('data-')) { - const key = Dataset.kebabToCamelCase( + const key = DatasetUtility.kebabToCamelCase( attribute[PropertySymbol.name].replace('data-', '') ); keys.push(key); @@ -79,37 +76,11 @@ export default class Dataset { } return keys; }, - has(_dataset: DatasetRecord, key: string): boolean { + has(_dataset: IDataset, key: string): boolean { return !!element[PropertySymbol.attributes].getNamedItem( - 'data-' + Dataset.camelCaseToKebab(key) + 'data-' + DatasetUtility.camelCaseToKebab(key) ); } }); } - - /** - * Transforms a kebab cased string to camel case. - * - * @param text Text string. - * @returns Camel cased string. - */ - public static kebabToCamelCase(text: string): string { - const parts = text.split('-'); - for (let i = 0, max = parts.length; i < max; i++) { - parts[i] = i > 0 ? parts[i].charAt(0).toUpperCase() + parts[i].slice(1) : parts[i]; - } - return parts.join(''); - } - - /** - * Transforms a camel cased string to kebab case. - * - * @param text Text string. - * @returns Kebab cased string. - */ - public static camelCaseToKebab(text: string): string { - return text - .toString() - .replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? '-' : '') + $.toLowerCase()); - } } diff --git a/packages/happy-dom/src/nodes/element/DatasetUtility.ts b/packages/happy-dom/src/nodes/element/DatasetUtility.ts new file mode 100644 index 000000000..6025046d7 --- /dev/null +++ b/packages/happy-dom/src/nodes/element/DatasetUtility.ts @@ -0,0 +1,33 @@ +/** + * Dataset utility. + * + * Reference: + * https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset + */ +export default class DatasetUtility { + /** + * Transforms a kebab cased string to camel case. + * + * @param text Text string. + * @returns Camel cased string. + */ + public static kebabToCamelCase(text: string): string { + const parts = text.split('-'); + for (let i = 0, max = parts.length; i < max; i++) { + parts[i] = i > 0 ? parts[i].charAt(0).toUpperCase() + parts[i].slice(1) : parts[i]; + } + return parts.join(''); + } + + /** + * Transforms a camel cased string to kebab case. + * + * @param text Text string. + * @returns Kebab cased string. + */ + public static camelCaseToKebab(text: string): string { + return text + .toString() + .replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? '-' : '') + $.toLowerCase()); + } +} diff --git a/packages/happy-dom/src/nodes/element/IDataset.ts b/packages/happy-dom/src/nodes/element/IDataset.ts new file mode 100644 index 000000000..3c92d451c --- /dev/null +++ b/packages/happy-dom/src/nodes/element/IDataset.ts @@ -0,0 +1,3 @@ +export default interface IDataset { + [key: string]: string; +} diff --git a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts index 0095083dd..0ef464a72 100644 --- a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts +++ b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts @@ -2,7 +2,6 @@ import Element from '../element/Element.js'; import * as PropertySymbol from '../../PropertySymbol.js'; import CSSStyleDeclaration from '../../css/declaration/CSSStyleDeclaration.js'; import PointerEvent from '../../event/events/PointerEvent.js'; -import Dataset from '../element/Dataset.js'; import NodeTypeEnum from '../node/NodeTypeEnum.js'; import DOMException from '../../exception/DOMException.js'; import Event from '../../event/Event.js'; @@ -12,6 +11,8 @@ import HTMLElementNamedNodeMap from './HTMLElementNamedNodeMap.js'; import NodeList from '../node/NodeList.js'; import Node from '../node/Node.js'; import HTMLCollection from '../element/HTMLCollection.js'; +import DatasetFactory from '../element/DatasetFactory.js'; +import IDataset from '../element/IDataset.js'; /** * HTML Element. @@ -63,7 +64,7 @@ export default class HTMLElement extends Element { public [PropertySymbol.style]: CSSStyleDeclaration = null; // Private properties - #dataset: Dataset = null; + #dataset: IDataset = null; #customElementDefineCallback: () => void = null; /** @@ -353,8 +354,8 @@ export default class HTMLElement extends Element { * * @returns Data set. */ - public get dataset(): { [key: string]: string } { - return (this.#dataset ??= new Dataset(this)).proxy; + public get dataset(): IDataset { + return (this.#dataset ??= DatasetFactory.createDataset(this)); } /** diff --git a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts index 4a940421c..6783a5f68 100644 --- a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts +++ b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts @@ -76,7 +76,7 @@ export default class HTMLIFrameElementPageLoader { const parentWindow = isSameOrigin ? window : new CrossOriginBrowserWindow(window); this.#browserIFrame = - this.#browserIFrame ?? BrowserFrameFactory.newChildFrame(this.#browserParentFrame); + this.#browserIFrame ?? BrowserFrameFactory.createChildFrame(this.#browserParentFrame); ((this.#browserIFrame.window.top)) = parentWindow; diff --git a/packages/happy-dom/src/nodes/svg-element/SVGElement.ts b/packages/happy-dom/src/nodes/svg-element/SVGElement.ts index 7582229a1..80146d9ad 100644 --- a/packages/happy-dom/src/nodes/svg-element/SVGElement.ts +++ b/packages/happy-dom/src/nodes/svg-element/SVGElement.ts @@ -3,10 +3,11 @@ import * as PropertySymbol from '../../PropertySymbol.js'; import Element from '../element/Element.js'; import SVGSVGElement from './SVGSVGElement.js'; import Event from '../../event/Event.js'; -import Dataset from '../element/Dataset.js'; import HTMLElementUtility from '../html-element/HTMLElementUtility.js'; import NamedNodeMap from '../../named-node-map/NamedNodeMap.js'; import SVGElementNamedNodeMap from './SVGElementNamedNodeMap.js'; +import DatasetFactory from '../element/DatasetFactory.js'; +import IDataset from '../element/IDataset.js'; /** * SVG Element. @@ -28,7 +29,7 @@ export default class SVGElement extends Element { public [PropertySymbol.style]: CSSStyleDeclaration | null = null; // Private properties - #dataset: Dataset = null; + #dataset: IDataset = null; /** * Returns viewport. @@ -61,8 +62,8 @@ export default class SVGElement extends Element { * * @returns Data set. */ - public get dataset(): { [key: string]: string } { - return (this.#dataset ??= new Dataset(this)).proxy; + public get dataset(): IDataset { + return (this.#dataset ??= DatasetFactory.createDataset(this)); } /** diff --git a/packages/happy-dom/src/storage/Storage.ts b/packages/happy-dom/src/storage/Storage.ts index 983343c87..e4d35aaac 100644 --- a/packages/happy-dom/src/storage/Storage.ts +++ b/packages/happy-dom/src/storage/Storage.ts @@ -1,16 +1,63 @@ +import * as PropertySymbol from '../PropertySymbol.js'; + /** * Storage. * * @see https://developer.mozilla.org/en-US/docs/Web/API/Storage */ export default class Storage { + public [PropertySymbol.data]: { [key: string]: string } = {}; + + /** + * + */ + constructor() { + const descriptors = Object.getOwnPropertyDescriptors(Storage.prototype); + + Object.defineProperty(this, 'length', { + enumerable: false, + configurable: true, + get: descriptors['length'].get.bind(this) + }); + + Object.defineProperty(this, 'key', { + enumerable: false, + configurable: true, + value: descriptors['key'].value.bind(this) + }); + + Object.defineProperty(this, 'setItem', { + enumerable: false, + configurable: true, + value: descriptors['setItem'].value.bind(this) + }); + + Object.defineProperty(this, 'getItem', { + enumerable: false, + configurable: true, + value: descriptors['getItem'].value.bind(this) + }); + + Object.defineProperty(this, 'removeItem', { + enumerable: false, + configurable: true, + value: descriptors['removeItem'].value.bind(this) + }); + + Object.defineProperty(this, 'clear', { + enumerable: false, + configurable: true, + value: descriptors['clear'].value.bind(this) + }); + } + /** * Returns length. * * @returns Length. */ public get length(): number { - return Object.keys(this).length; + return Object.keys(this[PropertySymbol.data]).length; } /** @@ -19,9 +66,9 @@ export default class Storage { * @param index Index. * @returns Name. */ - public key(index: number): string { - const name = Object.keys(this)[index]; - return name === undefined ? null : name; + public key(index: number): string | null { + const name = Object.keys(this[PropertySymbol.data])[index]; + return name !== undefined ? name : null; } /** @@ -31,7 +78,7 @@ export default class Storage { * @param item Item. */ public setItem(name: string, item: string): void { - this[name] = String(item); + this[PropertySymbol.data][name] = String(item); } /** @@ -40,8 +87,8 @@ export default class Storage { * @param name Name. * @returns Item. */ - public getItem(name: string): string { - return this[name] === undefined ? null : this[name]; + public getItem(name: string): string | null { + return this[PropertySymbol.data][name] !== undefined ? this[PropertySymbol.data][name] : null; } /** @@ -50,16 +97,13 @@ export default class Storage { * @param name Name. */ public removeItem(name: string): void { - delete this[name]; + delete this[PropertySymbol.data][name]; } /** * Clears storage. */ public clear(): void { - const keys = Object.keys(this); - for (const key of keys) { - delete this[key]; - } + this[PropertySymbol.data] = {}; } } diff --git a/packages/happy-dom/src/storage/StorageFactory.ts b/packages/happy-dom/src/storage/StorageFactory.ts new file mode 100644 index 000000000..cfdf53506 --- /dev/null +++ b/packages/happy-dom/src/storage/StorageFactory.ts @@ -0,0 +1,66 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import Storage from './Storage.js'; + +/** + * Dataset factory. + * + * Reference: + * https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/storage + */ +export default class StorageFactory { + /** + * Creates a new storage. + */ + public static createStorage(): Storage { + // Documentation for Proxy: + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy + return new Proxy(new Storage(), { + get(storage: Storage, key: string): string { + if (Storage.prototype.hasOwnProperty(key)) { + return storage[key]; + } + return storage[PropertySymbol.data][key]; + }, + set(storage: Storage, key: string, value: string): boolean { + if (Storage.prototype.hasOwnProperty(key)) { + return true; + } + storage[PropertySymbol.data][key] = String(value); + return true; + }, + deleteProperty(storage: Storage, key: string): boolean { + if (Storage.prototype.hasOwnProperty(key)) { + return false; + } + return delete storage[PropertySymbol.data][key]; + }, + ownKeys(storage: Storage): string[] { + return Object.keys(storage[PropertySymbol.data]); + }, + has(storage: Storage, key: string): boolean { + return storage[PropertySymbol.data][key] !== undefined; + }, + defineProperty(storage: Storage, key: string, descriptor: PropertyDescriptor): boolean { + if (Storage.prototype.hasOwnProperty(key) || descriptor.value === undefined) { + return false; + } + storage[PropertySymbol.data][key] = String(descriptor.value); + return true; + }, + getOwnPropertyDescriptor(storage: Storage, key: string): PropertyDescriptor { + if ( + Storage.prototype.hasOwnProperty(key) || + storage[PropertySymbol.data][key] === undefined + ) { + return; + } + return { + value: storage[PropertySymbol.data][key], + writable: true, + enumerable: true, + configurable: false + }; + } + }); + } +} diff --git a/packages/happy-dom/src/window/BrowserWindow.ts b/packages/happy-dom/src/window/BrowserWindow.ts index c11546cce..d9fd217e1 100644 --- a/packages/happy-dom/src/window/BrowserWindow.ts +++ b/packages/happy-dom/src/window/BrowserWindow.ts @@ -86,6 +86,7 @@ import SubmitEvent from '../event/events/SubmitEvent.js'; import Screen from '../screen/Screen.js'; import IRequestInit from '../fetch/types/IRequestInit.js'; import Storage from '../storage/Storage.js'; +import StorageFactory from '../storage/StorageFactory.js'; import HTMLCollection from '../nodes/element/HTMLCollection.js'; import HTMLFormControlsCollection from '../nodes/html-form-element/HTMLFormControlsCollection.js'; import NodeList from '../nodes/node/NodeList.js'; @@ -522,8 +523,8 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal this[PropertySymbol.navigator] = new Navigator(this); this[PropertySymbol.history] = new History(); this[PropertySymbol.screen] = new Screen(); - this[PropertySymbol.sessionStorage] = new Storage(); - this[PropertySymbol.localStorage] = new Storage(); + this[PropertySymbol.sessionStorage] = StorageFactory.createStorage(); + this[PropertySymbol.localStorage] = StorageFactory.createStorage(); this[PropertySymbol.location] = new Location(this.#browserFrame, options?.url ?? 'about:blank'); this.console = browserFrame.page.console; diff --git a/packages/happy-dom/test/browser/BrowserFrame.test.ts b/packages/happy-dom/test/browser/BrowserFrame.test.ts index 0339bcdbc..e2b1f9781 100644 --- a/packages/happy-dom/test/browser/BrowserFrame.test.ts +++ b/packages/happy-dom/test/browser/BrowserFrame.test.ts @@ -25,8 +25,8 @@ describe('BrowserFrame', () => { const browser = new Browser(); const page = browser.defaultContext.newPage(); expect(page.mainFrame.childFrames).toEqual([]); - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(page.mainFrame); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(page.mainFrame); expect(page.mainFrame.childFrames).toEqual([frame1, frame2]); }); }); @@ -36,8 +36,8 @@ describe('BrowserFrame', () => { const browser = new Browser(); const page = browser.defaultContext.newPage(); expect(page.mainFrame.parentFrame).toBe(null); - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(frame1); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(frame1); expect(frame2.parentFrame).toBe(frame1); expect(frame1.parentFrame).toBe(page.mainFrame); expect(page.mainFrame.parentFrame).toBe(null); @@ -124,8 +124,8 @@ describe('BrowserFrame', () => { it('Waits for all pages to complete.', async () => { const browser = new Browser(); const page = browser.newPage(); - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(page.mainFrame); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(page.mainFrame); page.mainFrame.evaluate('setTimeout(() => { globalThis.test = 1; }, 10);'); frame1.evaluate('setTimeout(() => { globalThis.test = 2; }, 10);'); frame2.evaluate('setTimeout(() => { globalThis.test = 3; }, 10);'); @@ -175,8 +175,8 @@ describe('BrowserFrame', () => { it('Aborts all ongoing operations.', async () => { const browser = new Browser(); const page = browser.newPage(); - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(page.mainFrame); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(page.mainFrame); page.mainFrame.evaluate('setTimeout(() => { globalThis.test = 1; }, 10);'); frame1.evaluate('setTimeout(() => { globalThis.test = 1; }, 10);'); frame2.evaluate('setTimeout(() => { globalThis.test = 2; }, 10);'); @@ -349,7 +349,7 @@ describe('BrowserFrame', () => { } }); const page = browser.newPage(); - const childFrame = BrowserFrameFactory.newChildFrame(page.mainFrame); + const childFrame = BrowserFrameFactory.createChildFrame(page.mainFrame); const oldWindow = childFrame.window; page.mainFrame.url = 'https://github.com'; @@ -377,7 +377,7 @@ describe('BrowserFrame', () => { } }); const page = browser.newPage(); - const childFrame = BrowserFrameFactory.newChildFrame(page.mainFrame); + const childFrame = BrowserFrameFactory.createChildFrame(page.mainFrame); const oldWindow = childFrame.window; page.mainFrame.url = 'https://github.com'; @@ -617,7 +617,7 @@ describe('BrowserFrame', () => { } }); const page = browser.newPage(); - const childFrame = BrowserFrameFactory.newChildFrame(page.mainFrame); + const childFrame = BrowserFrameFactory.createChildFrame(page.mainFrame); const oldWindow = childFrame.window; await childFrame.goto('http://localhost:9999'); @@ -640,7 +640,7 @@ describe('BrowserFrame', () => { } }); const page = browser.newPage(); - const childFrame = BrowserFrameFactory.newChildFrame(page.mainFrame); + const childFrame = BrowserFrameFactory.createChildFrame(page.mainFrame); const oldWindow = childFrame.window; const response = await childFrame.goto('http://localhost:9999'); diff --git a/packages/happy-dom/test/browser/BrowserPage.test.ts b/packages/happy-dom/test/browser/BrowserPage.test.ts index 13efc4014..574101cca 100644 --- a/packages/happy-dom/test/browser/BrowserPage.test.ts +++ b/packages/happy-dom/test/browser/BrowserPage.test.ts @@ -75,8 +75,8 @@ describe('BrowserPage', () => { it('Returns the frames.', () => { const browser = new Browser(); const page = browser.defaultContext.newPage(); - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(page.mainFrame); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(page.mainFrame); expect(page.frames).toEqual([page.mainFrame, frame1, frame2]); }); }); @@ -123,9 +123,9 @@ describe('BrowserPage', () => { it('Closes the page.', async () => { const browser = new Browser(); const page = browser.defaultContext.newPage(); - const mainFrame = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(page.mainFrame); + const mainFrame = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(page.mainFrame); await page.close(); @@ -144,8 +144,8 @@ describe('BrowserPage', () => { it('Waits for all pages to complete.', async () => { const browser = new Browser(); const page = browser.newPage(); - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(page.mainFrame); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(page.mainFrame); frame1.evaluate('setTimeout(() => { globalThis.test = 1; }, 10);'); frame2.evaluate('setTimeout(() => { globalThis.test = 2; }, 10);'); await page.waitUntilComplete(); @@ -175,8 +175,8 @@ describe('BrowserPage', () => { it('Aborts all ongoing operations.', async () => { const browser = new Browser(); const page = browser.newPage(); - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(page.mainFrame); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(page.mainFrame); frame1.evaluate('setTimeout(() => { globalThis.test = 1; }, 10);'); frame2.evaluate('setTimeout(() => { globalThis.test = 2; }, 10);'); page.abort(); diff --git a/packages/happy-dom/test/browser/detached-browser/DetachedBrowserFrame.test.ts b/packages/happy-dom/test/browser/detached-browser/DetachedBrowserFrame.test.ts index bc5dce229..063057a25 100644 --- a/packages/happy-dom/test/browser/detached-browser/DetachedBrowserFrame.test.ts +++ b/packages/happy-dom/test/browser/detached-browser/DetachedBrowserFrame.test.ts @@ -28,8 +28,8 @@ describe('DetachedBrowserFrame', () => { ); const page = browser.defaultContext.pages[0]; expect(page.mainFrame.childFrames).toEqual([]); - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(page.mainFrame); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(page.mainFrame); expect(page.mainFrame.childFrames).toEqual([frame1, frame2]); }); }); @@ -42,8 +42,8 @@ describe('DetachedBrowserFrame', () => { ); const page = browser.defaultContext.pages[0]; expect(page.mainFrame.parentFrame).toBe(null); - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(frame1); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(frame1); expect(frame2.parentFrame).toBe(frame1); expect(frame1.parentFrame).toBe(page.mainFrame); expect(page.mainFrame.parentFrame).toBe(null); @@ -151,8 +151,8 @@ describe('DetachedBrowserFrame', () => { browser.defaultContext.pages[0].mainFrame ); const page = browser.defaultContext.pages[0]; - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(page.mainFrame); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(page.mainFrame); page.mainFrame.evaluate('setTimeout(() => { globalThis.test = 1; }, 10);'); frame1.evaluate('setTimeout(() => { globalThis.test = 2; }, 10);'); frame2.evaluate('setTimeout(() => { globalThis.test = 3; }, 10);'); @@ -205,8 +205,8 @@ describe('DetachedBrowserFrame', () => { browser.defaultContext.pages[0].mainFrame ); const page = browser.defaultContext.newPage(); - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(page.mainFrame); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(page.mainFrame); page.mainFrame.evaluate('setTimeout(() => { globalThis.test = 1; }, 10);'); frame1.evaluate('setTimeout(() => { globalThis.test = 1; }, 10);'); frame2.evaluate('setTimeout(() => { globalThis.test = 2; }, 10);'); @@ -406,7 +406,7 @@ describe('DetachedBrowserFrame', () => { browser.defaultContext.pages[0].mainFrame ); const page = browser.defaultContext.pages[0]; - const childFrame = BrowserFrameFactory.newChildFrame(page.mainFrame); + const childFrame = BrowserFrameFactory.createChildFrame(page.mainFrame); const oldWindow = childFrame.window; page.mainFrame.url = 'https://github.com'; @@ -437,7 +437,7 @@ describe('DetachedBrowserFrame', () => { browser.defaultContext.pages[0].mainFrame ); const page = browser.defaultContext.pages[0]; - const childFrame = BrowserFrameFactory.newChildFrame(page.mainFrame); + const childFrame = BrowserFrameFactory.createChildFrame(page.mainFrame); const oldWindow = childFrame.window; page.mainFrame.url = 'https://github.com'; @@ -704,7 +704,7 @@ describe('DetachedBrowserFrame', () => { browser.defaultContext.pages[0].mainFrame ); const page = browser.defaultContext.pages[0]; - const childFrame = BrowserFrameFactory.newChildFrame(page.mainFrame); + const childFrame = BrowserFrameFactory.createChildFrame(page.mainFrame); const oldWindow = childFrame.window; await childFrame.goto('http://localhost:9999'); @@ -730,7 +730,7 @@ describe('DetachedBrowserFrame', () => { browser.defaultContext.pages[0].mainFrame ); const page = browser.defaultContext.pages[0]; - const childFrame = BrowserFrameFactory.newChildFrame(page.mainFrame); + const childFrame = BrowserFrameFactory.createChildFrame(page.mainFrame); const oldWindow = childFrame.window; const response = await childFrame.goto('http://localhost:9999'); diff --git a/packages/happy-dom/test/browser/detached-browser/DetachedBrowserPage.test.ts b/packages/happy-dom/test/browser/detached-browser/DetachedBrowserPage.test.ts index 9aef3710f..3c900363a 100644 --- a/packages/happy-dom/test/browser/detached-browser/DetachedBrowserPage.test.ts +++ b/packages/happy-dom/test/browser/detached-browser/DetachedBrowserPage.test.ts @@ -78,8 +78,8 @@ describe('DetachedBrowserPage', () => { const browser = new DetachedBrowser(BrowserWindow); browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.defaultContext.newPage(); - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(page.mainFrame); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(page.mainFrame); expect(page.frames).toEqual([page.mainFrame, frame1, frame2]); }); }); @@ -131,9 +131,9 @@ describe('DetachedBrowserPage', () => { const browser = new DetachedBrowser(BrowserWindow); browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.defaultContext.newPage(); - const mainFrame = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(page.mainFrame); + const mainFrame = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(page.mainFrame); await page.close(); @@ -154,8 +154,8 @@ describe('DetachedBrowserPage', () => { const browser = new DetachedBrowser(BrowserWindow); browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.newPage(); - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(page.mainFrame); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(page.mainFrame); frame1.evaluate('setTimeout(() => { globalThis.test = 1; }, 10);'); frame2.evaluate('setTimeout(() => { globalThis.test = 2; }, 10);'); await page.waitUntilComplete(); @@ -186,8 +186,8 @@ describe('DetachedBrowserPage', () => { const browser = new DetachedBrowser(BrowserWindow); browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.newPage(); - const frame1 = BrowserFrameFactory.newChildFrame(page.mainFrame); - const frame2 = BrowserFrameFactory.newChildFrame(page.mainFrame); + const frame1 = BrowserFrameFactory.createChildFrame(page.mainFrame); + const frame2 = BrowserFrameFactory.createChildFrame(page.mainFrame); frame1.evaluate('setTimeout(() => { globalThis.test = 1; }, 10);'); frame2.evaluate('setTimeout(() => { globalThis.test = 2; }, 10);'); page.abort(); diff --git a/packages/happy-dom/test/storage/Storage.test.ts b/packages/happy-dom/test/storage/Storage.test.ts index 5f9a35627..713ac1b81 100644 --- a/packages/happy-dom/test/storage/Storage.test.ts +++ b/packages/happy-dom/test/storage/Storage.test.ts @@ -1,11 +1,12 @@ import Storage from '../../src/storage/Storage.js'; +import StorageFactory from '../../src/storage/StorageFactory.js'; import { beforeEach, describe, it, expect } from 'vitest'; describe('Storage', () => { let storage: Storage; beforeEach(() => { - storage = new Storage(); + storage = StorageFactory.createStorage(); }); describe('get length()', () => { @@ -39,6 +40,51 @@ describe('Storage', () => { storage['key3'] = 'value3'; expect(storage.getItem('key3')).toBe('value3'); }); + + it('Handles keys already taken by Javascript.', () => { + expect(storage.getItem('length')).toBe(null); + expect(storage.getItem('key')).toBe(null); + expect(storage.getItem('setItem')).toBe(null); + expect(storage.getItem('getItem')).toBe(null); + expect(storage.getItem('removeItem')).toBe(null); + expect(storage.getItem('clear')).toBe(null); + + storage.setItem('length', 'value'); + storage.setItem('key', 'value'); + storage.setItem('setItem', 'value'); + storage.setItem('getItem', 'value'); + storage.setItem('removeItem', 'value'); + storage.setItem('clear', 'value'); + + expect(storage.getItem('length')).toBe('value'); + expect(storage.getItem('key')).toBe('value'); + expect(storage.getItem('setItem')).toBe('value'); + expect(storage.getItem('getItem')).toBe('value'); + expect(storage.getItem('removeItem')).toBe('value'); + expect(storage.getItem('clear')).toBe('value'); + + const storage2 = StorageFactory.createStorage(); + + // @ts-expect-error + storage2.length = 'value'; + // @ts-expect-error + storage2.key = 'value'; + // @ts-expect-error + storage2.setItem = 'value'; + // @ts-expect-error + storage2.getItem = 'value'; + // @ts-expect-error + storage2.removeItem = 'value'; + // @ts-expect-error + storage2.clear = 'value'; + + expect(storage2.length).toBe(0); + expect(storage2.key).toBeTypeOf('function'); + expect(storage2.setItem).toBeTypeOf('function'); + expect(storage2.getItem).toBeTypeOf('function'); + expect(storage2.removeItem).toBeTypeOf('function'); + expect(storage2.clear).toBeTypeOf('function'); + }); }); describe('setItem()', () => { @@ -62,6 +108,11 @@ describe('Storage', () => { expect(storage.getItem('key1')).toBe('[object Object]'); expect(storage['key1']).toBe('[object Object]'); }); + + it('Handles keys already taken by Javascript.', () => { + storage.setItem('key', 'value'); + expect(storage.getItem('key')).toBe('value'); + }); }); describe('removeItem()', () => { diff --git a/packages/happy-dom/test/window/BrowserWindow.test.ts b/packages/happy-dom/test/window/BrowserWindow.test.ts index 2cd1ebe6d..98efe0c2e 100644 --- a/packages/happy-dom/test/window/BrowserWindow.test.ts +++ b/packages/happy-dom/test/window/BrowserWindow.test.ts @@ -286,6 +286,18 @@ describe('BrowserWindow', () => { }); }); + describe('get localStorage()', () => { + it('Returns the "localStorage" object.', () => { + expect(window.localStorage).toBeInstanceOf(window.Storage); + }); + }); + + describe('get sessionStorage()', () => { + it('Returns the "sessionStorage" object.', () => { + expect(window.sessionStorage).toBeInstanceOf(window.Storage); + }); + }); + describe('eval()', () => { it('Respects direct eval.', () => { const result = window.eval(` @@ -1353,7 +1365,7 @@ describe('BrowserWindow', () => { describe('postMessage()', () => { it('Posts a message.', async () => { await new Promise((resolve) => { - const frame = BrowserFrameFactory.newChildFrame(browserFrame); + const frame = BrowserFrameFactory.createChildFrame(browserFrame); const message = 'test'; let triggeredEvent: MessageEvent | null = null; @@ -1559,7 +1571,10 @@ describe('BrowserWindow', () => { expect(newWindow instanceof CrossOriginBrowserWindow).toBe(true); expect(browser.defaultContext.pages.length).toBe(2); expect(browser.defaultContext.pages[0]).toBe(page); - expect(browser.defaultContext.pages[1].mainFrame.window === newWindow).toBe(false); + expect( + (browser.defaultContext.pages[1].mainFrame.window) === + newWindow + ).toBe(false); expect(browser.defaultContext.pages[1].mainFrame.url).toBe( 'https://developer.mozilla.org/en-US/docs/Web/API/Window/open' );