Skip to content

Commit

Permalink
capricorn86#308@trivial: Improves interface for NamedNodeMap and adds…
Browse files Browse the repository at this point in the history
… some more unit tests.
  • Loading branch information
daveed07 committed Oct 18, 2022
1 parent fbb7e74 commit dad18e4
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 137 deletions.
73 changes: 60 additions & 13 deletions 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<IAttr> {
[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<IAttr>;
type INamedNodeMapProperties = keyof INamedNodeMapProps;

type INamedNodeMap = INamedNodeMapProps & {
[k in Exclude<string | number, INamedNodeMapProperties>]: 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;
}
42 changes: 24 additions & 18 deletions packages/happy-dom/src/named-node-map/NamedNodeMap.ts
@@ -1,44 +1,46 @@
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.
*
* @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;
}

/**
* Length.
*
* @returns Length.
*/
public get length(): number {
return Object.keys(this._ownerElement._attributes).length;
return Object.keys(this.#ownerElement._attributes).length;
}

/**
Expand All @@ -50,27 +52,29 @@ 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;
}

/**
* 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);
}

/**
* Returns attribute by name and namespace.
*
* @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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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;
}
Expand All @@ -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<IAttr> {
let index = -1;
Expand Down
4 changes: 2 additions & 2 deletions packages/happy-dom/src/nodes/node/NodeUtility.ts
Expand Up @@ -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 = <Array<IAttr>>Object.values(elementA['_attributes']);
const listB = <Array<IAttr>>Object.values(elementB['_attributes']);

const lengthA = listA.length;
const lengthB = listB.length;
Expand Down
40 changes: 35 additions & 5 deletions packages/happy-dom/test/named-node-map/NamedNodeMap.test.ts
Expand Up @@ -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');

Expand Down Expand Up @@ -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');
});
Expand All @@ -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');
Expand All @@ -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);
});
Expand Down

0 comments on commit dad18e4

Please sign in to comment.