Skip to content

Commit

Permalink
fix: [#1258] Adds support for constructing custom element using new k…
Browse files Browse the repository at this point in the history
…eyword
  • Loading branch information
capricorn86 committed Mar 12, 2024
1 parent f6e8467 commit 1711be4
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 7 deletions.
43 changes: 43 additions & 0 deletions packages/happy-dom/src/custom-element/CustomElementRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import DOMException from '../exception/DOMException.js';
import * as PropertySymbol from '../PropertySymbol.js';
import HTMLElement from '../nodes/html-element/HTMLElement.js';
import Node from '../nodes/node/Node.js';
import IBrowserWindow from '../window/IBrowserWindow.js';
import NamespaceURI from '../config/NamespaceURI.js';

/**
* Custom elements registry.
Expand All @@ -12,6 +14,16 @@ export default class CustomElementRegistry {
} = {};
public [PropertySymbol.registedClass]: Map<typeof HTMLElement, string> = new Map();
public [PropertySymbol.callbacks]: { [k: string]: (() => void)[] } = {};
#window: IBrowserWindow;

/**
* Constructor.
*
* @param window Window.
*/
constructor(window: IBrowserWindow) {
this.#window = window;
}

/**
* Defines a custom element class.
Expand Down Expand Up @@ -44,6 +56,25 @@ export default class CustomElementRegistry {
);
}

const tagName = name.toUpperCase();

elementClass[PropertySymbol.ownerDocument] = this.#window.document;

Object.defineProperty(elementClass.prototype, 'localName', {
configurable: true,
get: () => name
});

Object.defineProperty(elementClass.prototype, 'tagName', {
configurable: true,
get: () => tagName
});

Object.defineProperty(elementClass.prototype, 'namespaceURI', {
configurable: true,
get: () => NamespaceURI.html
});

this[PropertySymbol.registry][name] = {
elementClass,
extends: options && options.extends ? options.extends.toLowerCase() : null
Expand Down Expand Up @@ -113,6 +144,18 @@ export default class CustomElementRegistry {
return this[PropertySymbol.registedClass].get(elementClass) || null;
}

/**
* Destroys the registry.
*/
public [PropertySymbol.destroy](): void {
for (const entity of Object.values(this[PropertySymbol.registry])) {
entity.elementClass[PropertySymbol.ownerDocument] = null;
}
this[PropertySymbol.registry] = {};
this[PropertySymbol.registedClass] = new Map();
this[PropertySymbol.callbacks] = {};
}

/**
* Validates the correctness of custom element tag names.
*
Expand Down
5 changes: 1 addition & 4 deletions packages/happy-dom/src/nodes/document/Document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1122,10 +1122,7 @@ export default class Document extends Node implements IDocument {
];

if (customElement) {
const element = NodeFactory.createNode<IHTMLElement>(this, customElement.elementClass);
element[PropertySymbol.tagName] = qualifiedName.toUpperCase();
element[PropertySymbol.localName] = qualifiedName;
element[PropertySymbol.namespaceURI] = namespaceURI;
const element = new customElement.elementClass();
element[PropertySymbol.isValue] = options && options.is ? String(options.is) : null;
return element;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/src/nodes/node/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ export default class Node extends EventTarget implements INode {
* @param otherNode Node to test with.
* @returns "true" if this node contains the other node.
*/
public contains(otherNode: INode | undefined): boolean {
public contains(otherNode: INode): boolean {
if (otherNode === undefined) {
return false;
}
Expand Down
6 changes: 5 additions & 1 deletion packages/happy-dom/src/window/BrowserWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ export default class BrowserWindow extends EventTarget implements IBrowserWindow

this.#browserFrame = browserFrame;

this.customElements = new CustomElementRegistry();
this.customElements = new CustomElementRegistry(this);
this.navigator = new Navigator(this);
this.history = new History();
this.screen = new Screen();
Expand Down Expand Up @@ -1274,6 +1274,10 @@ export default class BrowserWindow extends EventTarget implements IBrowserWindow
this.document.removeChild(node);
}

if (this.customElements[PropertySymbol.destroy]) {
this.customElements[PropertySymbol.destroy]();
}

this.document[PropertySymbol.activeElement] = null;
this.document[PropertySymbol.nextActiveElement] = null;
this.document[PropertySymbol.currentScript] = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Window from '../../src/window/Window.js';
import DOMException from '../../src/exception/DOMException.js';
import { beforeEach, describe, it, expect } from 'vitest';
import * as PropertySymbol from '../../src/PropertySymbol.js';
import NamespaceURI from '../../src/config/NamespaceURI.js';

describe('CustomElementRegistry', () => {
let customElements;
Expand All @@ -15,7 +16,7 @@ describe('CustomElementRegistry', () => {
beforeEach(() => {
window = new Window();
document = window.document;
customElements = new CustomElementRegistry();
customElements = new CustomElementRegistry(window);
CustomElement.observedAttributesCallCount = 0;
});

Expand All @@ -37,6 +38,16 @@ describe('CustomElementRegistry', () => {
expect(customElements[PropertySymbol.registry]['custom-element'].extends).toBe('ul');
});

it('Can construct CustomElement instance using "new".', () => {
customElements.define('custom-element', CustomElement);
const customElement = new CustomElement();
expect(customElement).toBeInstanceOf(CustomElement);
expect(customElement.ownerDocument).toBe(document);
expect(customElement.localName).toBe('custom-element');
expect(customElement.tagName).toBe('CUSTOM-ELEMENT');
expect(customElement.namespaceURI).toBe(NamespaceURI.html);
});

it('Throws an error if tag name does not contain "-".', () => {
expect(() => customElements.define('element', CustomElement)).toThrow(
new DOMException(
Expand Down

0 comments on commit 1711be4

Please sign in to comment.