From 167cabf5d808c3f18e1387effd0415d14fa0e6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Ledentu?= Date: Tue, 18 Oct 2022 14:46:57 +0200 Subject: [PATCH 1/4] #308@minor: Implement NamedNodeMap. --- .../happy-dom/src/nodes/attr/INamedNodeMap.ts | 23 +++ .../happy-dom/src/nodes/attr/NamedNodeMap.ts | 133 ++++++++++++++++++ .../happy-dom/src/nodes/element/Element.ts | 10 +- .../happy-dom/src/nodes/element/IElement.ts | 3 +- .../happy-dom/src/nodes/node/NodeUtility.ts | 4 +- packages/happy-dom/src/window/IWindow.ts | 2 + packages/happy-dom/src/window/Window.ts | 2 + .../test/nodes/element/Element.test.ts | 10 +- 8 files changed, 172 insertions(+), 15 deletions(-) create mode 100644 packages/happy-dom/src/nodes/attr/INamedNodeMap.ts create mode 100644 packages/happy-dom/src/nodes/attr/NamedNodeMap.ts diff --git a/packages/happy-dom/src/nodes/attr/INamedNodeMap.ts b/packages/happy-dom/src/nodes/attr/INamedNodeMap.ts new file mode 100644 index 000000000..cab5c9516 --- /dev/null +++ b/packages/happy-dom/src/nodes/attr/INamedNodeMap.ts @@ -0,0 +1,23 @@ +import IAttr from './IAttr'; + +export type INamedNodeMapProps = { + readonly length: number; + item: (index: number) => IAttr; + getNamedItem: (qualifiedName: string) => IAttr; + getNamedItemNS: (namespace: string, localName: string) => IAttr; + setNamedItem: (attr: IAttr) => IAttr; + setNamedItemNS: (attr: IAttr) => IAttr; + removeNamedItem: (qualifiedName: string) => IAttr; + removeNamedItemNS: (namespace: string, localName: string) => IAttr; + [Symbol.toStringTag]: string; +} & Iterable; +type INamedNodeMapProperties = keyof INamedNodeMapProps; + +type INamedNodeMap = INamedNodeMapProps & { + [k in Exclude]: IAttr; +}; + +/** + * NamedNodeMap interface. + */ +export default INamedNodeMap; diff --git a/packages/happy-dom/src/nodes/attr/NamedNodeMap.ts b/packages/happy-dom/src/nodes/attr/NamedNodeMap.ts new file mode 100644 index 000000000..65ce4344a --- /dev/null +++ b/packages/happy-dom/src/nodes/attr/NamedNodeMap.ts @@ -0,0 +1,133 @@ +import type Element from '../element/Element'; +import IAttr from './IAttr'; +import type { INamedNodeMapProps } from './INamedNodeMap'; + +/** + * NamedNodeMap. + * + * Reference: https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap. + */ +export default class NamedNodeMap implements INamedNodeMapProps { + private _element: Element; + + /** + * Constructor. + * + * @param element Associated element. + */ + constructor(element: Element) { + Object.defineProperty(this, '_element', { enumerable: false, writable: true, value: element }); + } + + /** + * Returns `Symbol.toStringTag`. + * + * @returns `Symbol.toStringTag`. + */ + public get [Symbol.toStringTag](): string { + return this.constructor.name; + } + + /** + * Length. + */ + public get length(): number { + return Object.keys(this._element._attributes).length; + } + + /** + * Returns attribute by index. + * + * @param index Index. + */ + public item(index: number): IAttr | null { + if (index < 0) { + return null; + } + const attr = Object.values(this._element._attributes)[index]; + return attr ? attr : null; + } + + /** + * Returns attribute by name. + * + * @param qualifiedName Name. + */ + public getNamedItem(qualifiedName: string): IAttr | null { + return this._element.getAttributeNode(qualifiedName); + } + + /** + * Returns attribute by name and namespace. + * + * @param namespace Namespace. + * @param localName Local name of the attribute. + */ + public getNamedItemNS(namespace: string, localName: string): IAttr | null { + return this._element.getAttributeNodeNS(namespace, localName); + } + + /** + * Adds a new attribute node. + * + * @param attr Attribute. + * @returns Replaced attribute. + */ + public setNamedItem(attr: IAttr): IAttr { + return this._element.setAttributeNode(attr); + } + + /** + * Adds a new namespaced attribute node. + * + * @param attr Attribute. + * @returns Replaced attribute. + */ + public setNamedItemNS(attr: IAttr): IAttr { + return this._element.setAttributeNodeNS(attr); + } + + /** + * Removes an attribute. + * + * @param qualifiedName Name of the attribute. + * @returns Removed attribute. + */ + public removeNamedItem(qualifiedName: string): IAttr | null { + const attr = this.getNamedItem(qualifiedName); + + if (attr) { + this._element.removeAttributeNode(attr); + } + return attr; + } + + /** + * Removes a namespaced attribute. + * + * @param namespace Namespace. + * @param localName Local name of the attribute. + * @returns Removed attribute. + */ + public removeNamedItemNS(namespace: string, localName: string): IAttr | null { + const attr = this.getNamedItemNS(namespace, localName); + + if (attr) { + this._element.removeAttributeNode(attr); + } + return attr; + } + + /** + * Iterator. + */ + public [Symbol.iterator](): Iterator { + let index = -1; + return { + next: () => { + index++; + return { value: this.item(index), done: index >= this.length }; + } + }; + } +} diff --git a/packages/happy-dom/src/nodes/element/Element.ts b/packages/happy-dom/src/nodes/element/Element.ts index e41f4c9f6..cfef4f03b 100644 --- a/packages/happy-dom/src/nodes/element/Element.ts +++ b/packages/happy-dom/src/nodes/element/Element.ts @@ -1,6 +1,7 @@ import Node from '../node/Node'; import ShadowRoot from '../shadow-root/ShadowRoot'; import Attr from '../attr/Attr'; +import NamedNodeMap from '../attr/NamedNodeMap'; import DOMRect from './DOMRect'; import DOMTokenList from '../../dom-token-list/DOMTokenList'; import IDOMTokenList from '../../dom-token-list/IDOMTokenList'; @@ -26,6 +27,8 @@ import IText from '../text/IText'; import IDOMRectList from './IDOMRectList'; import DOMRectListFactory from './DOMRectListFactory'; import IAttr from '../attr/IAttr'; +import INamedNodeMap from '../attr/INamedNodeMap'; + import Event from '../../event/Event'; /** @@ -249,11 +252,8 @@ export default class Element extends Node implements IElement { * * @returns Attributes. */ - public get attributes(): { [k: string | number]: IAttr } & { length: number } { - const attributes = Object.values(this._attributes); - return Object.assign({}, this._attributes, attributes, { - length: attributes.length - }); + public get attributes(): INamedNodeMap { + return Object.assign(new NamedNodeMap(this), Object.values(this._attributes), this._attributes); } /** diff --git a/packages/happy-dom/src/nodes/element/IElement.ts b/packages/happy-dom/src/nodes/element/IElement.ts index 20c6c7656..386ef2ee5 100644 --- a/packages/happy-dom/src/nodes/element/IElement.ts +++ b/packages/happy-dom/src/nodes/element/IElement.ts @@ -1,5 +1,6 @@ import IShadowRoot from '../shadow-root/IShadowRoot'; import IAttr from '../attr/IAttr'; +import INamedNodeMap from '../attr/INamedNodeMap'; import DOMRect from './DOMRect'; import IDOMTokenList from '../../dom-token-list/IDOMTokenList'; import INode from './../node/INode'; @@ -29,7 +30,7 @@ export default interface IElement extends IChildNode, INonDocumentTypeChildNode, slot: string; readonly nodeName: string; readonly localName: string; - readonly attributes: { [k: string | number]: IAttr } & { length: number }; + readonly attributes: INamedNodeMap; // Events oncancel: (event: Event) => void | null; diff --git a/packages/happy-dom/src/nodes/node/NodeUtility.ts b/packages/happy-dom/src/nodes/node/NodeUtility.ts index 12e91da93..bd331d8e3 100644 --- a/packages/happy-dom/src/nodes/node/NodeUtility.ts +++ b/packages/happy-dom/src/nodes/node/NodeUtility.ts @@ -148,8 +148,8 @@ export default class NodeUtility { * @param elementB */ public static attributeListsEqual(elementA: IElement, elementB: IElement): boolean { - const listA = Object.values(elementA.attributes); - const listB = Object.values(elementB.attributes); + const listA = Array.from(elementA.attributes); + const listB = Array.from(elementB.attributes); const lengthA = listA.length; const lengthB = listB.length; diff --git a/packages/happy-dom/src/window/IWindow.ts b/packages/happy-dom/src/window/IWindow.ts index 3d9a61eb9..4b6aed156 100644 --- a/packages/happy-dom/src/window/IWindow.ts +++ b/packages/happy-dom/src/window/IWindow.ts @@ -95,6 +95,7 @@ import MediaQueryList from '../match-media/MediaQueryList'; import DOMRect from '../nodes/element/DOMRect'; import Window from './Window'; import Attr from '../nodes/attr/Attr'; +import NamedNodeMap from '../nodes/attr/NamedNodeMap'; import { URLSearchParams } from 'url'; import { Performance } from 'perf_hooks'; import IElement from '../nodes/element/IElement'; @@ -134,6 +135,7 @@ export default interface IWindow extends IEventTarget, NodeJS.Global { readonly HTMLBaseElement: typeof HTMLBaseElement; readonly HTMLDialogElement: typeof HTMLDialogElement; readonly Attr: typeof Attr; + readonly NamedNodeMap: typeof NamedNodeMap; readonly SVGSVGElement: typeof SVGSVGElement; readonly SVGElement: typeof SVGElement; readonly Image: typeof Image; diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts index 5120800a6..d39353627 100644 --- a/packages/happy-dom/src/window/Window.ts +++ b/packages/happy-dom/src/window/Window.ts @@ -108,6 +108,7 @@ import { Buffer } from 'buffer'; import Base64 from '../base64/Base64'; import IDocument from '../nodes/document/IDocument'; import Attr from '../nodes/attr/Attr'; +import NamedNodeMap from '../nodes/attr/NamedNodeMap'; import IElement from '../nodes/element/IElement'; import ProcessingInstruction from '../nodes/processing-instruction/ProcessingInstruction'; @@ -167,6 +168,7 @@ export default class Window extends EventTarget implements IWindow { public readonly HTMLBaseElement = HTMLBaseElement; public readonly HTMLDialogElement = HTMLDialogElement; public readonly Attr = Attr; + public readonly NamedNodeMap = NamedNodeMap; public readonly SVGSVGElement = SVGSVGElement; public readonly SVGElement = SVGElement; public readonly Text = Text; diff --git a/packages/happy-dom/test/nodes/element/Element.test.ts b/packages/happy-dom/test/nodes/element/Element.test.ts index 0d6b551a7..203445b66 100644 --- a/packages/happy-dom/test/nodes/element/Element.test.ts +++ b/packages/happy-dom/test/nodes/element/Element.test.ts @@ -1198,9 +1198,7 @@ describe('Element', () => { it('Removes an attribute.', () => { element.setAttribute('key1', 'value1'); element.removeAttribute('key1'); - expect(element.attributes).toEqual({ - length: 0 - }); + expect(element.attributes.length).toBe(0); }); }); @@ -1208,9 +1206,7 @@ describe('Element', () => { it('Removes a namespace attribute.', () => { element.setAttributeNS(NAMESPACE_URI, 'global:local', 'value'); element.removeAttributeNS(NAMESPACE_URI, 'local'); - expect(element.attributes).toEqual({ - length: 0 - }); + expect(element.attributes.length).toBe(0); }); }); @@ -1478,7 +1474,7 @@ describe('Element', () => { element.setAttributeNode(attribute); element[method](attribute); - expect(element.attributes).toEqual({ length: 0 }); + expect(element.attributes.length).toBe(0); }); }); } From fbb7e748f5cfef219c3589db4515d779b73fbe28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Ledentu?= Date: Tue, 18 Oct 2022 16:50:00 +0200 Subject: [PATCH 2/4] #308@trivial: Refactor and add tests on NamedNodeMap. --- .../attr => named-node-map}/INamedNodeMap.ts | 2 +- .../attr => named-node-map}/NamedNodeMap.ts | 30 +++-- .../happy-dom/src/nodes/element/Element.ts | 4 +- .../happy-dom/src/nodes/element/IElement.ts | 2 +- packages/happy-dom/src/window/IWindow.ts | 2 +- packages/happy-dom/src/window/Window.ts | 2 +- .../test/named-node-map/NamedNodeMap.test.ts | 124 ++++++++++++++++++ 7 files changed, 148 insertions(+), 18 deletions(-) rename packages/happy-dom/src/{nodes/attr => named-node-map}/INamedNodeMap.ts (94%) rename packages/happy-dom/src/{nodes/attr => named-node-map}/NamedNodeMap.ts (75%) create mode 100644 packages/happy-dom/test/named-node-map/NamedNodeMap.test.ts diff --git a/packages/happy-dom/src/nodes/attr/INamedNodeMap.ts b/packages/happy-dom/src/named-node-map/INamedNodeMap.ts similarity index 94% rename from packages/happy-dom/src/nodes/attr/INamedNodeMap.ts rename to packages/happy-dom/src/named-node-map/INamedNodeMap.ts index cab5c9516..f05de158b 100644 --- a/packages/happy-dom/src/nodes/attr/INamedNodeMap.ts +++ b/packages/happy-dom/src/named-node-map/INamedNodeMap.ts @@ -1,4 +1,4 @@ -import IAttr from './IAttr'; +import IAttr from '../nodes/attr/IAttr'; export type INamedNodeMapProps = { readonly length: number; diff --git a/packages/happy-dom/src/nodes/attr/NamedNodeMap.ts b/packages/happy-dom/src/named-node-map/NamedNodeMap.ts similarity index 75% rename from packages/happy-dom/src/nodes/attr/NamedNodeMap.ts rename to packages/happy-dom/src/named-node-map/NamedNodeMap.ts index 65ce4344a..c3c1317fe 100644 --- a/packages/happy-dom/src/nodes/attr/NamedNodeMap.ts +++ b/packages/happy-dom/src/named-node-map/NamedNodeMap.ts @@ -1,5 +1,5 @@ -import type Element from '../element/Element'; -import IAttr from './IAttr'; +import type Element from '../nodes/element/Element'; +import IAttr from '../nodes/attr/IAttr'; import type { INamedNodeMapProps } from './INamedNodeMap'; /** @@ -8,7 +8,10 @@ import type { INamedNodeMapProps } from './INamedNodeMap'; * Reference: https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap. */ export default class NamedNodeMap implements INamedNodeMapProps { - private _element: Element; + /** + * Reference to the element. + */ + private _ownerElement: Element; /** * Constructor. @@ -16,7 +19,10 @@ export default class NamedNodeMap implements INamedNodeMapProps { * @param element Associated element. */ constructor(element: Element) { - Object.defineProperty(this, '_element', { enumerable: false, writable: true, value: element }); + Object.defineProperty(this, '_ownerElement', { + enumerable: false, + value: element + }); } /** @@ -32,7 +38,7 @@ export default class NamedNodeMap implements INamedNodeMapProps { * Length. */ public get length(): number { - return Object.keys(this._element._attributes).length; + return Object.keys(this._ownerElement._attributes).length; } /** @@ -44,7 +50,7 @@ export default class NamedNodeMap implements INamedNodeMapProps { if (index < 0) { return null; } - const attr = Object.values(this._element._attributes)[index]; + const attr = Object.values(this._ownerElement._attributes)[index]; return attr ? attr : null; } @@ -54,7 +60,7 @@ export default class NamedNodeMap implements INamedNodeMapProps { * @param qualifiedName Name. */ public getNamedItem(qualifiedName: string): IAttr | null { - return this._element.getAttributeNode(qualifiedName); + return this._ownerElement.getAttributeNode(qualifiedName); } /** @@ -64,7 +70,7 @@ export default class NamedNodeMap implements INamedNodeMapProps { * @param localName Local name of the attribute. */ public getNamedItemNS(namespace: string, localName: string): IAttr | null { - return this._element.getAttributeNodeNS(namespace, localName); + return this._ownerElement.getAttributeNodeNS(namespace, localName); } /** @@ -74,7 +80,7 @@ export default class NamedNodeMap implements INamedNodeMapProps { * @returns Replaced attribute. */ public setNamedItem(attr: IAttr): IAttr { - return this._element.setAttributeNode(attr); + return this._ownerElement.setAttributeNode(attr); } /** @@ -84,7 +90,7 @@ export default class NamedNodeMap implements INamedNodeMapProps { * @returns Replaced attribute. */ public setNamedItemNS(attr: IAttr): IAttr { - return this._element.setAttributeNodeNS(attr); + return this._ownerElement.setAttributeNodeNS(attr); } /** @@ -97,7 +103,7 @@ export default class NamedNodeMap implements INamedNodeMapProps { const attr = this.getNamedItem(qualifiedName); if (attr) { - this._element.removeAttributeNode(attr); + this._ownerElement.removeAttributeNode(attr); } return attr; } @@ -113,7 +119,7 @@ export default class NamedNodeMap implements INamedNodeMapProps { const attr = this.getNamedItemNS(namespace, localName); if (attr) { - this._element.removeAttributeNode(attr); + this._ownerElement.removeAttributeNode(attr); } return attr; } diff --git a/packages/happy-dom/src/nodes/element/Element.ts b/packages/happy-dom/src/nodes/element/Element.ts index cfef4f03b..0e90932d0 100644 --- a/packages/happy-dom/src/nodes/element/Element.ts +++ b/packages/happy-dom/src/nodes/element/Element.ts @@ -1,7 +1,7 @@ import Node from '../node/Node'; import ShadowRoot from '../shadow-root/ShadowRoot'; import Attr from '../attr/Attr'; -import NamedNodeMap from '../attr/NamedNodeMap'; +import NamedNodeMap from '../../named-node-map/NamedNodeMap'; import DOMRect from './DOMRect'; import DOMTokenList from '../../dom-token-list/DOMTokenList'; import IDOMTokenList from '../../dom-token-list/IDOMTokenList'; @@ -27,7 +27,7 @@ import IText from '../text/IText'; import IDOMRectList from './IDOMRectList'; import DOMRectListFactory from './DOMRectListFactory'; import IAttr from '../attr/IAttr'; -import INamedNodeMap from '../attr/INamedNodeMap'; +import INamedNodeMap from '../../named-node-map/INamedNodeMap'; import Event from '../../event/Event'; diff --git a/packages/happy-dom/src/nodes/element/IElement.ts b/packages/happy-dom/src/nodes/element/IElement.ts index 386ef2ee5..7649cd0bf 100644 --- a/packages/happy-dom/src/nodes/element/IElement.ts +++ b/packages/happy-dom/src/nodes/element/IElement.ts @@ -1,6 +1,6 @@ import IShadowRoot from '../shadow-root/IShadowRoot'; import IAttr from '../attr/IAttr'; -import INamedNodeMap from '../attr/INamedNodeMap'; +import INamedNodeMap from '../../named-node-map/INamedNodeMap'; import DOMRect from './DOMRect'; import IDOMTokenList from '../../dom-token-list/IDOMTokenList'; import INode from './../node/INode'; diff --git a/packages/happy-dom/src/window/IWindow.ts b/packages/happy-dom/src/window/IWindow.ts index 4b6aed156..94dee772f 100644 --- a/packages/happy-dom/src/window/IWindow.ts +++ b/packages/happy-dom/src/window/IWindow.ts @@ -95,7 +95,7 @@ import MediaQueryList from '../match-media/MediaQueryList'; import DOMRect from '../nodes/element/DOMRect'; import Window from './Window'; import Attr from '../nodes/attr/Attr'; -import NamedNodeMap from '../nodes/attr/NamedNodeMap'; +import NamedNodeMap from '../named-node-map/NamedNodeMap'; import { URLSearchParams } from 'url'; import { Performance } from 'perf_hooks'; import IElement from '../nodes/element/IElement'; diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts index d39353627..ac23a14f1 100644 --- a/packages/happy-dom/src/window/Window.ts +++ b/packages/happy-dom/src/window/Window.ts @@ -108,7 +108,7 @@ import { Buffer } from 'buffer'; import Base64 from '../base64/Base64'; import IDocument from '../nodes/document/IDocument'; import Attr from '../nodes/attr/Attr'; -import NamedNodeMap from '../nodes/attr/NamedNodeMap'; +import NamedNodeMap from '../named-node-map/NamedNodeMap'; import IElement from '../nodes/element/IElement'; import ProcessingInstruction from '../nodes/processing-instruction/ProcessingInstruction'; diff --git a/packages/happy-dom/test/named-node-map/NamedNodeMap.test.ts b/packages/happy-dom/test/named-node-map/NamedNodeMap.test.ts new file mode 100644 index 000000000..f2812903b --- /dev/null +++ b/packages/happy-dom/test/named-node-map/NamedNodeMap.test.ts @@ -0,0 +1,124 @@ +import IWindow from '../../src/window/IWindow'; +import Window from '../../src/window/Window'; +import IDocument from '../../src/nodes/document/IDocument'; +import IElement from '../../src/nodes/element/IElement'; +import INamedNodeMap from 'src/named-node-map/INamedNodeMap'; + +describe('NamedNodeMap', () => { + let window: IWindow; + let document: IDocument; + let element: IElement; + let attributes: INamedNodeMap; + + beforeEach(() => { + window = new Window(); + document = window.document; + element = document.createElement('div'); + attributes = element.attributes; + }); + + describe('get length()', () => { + it('Is an integer representing the number of objects stored in the object.', () => { + element.setAttribute('key1', 'value1'); + element.setAttribute('key2', 'value2'); + + expect(attributes.length).toBe(2); + + element.setAttribute('key3', 'value3'); + + expect(attributes.length).toBe(3); + }); + }); + + describe('item()', () => { + it('Returns an attribute by index.', () => { + element.setAttribute('key1', 'value1'); + element.setAttribute('key2', 'value2'); + + expect(attributes.item(0).name).toBe('key1'); + expect(attributes.item(0).value).toBe('value1'); + expect(attributes.item(1).name).toBe('key2'); + expect(attributes.item(1).value).toBe('value2'); + }); + }); + + describe('getNamedItem()', () => { + it('Returns an attribute by name.', () => { + element.setAttribute('key1', 'value1'); + element.setAttribute('key2', 'value2'); + + expect(attributes.getNamedItem('key1').name).toBe('key1'); + expect(attributes.getNamedItem('key1').value).toBe('value1'); + expect(attributes.getNamedItem('key2').name).toBe('key2'); + expect(attributes.getNamedItem('key2').value).toBe('value2'); + }); + }); + + describe('getNamedItemNS()', () => { + it('Returns an attribute by name.', () => { + element.setAttributeNS('namespace', 'key1', 'value1'); + element.setAttributeNS('namespace', 'key2', 'value2'); + + expect(attributes.getNamedItemNS('namespace', 'key1').name).toBe('key1'); + expect(attributes.getNamedItemNS('namespace', 'key1').value).toBe('value1'); + expect(attributes.getNamedItemNS('namespace', 'key2').name).toBe('key2'); + expect(attributes.getNamedItemNS('namespace', 'key2').value).toBe('value2'); + }); + }); + + describe('setNamedItem()', () => { + it('Adds an attribute when not existing.', () => { + element.setAttribute('key', 'value'); + const attr = attributes.removeNamedItem('key'); + + expect(attributes.getNamedItem('key')).toBe(null); + + attributes.setNamedItem(attr); + + expect(attributes.getNamedItem('key')).toBe(attr); + }); + + it('Replaces an attribute when existing.', () => { + element.setAttribute('key', 'value1'); + const attr = attributes.getNamedItem('key'); + attr.value = 'value2'; + + attributes.setNamedItem(attr); + + expect(attributes.getNamedItem('key')).toBe(attr); + expect(element.getAttribute('key')).toBe('value2'); + }); + }); + + describe('setNamedItemNS()', () => { + it('Adds an namespaced attribute when not existing.', () => { + element.setAttributeNS('namespace', 'key', 'value'); + const attr = attributes.removeNamedItemNS('namespace', 'key'); + + attributes.setNamedItemNS(attr); + + expect(attributes.getNamedItem('key')).toBe(attr); + expect(element.getAttributeNS('namespace', 'key')).toBe('value'); + }); + + it('Replaces an attribute when existing.', () => { + element.setAttributeNS('namespace', 'key', 'value1'); + const attr = attributes.getNamedItemNS('namespace', 'key'); + attr.value = 'value2'; + + attributes.setNamedItemNS(attr); + + expect(attributes.getNamedItemNS('namespace', 'key')).toBe(attr); + expect(element.getAttributeNS('namespace', 'key')).toBe('value2'); + }); + }); + + describe('removeNamedItem()', () => { + it('Removes an attribute from the list.', () => { + element.setAttribute('key', 'value'); + attributes.removeNamedItem('key'); + + expect(element.getAttribute('key')).toBe(null); + }); + }); +}); From dad18e4a76c35d549f6a8affa3b4572d337c2f06 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 18 Oct 2022 23:25:59 +0200 Subject: [PATCH 3/4] #308@trivial: Improves interface for NamedNodeMap and adds some more unit tests. --- .../src/named-node-map/INamedNodeMap.ts | 73 +++++++++-- .../src/named-node-map/NamedNodeMap.ts | 42 ++++--- .../happy-dom/src/nodes/node/NodeUtility.ts | 4 +- .../test/named-node-map/NamedNodeMap.test.ts | 40 +++++- .../test/nodes/element/Element.test.ts | 116 +++++++++--------- .../test/xml-parser/XMLParser.test.ts | 82 ++++++------- 6 files changed, 220 insertions(+), 137 deletions(-) diff --git a/packages/happy-dom/src/named-node-map/INamedNodeMap.ts b/packages/happy-dom/src/named-node-map/INamedNodeMap.ts index f05de158b..a0a904fc8 100644 --- a/packages/happy-dom/src/named-node-map/INamedNodeMap.ts +++ b/packages/happy-dom/src/named-node-map/INamedNodeMap.ts @@ -1,23 +1,70 @@ import IAttr from '../nodes/attr/IAttr'; -export type INamedNodeMapProps = { +/** + * NamedNodeMap. + * + * Reference: + * https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap. + */ +export default interface INamedNodeMap extends Iterable { + [index: number]: IAttr; + [Symbol.toStringTag]: string; readonly length: number; + + /** + * Returns attribute by index. + * + * @param index Index. + */ item: (index: number) => IAttr; + + /** + * Returns attribute by name. + * + * @param qualifiedName Name. + * @returns Attribute. + */ getNamedItem: (qualifiedName: string) => IAttr; + + /** + * Returns attribute by name and namespace. + * + * @param namespace Namespace. + * @param localName Local name of the attribute. + * @returns Attribute. + */ getNamedItemNS: (namespace: string, localName: string) => IAttr; + + /** + * Adds a new attribute node. + * + * @param attr Attribute. + * @returns Replaced attribute. + */ setNamedItem: (attr: IAttr) => IAttr; + + /** + * Adds a new namespaced attribute node. + * + * @param attr Attribute. + * @returns Replaced attribute. + */ setNamedItemNS: (attr: IAttr) => IAttr; - removeNamedItem: (qualifiedName: string) => IAttr; - removeNamedItemNS: (namespace: string, localName: string) => IAttr; - [Symbol.toStringTag]: string; -} & Iterable; -type INamedNodeMapProperties = keyof INamedNodeMapProps; -type INamedNodeMap = INamedNodeMapProps & { - [k in Exclude]: IAttr; -}; + /** + * Removes an attribute. + * + * @param qualifiedName Name of the attribute. + * @returns Removed attribute. + */ + removeNamedItem: (qualifiedName: string) => IAttr; -/** - * NamedNodeMap interface. - */ -export default INamedNodeMap; + /** + * Removes a namespaced attribute. + * + * @param namespace Namespace. + * @param localName Local name of the attribute. + * @returns Removed attribute. + */ + removeNamedItemNS: (namespace: string, localName: string) => IAttr; +} diff --git a/packages/happy-dom/src/named-node-map/NamedNodeMap.ts b/packages/happy-dom/src/named-node-map/NamedNodeMap.ts index c3c1317fe..19b18968a 100644 --- a/packages/happy-dom/src/named-node-map/NamedNodeMap.ts +++ b/packages/happy-dom/src/named-node-map/NamedNodeMap.ts @@ -1,17 +1,20 @@ import type Element from '../nodes/element/Element'; import IAttr from '../nodes/attr/IAttr'; -import type { INamedNodeMapProps } from './INamedNodeMap'; +import INamedNodeMap from './INamedNodeMap'; /** * NamedNodeMap. * - * Reference: https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap. + * Reference: + * https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap. */ -export default class NamedNodeMap implements INamedNodeMapProps { +export default class NamedNodeMap implements INamedNodeMap { + [index: number]: IAttr; + /** * Reference to the element. */ - private _ownerElement: Element; + #ownerElement: Element; /** * Constructor. @@ -19,16 +22,13 @@ export default class NamedNodeMap implements INamedNodeMapProps { * @param element Associated element. */ constructor(element: Element) { - Object.defineProperty(this, '_ownerElement', { - enumerable: false, - value: element - }); + this.#ownerElement = element; } /** - * Returns `Symbol.toStringTag`. + * Returns string. * - * @returns `Symbol.toStringTag`. + * @returns string. */ public get [Symbol.toStringTag](): string { return this.constructor.name; @@ -36,9 +36,11 @@ export default class NamedNodeMap implements INamedNodeMapProps { /** * Length. + * + * @returns Length. */ public get length(): number { - return Object.keys(this._ownerElement._attributes).length; + return Object.keys(this.#ownerElement._attributes).length; } /** @@ -50,7 +52,7 @@ export default class NamedNodeMap implements INamedNodeMapProps { if (index < 0) { return null; } - const attr = Object.values(this._ownerElement._attributes)[index]; + const attr = Object.values(this.#ownerElement._attributes)[index]; return attr ? attr : null; } @@ -58,9 +60,10 @@ export default class NamedNodeMap implements INamedNodeMapProps { * Returns attribute by name. * * @param qualifiedName Name. + * @returns Attribute. */ public getNamedItem(qualifiedName: string): IAttr | null { - return this._ownerElement.getAttributeNode(qualifiedName); + return this.#ownerElement.getAttributeNode(qualifiedName); } /** @@ -68,9 +71,10 @@ export default class NamedNodeMap implements INamedNodeMapProps { * * @param namespace Namespace. * @param localName Local name of the attribute. + * @returns Attribute. */ public getNamedItemNS(namespace: string, localName: string): IAttr | null { - return this._ownerElement.getAttributeNodeNS(namespace, localName); + return this.#ownerElement.getAttributeNodeNS(namespace, localName); } /** @@ -80,7 +84,7 @@ export default class NamedNodeMap implements INamedNodeMapProps { * @returns Replaced attribute. */ public setNamedItem(attr: IAttr): IAttr { - return this._ownerElement.setAttributeNode(attr); + return this.#ownerElement.setAttributeNode(attr); } /** @@ -90,7 +94,7 @@ export default class NamedNodeMap implements INamedNodeMapProps { * @returns Replaced attribute. */ public setNamedItemNS(attr: IAttr): IAttr { - return this._ownerElement.setAttributeNodeNS(attr); + return this.#ownerElement.setAttributeNodeNS(attr); } /** @@ -103,7 +107,7 @@ export default class NamedNodeMap implements INamedNodeMapProps { const attr = this.getNamedItem(qualifiedName); if (attr) { - this._ownerElement.removeAttributeNode(attr); + this.#ownerElement.removeAttributeNode(attr); } return attr; } @@ -119,13 +123,15 @@ export default class NamedNodeMap implements INamedNodeMapProps { const attr = this.getNamedItemNS(namespace, localName); if (attr) { - this._ownerElement.removeAttributeNode(attr); + this.#ownerElement.removeAttributeNode(attr); } return attr; } /** * Iterator. + * + * @returns Iterator. */ public [Symbol.iterator](): Iterator { let index = -1; diff --git a/packages/happy-dom/src/nodes/node/NodeUtility.ts b/packages/happy-dom/src/nodes/node/NodeUtility.ts index bd331d8e3..0cafdcf0a 100644 --- a/packages/happy-dom/src/nodes/node/NodeUtility.ts +++ b/packages/happy-dom/src/nodes/node/NodeUtility.ts @@ -148,8 +148,8 @@ export default class NodeUtility { * @param elementB */ public static attributeListsEqual(elementA: IElement, elementB: IElement): boolean { - const listA = Array.from(elementA.attributes); - const listB = Array.from(elementB.attributes); + const listA = >Object.values(elementA['_attributes']); + const listB = >Object.values(elementB['_attributes']); const lengthA = listA.length; const lengthB = listB.length; diff --git a/packages/happy-dom/test/named-node-map/NamedNodeMap.test.ts b/packages/happy-dom/test/named-node-map/NamedNodeMap.test.ts index f2812903b..9b1214a66 100644 --- a/packages/happy-dom/test/named-node-map/NamedNodeMap.test.ts +++ b/packages/happy-dom/test/named-node-map/NamedNodeMap.test.ts @@ -17,12 +17,34 @@ describe('NamedNodeMap', () => { attributes = element.attributes; }); - describe('get length()', () => { - it('Is an integer representing the number of objects stored in the object.', () => { + describe('get toString()', () => { + it('Returns a stirng.', () => { + expect(attributes.toString()).toBe('[object NamedNodeMap]'); + }); + }); + + describe('get toString()', () => { + it('Returns a stirng.', () => { + expect(attributes.toString()).toBe('[object NamedNodeMap]'); + }); + }); + + describe('Symbol.iterator()', () => { + it('Handles being an iterator.', () => { element.setAttribute('key1', 'value1'); element.setAttribute('key2', 'value2'); + const attributeList = []; + + for (const attribute of attributes) { + attributeList.push(attribute); + } + expect(attributes.length).toBe(2); + expect(attributeList[0].name).toBe('key1'); + expect(attributeList[0].value).toBe('value1'); + expect(attributeList[1].name).toBe('key2'); + expect(attributeList[1].value).toBe('value2'); element.setAttribute('key3', 'value3'); @@ -83,8 +105,10 @@ describe('NamedNodeMap', () => { const attr = attributes.getNamedItem('key'); attr.value = 'value2'; - attributes.setNamedItem(attr); + const replaced = attributes.setNamedItem(attr); + expect(replaced.name).toBe('key'); + expect(replaced.value).toBe('value1'); expect(attributes.getNamedItem('key')).toBe(attr); expect(element.getAttribute('key')).toBe('value2'); }); @@ -106,7 +130,10 @@ describe('NamedNodeMap', () => { const attr = attributes.getNamedItemNS('namespace', 'key'); attr.value = 'value2'; - attributes.setNamedItemNS(attr); + const replaced = attributes.setNamedItemNS(attr); + + expect(replaced.name).toBe('key'); + expect(replaced.value).toBe('value1'); expect(attributes.getNamedItemNS('namespace', 'key')).toBe(attr); expect(element.getAttributeNS('namespace', 'key')).toBe('value2'); @@ -116,7 +143,10 @@ describe('NamedNodeMap', () => { describe('removeNamedItem()', () => { it('Removes an attribute from the list.', () => { element.setAttribute('key', 'value'); - attributes.removeNamedItem('key'); + const removed = attributes.removeNamedItem('key'); + + expect(removed.name).toBe('key'); + expect(removed.value).toBe('value'); expect(element.getAttribute('key')).toBe(null); }); diff --git a/packages/happy-dom/test/nodes/element/Element.test.ts b/packages/happy-dom/test/nodes/element/Element.test.ts index 203445b66..b07af70b0 100644 --- a/packages/happy-dom/test/nodes/element/Element.test.ts +++ b/packages/happy-dom/test/nodes/element/Element.test.ts @@ -267,26 +267,26 @@ describe('Element', () => { expect(element.attributes[2].ownerElement === element).toBe(true); expect(element.attributes[2].ownerDocument === document).toBe(true); - expect(element.attributes.key1.name).toBe('key1'); - expect(element.attributes.key1.value).toBe('value1'); - expect(element.attributes.key1.namespaceURI).toBe(null); - expect(element.attributes.key1.specified).toBe(true); - expect(element.attributes.key1.ownerElement === element).toBe(true); - expect(element.attributes.key1.ownerDocument === document).toBe(true); - - expect(element.attributes.key2.name).toBe('key2'); - expect(element.attributes.key2.value).toBe('value2'); - expect(element.attributes.key2.namespaceURI).toBe(null); - expect(element.attributes.key2.specified).toBe(true); - expect(element.attributes.key2.ownerElement === element).toBe(true); - expect(element.attributes.key2.ownerDocument === document).toBe(true); - - expect(element.attributes.key3.name).toBe('key3'); - expect(element.attributes.key3.value).toBe('value3'); - expect(element.attributes.key3.namespaceURI).toBe(null); - expect(element.attributes.key3.specified).toBe(true); - expect(element.attributes.key3.ownerElement === element).toBe(true); - expect(element.attributes.key3.ownerDocument === document).toBe(true); + expect(element.attributes['key1'].name).toBe('key1'); + expect(element.attributes['key1'].value).toBe('value1'); + expect(element.attributes['key1'].namespaceURI).toBe(null); + expect(element.attributes['key1'].specified).toBe(true); + expect(element.attributes['key1'].ownerElement === element).toBe(true); + expect(element.attributes['key1'].ownerDocument === document).toBe(true); + + expect(element.attributes['key2'].name).toBe('key2'); + expect(element.attributes['key2'].value).toBe('value2'); + expect(element.attributes['key2'].namespaceURI).toBe(null); + expect(element.attributes['key2'].specified).toBe(true); + expect(element.attributes['key2'].ownerElement === element).toBe(true); + expect(element.attributes['key2'].ownerDocument === document).toBe(true); + + expect(element.attributes['key3'].name).toBe('key3'); + expect(element.attributes['key3'].value).toBe('value3'); + expect(element.attributes['key3'].namespaceURI).toBe(null); + expect(element.attributes['key3'].specified).toBe(true); + expect(element.attributes['key3'].ownerElement === element).toBe(true); + expect(element.attributes['key3'].ownerDocument === document).toBe(true); }); }); @@ -1106,19 +1106,19 @@ describe('Element', () => { expect(element.attributes[1].ownerElement === element).toBe(true); expect(element.attributes[1].ownerDocument === document).toBe(true); - expect(element.attributes.key1.name).toBe('key1'); - expect(element.attributes.key1.value).toBe('value1'); - expect(element.attributes.key1.namespaceURI).toBe(null); - expect(element.attributes.key1.specified).toBe(true); - expect(element.attributes.key1.ownerElement === element).toBe(true); - expect(element.attributes.key1.ownerDocument === document).toBe(true); + expect(element.attributes['key1'].name).toBe('key1'); + expect(element.attributes['key1'].value).toBe('value1'); + expect(element.attributes['key1'].namespaceURI).toBe(null); + expect(element.attributes['key1'].specified).toBe(true); + expect(element.attributes['key1'].ownerElement === element).toBe(true); + expect(element.attributes['key1'].ownerDocument === document).toBe(true); - expect(element.attributes.key2.name).toBe('key2'); - expect(element.attributes.key2.value).toBe(''); - expect(element.attributes.key2.namespaceURI).toBe(null); - expect(element.attributes.key2.specified).toBe(true); - expect(element.attributes.key2.ownerElement === element).toBe(true); - expect(element.attributes.key2.ownerDocument === document).toBe(true); + expect(element.attributes['key2'].name).toBe('key2'); + expect(element.attributes['key2'].value).toBe(''); + expect(element.attributes['key2'].namespaceURI).toBe(null); + expect(element.attributes['key2'].specified).toBe(true); + expect(element.attributes['key2'].ownerElement === element).toBe(true); + expect(element.attributes['key2'].ownerDocument === document).toBe(true); }); }); @@ -1345,19 +1345,19 @@ describe('Element', () => { expect((element.attributes[1]).ownerElement).toBe(element); expect((element.attributes[1]).ownerDocument).toBe(document); - expect((element.attributes.key1).name).toBe('key1'); - expect((element.attributes.key1).namespaceURI).toBe(NamespaceURI.svg); - expect((element.attributes.key1).value).toBe('value1'); - expect((element.attributes.key1).specified).toBe(true); - expect((element.attributes.key1).ownerElement).toBe(element); - expect((element.attributes.key1).ownerDocument).toBe(document); - - expect((element.attributes.key2).name).toBe('key2'); - expect((element.attributes.key2).namespaceURI).toBe(null); - expect((element.attributes.key2).value).toBe('value2'); - expect((element.attributes.key2).specified).toBe(true); - expect((element.attributes.key2).ownerElement).toBe(element); - expect((element.attributes.key2).ownerDocument).toBe(document); + expect((element.attributes['key1']).name).toBe('key1'); + expect((element.attributes['key1']).namespaceURI).toBe(NamespaceURI.svg); + expect((element.attributes['key1']).value).toBe('value1'); + expect((element.attributes['key1']).specified).toBe(true); + expect((element.attributes['key1']).ownerElement).toBe(element); + expect((element.attributes['key1']).ownerDocument).toBe(document); + + expect((element.attributes['key2']).name).toBe('key2'); + expect((element.attributes['key2']).namespaceURI).toBe(null); + expect((element.attributes['key2']).value).toBe('value2'); + expect((element.attributes['key2']).specified).toBe(true); + expect((element.attributes['key2']).ownerElement).toBe(element); + expect((element.attributes['key2']).ownerDocument).toBe(document); }); it('Sets an Attr node on an element.', () => { @@ -1387,19 +1387,19 @@ describe('Element', () => { expect((svg.attributes[1]).ownerElement).toBe(svg); expect((svg.attributes[1]).ownerDocument).toBe(document); - expect((svg.attributes.KEY1).name).toBe('KEY1'); - expect((svg.attributes.KEY1).namespaceURI).toBe(NamespaceURI.svg); - expect((svg.attributes.KEY1).value).toBe('value1'); - expect((svg.attributes.KEY1).specified).toBe(true); - expect((svg.attributes.KEY1).ownerElement).toBe(svg); - expect((svg.attributes.KEY1).ownerDocument).toBe(document); - - expect((svg.attributes.key2).name).toBe('key2'); - expect((svg.attributes.key2).namespaceURI).toBe(null); - expect((svg.attributes.key2).value).toBe('value2'); - expect((svg.attributes.key2).specified).toBe(true); - expect((svg.attributes.key2).ownerElement).toBe(svg); - expect((svg.attributes.key2).ownerDocument).toBe(document); + expect((svg.attributes['KEY1']).name).toBe('KEY1'); + expect((svg.attributes['KEY1']).namespaceURI).toBe(NamespaceURI.svg); + expect((svg.attributes['KEY1']).value).toBe('value1'); + expect((svg.attributes['KEY1']).specified).toBe(true); + expect((svg.attributes['KEY1']).ownerElement).toBe(svg); + expect((svg.attributes['KEY1']).ownerDocument).toBe(document); + + expect((svg.attributes['key2']).name).toBe('key2'); + expect((svg.attributes['key2']).namespaceURI).toBe(null); + expect((svg.attributes['key2']).value).toBe('value2'); + expect((svg.attributes['key2']).specified).toBe(true); + expect((svg.attributes['key2']).ownerElement).toBe(svg); + expect((svg.attributes['key2']).ownerDocument).toBe(document); }); }); } diff --git a/packages/happy-dom/test/xml-parser/XMLParser.test.ts b/packages/happy-dom/test/xml-parser/XMLParser.test.ts index 17d213807..e1448b650 100644 --- a/packages/happy-dom/test/xml-parser/XMLParser.test.ts +++ b/packages/happy-dom/test/xml-parser/XMLParser.test.ts @@ -80,25 +80,25 @@ describe('XMLParser', () => { true ); - expect((root.childNodes[0]).attributes.class.name).toBe('class'); - expect((root.childNodes[0]).attributes.class.value).toBe('class1 class2'); - expect((root.childNodes[0]).attributes.class.namespaceURI).toBe(null); - expect((root.childNodes[0]).attributes.class.specified).toBe(true); + expect((root.childNodes[0]).attributes['class'].name).toBe('class'); + expect((root.childNodes[0]).attributes['class'].value).toBe('class1 class2'); + expect((root.childNodes[0]).attributes['class'].namespaceURI).toBe(null); + expect((root.childNodes[0]).attributes['class'].specified).toBe(true); expect( - (root.childNodes[0]).attributes.class.ownerElement === root.childNodes[0] + (root.childNodes[0]).attributes['class'].ownerElement === root.childNodes[0] + ).toBe(true); + expect( + (root.childNodes[0]).attributes['class'].ownerDocument === document ).toBe(true); - expect((root.childNodes[0]).attributes.class.ownerDocument === document).toBe( - true - ); - expect((root.childNodes[0]).attributes.id.name).toBe('id'); - expect((root.childNodes[0]).attributes.id.value).toBe('id'); - expect((root.childNodes[0]).attributes.id.namespaceURI).toBe(null); - expect((root.childNodes[0]).attributes.id.specified).toBe(true); + expect((root.childNodes[0]).attributes['id'].name).toBe('id'); + expect((root.childNodes[0]).attributes['id'].value).toBe('id'); + expect((root.childNodes[0]).attributes['id'].namespaceURI).toBe(null); + expect((root.childNodes[0]).attributes['id'].specified).toBe(true); expect( - (root.childNodes[0]).attributes.id.ownerElement === root.childNodes[0] + (root.childNodes[0]).attributes['id'].ownerElement === root.childNodes[0] ).toBe(true); - expect((root.childNodes[0]).attributes.id.ownerDocument === document).toBe( + expect((root.childNodes[0]).attributes['id'].ownerDocument === document).toBe( true ); @@ -295,33 +295,33 @@ describe('XMLParser', () => { expect(svg.attributes[3].ownerElement === svg).toBe(true); expect(svg.attributes[3].ownerDocument === document).toBe(true); - expect(svg.attributes.viewBox.name).toBe('viewBox'); - expect(svg.attributes.viewBox.value).toBe('0 0 300 100'); - expect(svg.attributes.viewBox.namespaceURI).toBe(null); - expect(svg.attributes.viewBox.specified).toBe(true); - expect(svg.attributes.viewBox.ownerElement === svg).toBe(true); - expect(svg.attributes.viewBox.ownerDocument === document).toBe(true); - - expect(svg.attributes.stroke.name).toBe('stroke'); - expect(svg.attributes.stroke.value).toBe('red'); - expect(svg.attributes.stroke.namespaceURI).toBe(null); - expect(svg.attributes.stroke.specified).toBe(true); - expect(svg.attributes.stroke.ownerElement === svg).toBe(true); - expect(svg.attributes.stroke.ownerDocument === document).toBe(true); - - expect(svg.attributes.fill.name).toBe('fill'); - expect(svg.attributes.fill.value).toBe('grey'); - expect(svg.attributes.fill.namespaceURI).toBe(null); - expect(svg.attributes.fill.specified).toBe(true); - expect(svg.attributes.fill.ownerElement === svg).toBe(true); - expect(svg.attributes.fill.ownerDocument === document).toBe(true); - - expect(svg.attributes.xmlns.name).toBe('xmlns'); - expect(svg.attributes.xmlns.value).toBe(NamespaceURI.html); - expect(svg.attributes.xmlns.namespaceURI).toBe(NamespaceURI.html); - expect(svg.attributes.xmlns.specified).toBe(true); - expect(svg.attributes.xmlns.ownerElement === svg).toBe(true); - expect(svg.attributes.xmlns.ownerDocument === document).toBe(true); + expect(svg.attributes['viewBox'].name).toBe('viewBox'); + expect(svg.attributes['viewBox'].value).toBe('0 0 300 100'); + expect(svg.attributes['viewBox'].namespaceURI).toBe(null); + expect(svg.attributes['viewBox'].specified).toBe(true); + expect(svg.attributes['viewBox'].ownerElement === svg).toBe(true); + expect(svg.attributes['viewBox'].ownerDocument === document).toBe(true); + + expect(svg.attributes['stroke'].name).toBe('stroke'); + expect(svg.attributes['stroke'].value).toBe('red'); + expect(svg.attributes['stroke'].namespaceURI).toBe(null); + expect(svg.attributes['stroke'].specified).toBe(true); + expect(svg.attributes['stroke'].ownerElement === svg).toBe(true); + expect(svg.attributes['stroke'].ownerDocument === document).toBe(true); + + expect(svg.attributes['fill'].name).toBe('fill'); + expect(svg.attributes['fill'].value).toBe('grey'); + expect(svg.attributes['fill'].namespaceURI).toBe(null); + expect(svg.attributes['fill'].specified).toBe(true); + expect(svg.attributes['fill'].ownerElement === svg).toBe(true); + expect(svg.attributes['fill'].ownerDocument === document).toBe(true); + + expect(svg.attributes['xmlns'].name).toBe('xmlns'); + expect(svg.attributes['xmlns'].value).toBe(NamespaceURI.html); + expect(svg.attributes['xmlns'].namespaceURI).toBe(NamespaceURI.html); + expect(svg.attributes['xmlns'].specified).toBe(true); + expect(svg.attributes['xmlns'].ownerElement === svg).toBe(true); + expect(svg.attributes['xmlns'].ownerDocument === document).toBe(true); expect(new XMLSerializer().serializeToString(root).replace(/[\s]/gm, '')).toBe( ` From 0e5862e019328befabe05a66d533a974685c2f1e Mon Sep 17 00:00:00 2001 From: David Ortner Date: Tue, 18 Oct 2022 23:38:59 +0200 Subject: [PATCH 4/4] #308@trivial: Fixes failing unit test. --- packages/happy-dom/test/named-node-map/NamedNodeMap.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/happy-dom/test/named-node-map/NamedNodeMap.test.ts b/packages/happy-dom/test/named-node-map/NamedNodeMap.test.ts index 9b1214a66..fefa0a226 100644 --- a/packages/happy-dom/test/named-node-map/NamedNodeMap.test.ts +++ b/packages/happy-dom/test/named-node-map/NamedNodeMap.test.ts @@ -102,7 +102,7 @@ describe('NamedNodeMap', () => { it('Replaces an attribute when existing.', () => { element.setAttribute('key', 'value1'); - const attr = attributes.getNamedItem('key'); + const attr = document.createAttributeNS('namespace', 'key'); attr.value = 'value2'; const replaced = attributes.setNamedItem(attr); @@ -127,7 +127,7 @@ describe('NamedNodeMap', () => { it('Replaces an attribute when existing.', () => { element.setAttributeNS('namespace', 'key', 'value1'); - const attr = attributes.getNamedItemNS('namespace', 'key'); + const attr = document.createAttributeNS('namespace', 'key'); attr.value = 'value2'; const replaced = attributes.setNamedItemNS(attr);