Skip to content

Commit

Permalink
Merge pull request #638 from capricorn86/task/637-htmlselectelementse…
Browse files Browse the repository at this point in the history
…lectedindex-no-longer-cater-for-selected-attribute-in-options

#637@patch: Fixes problem with HTMLSelectElement.selectedIndex not re…
  • Loading branch information
capricorn86 committed Oct 25, 2022
2 parents 59a8ed7 + 8d02d8c commit e001788
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 96 deletions.
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
104 changes: 69 additions & 35 deletions packages/happy-dom/src/nodes/html-option-element/HTMLOptionElement.ts
@@ -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

0 comments on commit e001788

Please sign in to comment.