Skip to content

Commit

Permalink
#875@patch: Fixes issue in HTMLCollection and HTMLFormControlsCollect…
Browse files Browse the repository at this point in the history
…ion related to id/name of elements colliding with class properties and methods.
  • Loading branch information
capricorn86 committed May 2, 2023
1 parent f8618cd commit 6f4e470
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 5 deletions.
10 changes: 7 additions & 3 deletions packages/happy-dom/src/nodes/element/HTMLCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default class HTMLCollection<T> extends Array implements IHTMLCollection<
this._namedItems[name].push(node);
}

if (!this[name] && this._isValidPropertyName(name)) {
if (!this.hasOwnProperty(name) && this._isValidPropertyName(name)) {
this[name] = this._namedItems[name][0];
}
}
Expand All @@ -62,7 +62,7 @@ export default class HTMLCollection<T> extends Array implements IHTMLCollection<

if (this._namedItems[name].length === 0) {
delete this._namedItems[name];
if (this[name] && this._isValidPropertyName(name)) {
if (this.hasOwnProperty(name) && this._isValidPropertyName(name)) {
delete this[name];
}
} else if (this._isValidPropertyName(name)) {
Expand All @@ -79,6 +79,10 @@ export default class HTMLCollection<T> extends Array implements IHTMLCollection<
* @returns True if the property name is valid.
*/
protected _isValidPropertyName(name: string): boolean {
return isNaN(Number(name)) || name.includes('.');
return (
!this.constructor.prototype.hasOwnProperty(name) &&
!Array.prototype.hasOwnProperty(name) &&
(isNaN(Number(name)) || name.includes('.'))
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export default class HTMLFormControlsCollection

if (this._namedItems[name].length === 0) {
delete this._namedItems[name];
if (this[name] && this._isValidPropertyName(name)) {
if (this.hasOwnProperty(name) && this._isValidPropertyName(name)) {
delete this[name];
}
} else if (this._isValidPropertyName(name)) {
Expand All @@ -111,6 +111,10 @@ export default class HTMLFormControlsCollection
* @returns True if the property name is valid.
*/
protected _isValidPropertyName(name: string): boolean {
return isNaN(Number(name)) || name.includes('.');
return (
!this.constructor.prototype.hasOwnProperty(name) &&
!Array.prototype.hasOwnProperty(name) &&
(isNaN(Number(name)) || name.includes('.'))
);
}
}
40 changes: 40 additions & 0 deletions packages/happy-dom/test/nodes/element/HTMLCollection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,45 @@ describe('HTMLCollection', () => {
expect(div.children.namedItem('0') === container3).toBe(true);
expect(div.children.namedItem('1') === container4).toBe(true);
});

it('Supports attributes that has the same name as properties and methods of the HTMLCollection class.', () => {
const div = document.createElement('div');
div.innerHTML = `<div name="length" class="container1"></div><div name="namedItem" class="container2"></div><div name="push" class="container3"></div>`;
const container1 = div.querySelector('.container1');
const container2 = div.querySelector('.container2');
const container3 = div.querySelector('.container3');

expect(div.children.length).toBe(3);
expect(div.children[0] === container1).toBe(true);
expect(div.children[1] === container2).toBe(true);
expect(div.children[2] === container3).toBe(true);
expect(div.children.namedItem('length') === container1).toBe(true);
expect(div.children.namedItem('namedItem') === container2).toBe(true);
expect(div.children.namedItem('push') === container3).toBe(true);

expect(typeof div.children['namedItem']).toBe('function');
expect(typeof div.children['push']).toBe('function');

container2.remove();

expect(div.children.length).toBe(2);
expect(div.children[0] === container1).toBe(true);
expect(div.children[1] === container3).toBe(true);
expect(div.children.namedItem('length') === container1).toBe(true);
expect(div.children.namedItem('push') === container3).toBe(true);

div.insertBefore(container2, container3);

expect(div.children.length).toBe(3);
expect(div.children[0] === container1).toBe(true);
expect(div.children[1] === container2).toBe(true);
expect(div.children[2] === container3).toBe(true);
expect(div.children.namedItem('length') === container1).toBe(true);
expect(div.children.namedItem('namedItem') === container2).toBe(true);
expect(div.children.namedItem('push') === container3).toBe(true);

expect(typeof div.children['namedItem']).toBe('function');
expect(typeof div.children['push']).toBe('function');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,79 @@ describe('HTMLFormElement', () => {
expect(elements['checkbox1'] === undefined).toBe(true);
expect(elements['radio1'] === undefined).toBe(true);
});

it('Returns control elements using the same name as properties and methods of the HTMLCollection class.', () => {
element.innerHTML = `
<div>
<input type="text" name="length" value="value1">
<input type="checkbox" name="namedItem" value="value1">
<input type="checkbox" name="namedItem" value="value2" checked>
<input type="checkbox" name="namedItem" value="value3">
<input type="hidden" name="push" value="value1">
</div>
`;
const elements = element.elements;
const root = element.children[0];

expect(element.length).toBe(5);
expect(elements.length).toBe(5);

expect(element[0] === root.children[0]).toBe(true);
expect(element[1] === root.children[1]).toBe(true);
expect(element[2] === root.children[2]).toBe(true);
expect(element[3] === root.children[3]).toBe(true);
expect(element[4] === root.children[4]).toBe(true);

expect(elements[0] === root.children[0]).toBe(true);
expect(elements[1] === root.children[1]).toBe(true);
expect(elements[2] === root.children[2]).toBe(true);
expect(elements[3] === root.children[3]).toBe(true);
expect(elements[4] === root.children[4]).toBe(true);

expect(elements.item(0) === root.children[0]).toBe(true);
expect(elements.item(1) === root.children[1]).toBe(true);
expect(elements.item(2) === root.children[2]).toBe(true);
expect(elements.item(3) === root.children[3]).toBe(true);
expect(elements.item(4) === root.children[4]).toBe(true);

const radioNodeList = new RadioNodeList();
radioNodeList.push(root.children[1]);
radioNodeList.push(root.children[2]);
radioNodeList.push(root.children[3]);

expect(typeof elements.push).toBe('function');
expect(typeof elements.namedItem).toBe('function');
expect(elements.namedItem('length') === root.children[0]).toBe(true);
expect(elements.namedItem('namedItem')).toEqual(radioNodeList);
expect(elements.namedItem('push') === root.children[4]).toBe(true);

const children = root.children.slice();

for (const child of children) {
root.removeChild(child);
}

expect(element.length).toBe(0);

for (const child of children) {
root.appendChild(child);
}

expect(element.length).toBe(5);
expect(elements.length).toBe(5);

expect(elements[0] === root.children[0]).toBe(true);
expect(elements[1] === root.children[1]).toBe(true);
expect(elements[2] === root.children[2]).toBe(true);
expect(elements[3] === root.children[3]).toBe(true);
expect(elements[4] === root.children[4]).toBe(true);

expect(typeof elements.push).toBe('function');
expect(typeof elements.namedItem).toBe('function');
expect(elements.namedItem('length') === root.children[0]).toBe(true);
expect(elements.namedItem('namedItem')).toEqual(radioNodeList);
expect(elements.namedItem('push') === root.children[4]).toBe(true);
});
});

describe('submit()', () => {
Expand Down

0 comments on commit 6f4e470

Please sign in to comment.