Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: capricorn86/happy-dom
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v13.7.7
Choose a base ref
...
head repository: capricorn86/happy-dom
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 0dfe51d6006c09b2f12ec2ec4f15858ae6450060
Choose a head ref
  • 3 commits
  • 5 files changed
  • 1 contributor

Commits on Mar 12, 2024

  1. fix: [#1258] Adds support for constructing custom element using new k…

    …eyword
    capricorn86 committed Mar 12, 2024
    Copy the full SHA
    1711be4 View commit details
  2. chore: [#1258] Fixes unit tests

    capricorn86 committed Mar 12, 2024
    Copy the full SHA
    1f21076 View commit details
  3. Merge pull request #1298 from capricorn86/1258-error-creating-custom-…

    …element
    
    fix: [#1258] Adds support for constructing custom element using new k…
    capricorn86 authored Mar 12, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    0dfe51d View commit details
49 changes: 49 additions & 0 deletions packages/happy-dom/src/custom-element/CustomElementRegistry.ts
Original file line number Diff line number Diff line change
@@ -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.
@@ -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.
@@ -44,6 +56,31 @@ export default class CustomElementRegistry {
);
}

const tagName = name.toUpperCase();

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

Object.defineProperty(elementClass.prototype, 'localName', {
configurable: true,
get: function () {
return this[PropertySymbol.localName] || name;
}
});

Object.defineProperty(elementClass.prototype, 'tagName', {
configurable: true,
get: function () {
return this[PropertySymbol.tagName] || tagName;
}
});

Object.defineProperty(elementClass.prototype, 'namespaceURI', {
configurable: true,
get: function () {
return this[PropertySymbol.namespaceURI] || NamespaceURI.html;
}
});

this[PropertySymbol.registry][name] = {
elementClass,
extends: options && options.extends ? options.extends.toLowerCase() : null
@@ -113,6 +150,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.
*
2 changes: 1 addition & 1 deletion packages/happy-dom/src/nodes/document/Document.ts
Original file line number Diff line number Diff line change
@@ -1122,7 +1122,7 @@ export default class Document extends Node implements IDocument {
];

if (customElement) {
const element = NodeFactory.createNode<IHTMLElement>(this, customElement.elementClass);
const element = new customElement.elementClass();
element[PropertySymbol.tagName] = qualifiedName.toUpperCase();
element[PropertySymbol.localName] = qualifiedName;
element[PropertySymbol.namespaceURI] = namespaceURI;
2 changes: 1 addition & 1 deletion packages/happy-dom/src/nodes/node/Node.ts
Original file line number Diff line number Diff line change
@@ -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;
}
6 changes: 5 additions & 1 deletion packages/happy-dom/src/window/BrowserWindow.ts
Original file line number Diff line number Diff line change
@@ -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();
@@ -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;
Original file line number Diff line number Diff line change
@@ -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;
@@ -15,7 +16,7 @@ describe('CustomElementRegistry', () => {
beforeEach(() => {
window = new Window();
document = window.document;
customElements = new CustomElementRegistry();
customElements = new CustomElementRegistry(window);
CustomElement.observedAttributesCallCount = 0;
});

@@ -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(