Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#637@patch: Fixes problem with HTMLSelectElement.selectedIndex not re… #638

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 15 additions & 3 deletions packages/happy-dom/src/nodes/element/Element.ts
Expand Up @@ -919,8 +919,17 @@ export default class Element extends Node implements IElement {
* Removes an Attr node.
*
* @param attribute Attribute.
* @returns Removed attribute.
*/
public removeAttributeNode(attribute: IAttr): void {
public removeAttributeNode(attribute: IAttr): IAttr {
const removedAttribute = this._attributes[attribute.name];

if (removedAttribute !== attribute) {
throw new DOMException(
`Failed to execute 'removeAttributeNode' on 'Element': The node provided is owned by another element.`
);
}

delete this._attributes[attribute.name];

if (this.isConnected) {
Expand Down Expand Up @@ -954,15 +963,18 @@ export default class Element extends Node implements IElement {
}
}
}

return attribute;
}

/**
* Removes an Attr node.
*
* @param attribute Attribute.
* @returns Removed attribute.
*/
public removeAttributeNodeNS(attribute: IAttr): void {
this.removeAttributeNode(attribute);
public removeAttributeNodeNS(attribute: IAttr): IAttr {
return this.removeAttributeNode(attribute);
}

/**
Expand Down
6 changes: 4 additions & 2 deletions packages/happy-dom/src/nodes/element/IElement.ts
Expand Up @@ -260,15 +260,17 @@ export default interface IElement extends IChildNode, INonDocumentTypeChildNode,
* Removes an Attr node.
*
* @param attribute Attribute.
* @returns Removed attribute.
*/
removeAttributeNode(attribute: IAttr): void;
removeAttributeNode(attribute: IAttr): IAttr;

/**
* Removes an Attr node.
*
* @param attribute Attribute.
* @returns Removed attribute.
*/
removeAttributeNodeNS(attribute: IAttr): void;
removeAttributeNodeNS(attribute: IAttr): IAttr;

/**
* Clones a node.
Expand Down
15 changes: 3 additions & 12 deletions packages/happy-dom/src/nodes/html-element/HTMLElement.ts
Expand Up @@ -421,11 +421,7 @@ export default class HTMLElement extends Element implements IHTMLElement {
}

/**
* The setAttributeNode() method adds a new Attr node to the specified element.
*
* @override
* @param attribute Attribute.
* @returns Replaced attribute.
*/
public setAttributeNode(attribute: IAttr): IAttr {
const replacedAttribute = super.setAttributeNode(attribute);
Expand All @@ -438,25 +434,20 @@ export default class HTMLElement extends Element implements IHTMLElement {
}

/**
* Removes an Attr node.
*
* @override
* @param attribute Attribute.
*/
public removeAttributeNode(attribute: IAttr): void {
public removeAttributeNode(attribute: IAttr): IAttr {
super.removeAttributeNode(attribute);

if (attribute.name === 'style' && this._style) {
this._style.cssText = '';
}

return attribute;
}

/**
* Clones a node.
*
* @override
* @param [deep=false] "true" to clone deep.
* @returns Cloned node.
*/
public cloneNode(deep = false): IHTMLElement {
const clone = <HTMLElement>super.cloneNode(deep);
Expand Down
@@ -1,7 +1,8 @@
import IAttr from '../attr/IAttr';
import HTMLElement from '../html-element/HTMLElement';
import IHTMLElement from '../html-element/IHTMLElement';
import IHTMLFormElement from '../html-form-element/IHTMLFormElement';
import IHTMLSelectElement from '../html-select-element/IHTMLSelectElement';
import HTMLSelectElement from '../html-select-element/HTMLSelectElement';
import IHTMLOptionElement from './IHTMLOptionElement';

/**
Expand All @@ -12,6 +13,8 @@ import IHTMLOptionElement from './IHTMLOptionElement';
*/
export default class HTMLOptionElement extends HTMLElement implements IHTMLOptionElement {
public _index: number;
public _selectedness = false;
public _dirtyness = false;

/**
* Returns inner text, which is the rendered appearance of text.
Expand Down Expand Up @@ -59,20 +62,7 @@ export default class HTMLOptionElement extends HTMLElement implements IHTMLOptio
* @returns Selected.
*/
public get selected(): boolean {
const parentNode = <IHTMLSelectElement>this.parentNode;

if (parentNode?.tagName === 'SELECT') {
let index = -1;
for (let i = 0; i < parentNode.options.length; i++) {
if (parentNode.options[i] === this) {
index = i;
break;
}
}
return index !== -1 && parentNode.options.selectedIndex === index;
}

return false;
return this._selectedness;
}

/**
Expand All @@ -81,26 +71,13 @@ export default class HTMLOptionElement extends HTMLElement implements IHTMLOptio
* @param selected Selected.
*/
public set selected(selected: boolean) {
const parentNode = <IHTMLSelectElement>this.parentNode;
if (parentNode?.tagName === 'SELECT') {
if (selected) {
let index = -1;

for (let i = 0; i < parentNode.options.length; i++) {
if (parentNode.options[i] === this) {
index = i;
break;
}
}

if (index !== -1) {
parentNode.options.selectedIndex = index;
}
} else if (parentNode.options.length) {
parentNode.options.selectedIndex = 0;
} else {
parentNode.options.selectedIndex = -1;
}
const selectElement = this._getSelectElement();

this._dirtyness = true;
this._selectedness = Boolean(selected);

if (selectElement) {
selectElement._resetOptionSelectednes(this._selectedness ? this : null);
}
}

Expand Down Expand Up @@ -143,4 +120,61 @@ export default class HTMLOptionElement extends HTMLElement implements IHTMLOptio
public set value(value: string) {
this.setAttributeNS(null, 'value', value);
}

/**
* @override
*/
public setAttributeNode(attribute: IAttr): IAttr {
const replacedAttribute = super.setAttributeNode(attribute);

if (
!this._dirtyness &&
attribute.name === 'selected' &&
replacedAttribute?.value !== attribute.value
) {
const selectElement = this._getSelectElement();

this._selectedness = true;

if (selectElement) {
selectElement._resetOptionSelectednes(this);
}
}

return replacedAttribute;
}

/**
* @override
*/
public removeAttributeNode(attribute: IAttr): IAttr {
super.removeAttributeNode(attribute);

if (!this._dirtyness && attribute.name === 'selected') {
const selectElement = this._getSelectElement();

this._selectedness = false;

if (selectElement) {
selectElement._resetOptionSelectednes();
}
}

return attribute;
}

/**
* Returns select element.
*
* @returns Select element.
*/
private _getSelectElement(): HTMLSelectElement {
const parentNode = <HTMLSelectElement>this.parentNode;
if (parentNode?.tagName === 'SELECT') {
return <HTMLSelectElement>parentNode;
}
if ((<HTMLSelectElement>parentNode?.parentNode)?.tagName === 'SELECT') {
return <HTMLSelectElement>parentNode.parentNode;
}
}
}
Expand Up @@ -16,7 +16,6 @@ export default class HTMLOptionsCollection
implements IHTMLOptionsCollection
{
private _selectElement: IHTMLSelectElement;
private _selectedIndex = -1;

/**
*
Expand All @@ -34,7 +33,7 @@ export default class HTMLOptionsCollection
* @returns SelectedIndex.
*/
public get selectedIndex(): number {
return this._selectedIndex;
return this._selectElement.selectedIndex;
}

/**
Expand All @@ -43,13 +42,7 @@ export default class HTMLOptionsCollection
* @param selectedIndex SelectedIndex.
*/
public set selectedIndex(selectedIndex: number) {
if (typeof selectedIndex === 'number' && !isNaN(selectedIndex)) {
if (selectedIndex >= 0 && selectedIndex < this.length) {
this._selectedIndex = selectedIndex;
} else {
this._selectedIndex = -1;
}
}
this._selectElement.selectedIndex = selectedIndex;
}

/**
Expand Down