Repository: capricorn86/happy-dom
Pull request #152 (2020-11-15 21:14:49)

#117@patch: HTMLElement.observedAttributes should be called during cu…
…stomElements.define() only once if attributeChangedCallback() is defined.


Patch:
diff --git a/packages/happy-dom/src/custom-element/CustomElementRegistry.ts b/packages/happy-dom/src/custom-element/CustomElementRegistry.ts
index 14fdfb67..27cf4cf6 100644
--- a/packages/happy-dom/src/custom-element/CustomElementRegistry.ts
+++ b/packages/happy-dom/src/custom-element/CustomElementRegistry.ts
@@ -35,6 +35,10 @@ export default class CustomElementRegistry {
 			extends: options && options.extends ? options.extends.toLowerCase() : null
 		};
 
+		if (elementClass.prototype.attributeChangedCallback) {
+			elementClass._observedAttributes = elementClass.observedAttributes || null;
+		}
+
 		if (this._callbacks[name]) {
 			for (const callback of this._callbacks[name]) {
 				callback();
diff --git a/packages/happy-dom/src/nodes/element/Element.ts b/packages/happy-dom/src/nodes/element/Element.ts
index 70319423..cefff7b5 100644
--- a/packages/happy-dom/src/nodes/element/Element.ts
+++ b/packages/happy-dom/src/nodes/element/Element.ts
@@ -31,6 +31,7 @@ export default class Element extends Node implements IElement {
 	public children: Element[] = [];
 	public _attributes: { [k: string]: Attr } = {};
 	public readonly namespaceURI: string = null;
+	public static _observedAttributes: string[] = null;
 
 	/**
 	 * Returns a list of observed attributes.
@@ -636,8 +637,8 @@ export default class Element extends Node implements IElement {
 
 		if (
 			this.attributeChangedCallback &&
-			(<typeof Element>this.constructor).observedAttributes &&
-			(<typeof Element>this.constructor).observedAttributes.includes(name)
+			(<typeof Element>this.constructor)._observedAttributes &&
+			(<typeof Element>this.constructor)._observedAttributes.includes(name)
 		) {
 			this.attributeChangedCallback(name, oldValue, attribute.value);
 		}
@@ -681,8 +682,8 @@ export default class Element extends Node implements IElement {
 
 		if (
 			this.attributeChangedCallback &&
-			(<typeof Element>this.constructor).observedAttributes &&
-			(<typeof Element>this.constructor).observedAttributes.includes(attribute.name)
+			(<typeof Element>this.constructor)._observedAttributes &&
+			(<typeof Element>this.constructor)._observedAttributes.includes(attribute.name)
 		) {
 			this.attributeChangedCallback(attribute.name, attribute.value, null);
 		}


Test patch:
diff --git a/packages/happy-dom/test/CustomElement.ts b/packages/happy-dom/test/CustomElement.ts
index 414ac8c4..95b3fa62 100644
--- a/packages/happy-dom/test/CustomElement.ts
+++ b/packages/happy-dom/test/CustomElement.ts
@@ -5,6 +5,7 @@ import Window from '../src/window/Window';
  */
 export default class CustomElement extends new Window().HTMLElement {
 	public changedAttributes = [];
+	public static observedAttributesCallCount = 0;
 
 	/**
 	 * Returns a list of observed attributes.
@@ -12,6 +13,7 @@ export default class CustomElement extends new Window().HTMLElement {
 	 * @return Observered attributes.
 	 */
 	public static get observedAttributes(): string[] {
+		this.observedAttributesCallCount++;
 		return ['key1', 'key2'];
 	}
 
diff --git a/packages/happy-dom/test/custom-element/CustomElementRegistry.test.ts b/packages/happy-dom/test/custom-element/CustomElementRegistry.test.ts
index 6e017175..2a1eeaca 100644
--- a/packages/happy-dom/test/custom-element/CustomElementRegistry.test.ts
+++ b/packages/happy-dom/test/custom-element/CustomElementRegistry.test.ts
@@ -1,13 +1,12 @@
+import CustomElement from '../CustomElement';
 import CustomElementRegistry from '../../src/custom-element/CustomElementRegistry';
-import HTMLElement from '../../src/nodes/html-element/HTMLElement';
-
-class CustomElement extends HTMLElement {}
 
 describe('CustomElementRegistry', () => {
 	let customElements;
 
 	beforeEach(() => {
 		customElements = new CustomElementRegistry();
+		CustomElement.observedAttributesCallCount = 0;
 	});
 
 	describe('define()', () => {
@@ -34,6 +33,12 @@ describe('CustomElementRegistry', () => {
 				)
 			);
 		});
+
+		test('Calls observed attributes and set _observedAttributes as a property on the element class.', () => {
+			customElements.define('custom-element', CustomElement);
+			expect(CustomElement.observedAttributesCallCount).toBe(1);
+			expect(CustomElement._observedAttributes).toEqual(['key1', 'key2']);
+		});
 	});
 
 	describe('whenDefined()', () => {