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

fix: [#1439] HTMLInputElement's Indeterminate property doesn't behave properly #1475

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/happy-dom/src/PropertySymbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const getAttributeName = Symbol('getAttributeName');
export const happyDOMSettingsID = Symbol('happyDOMSettingsID');
export const height = Symbol('height');
export const immediatePropagationStopped = Symbol('immediatePropagationStopped');
export const indeterminate = Symbol('indeterminate');
export const isFirstWrite = Symbol('isFirstWrite');
export const isFirstWriteAfterOpen = Symbol('isFirstWriteAfterOpen');
export const isInPassiveEventListener = Symbol('isInPassiveEventListener');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export default class HTMLInputElement extends HTMLElement {
public [PropertySymbol.validationMessage] = '';
public [PropertySymbol.validity] = new ValidityState(this);
public [PropertySymbol.files]: FileList = new FileList();
public [PropertySymbol.indeterminate]: boolean = false;

// Private properties
#selectionStart: number = null;
Expand Down Expand Up @@ -685,7 +686,7 @@ export default class HTMLInputElement extends HTMLElement {
* @returns Indeterminate.
*/
public get indeterminate(): boolean {
return this.getAttribute('indeterminate') !== null;
return this[PropertySymbol.indeterminate];
}

/**
Expand All @@ -694,11 +695,7 @@ export default class HTMLInputElement extends HTMLElement {
* @param indeterminate Indeterminate.
*/
public set indeterminate(indeterminate: boolean) {
if (!indeterminate) {
this.removeAttribute('indeterminate');
} else {
this.setAttribute('indeterminate', '');
}
this[PropertySymbol.indeterminate] = Boolean(indeterminate);
}

/**
Expand Down Expand Up @@ -1313,6 +1310,7 @@ export default class HTMLInputElement extends HTMLElement {
}

let previousCheckedValue: boolean | null = null;
const previousIndeterminateValue: boolean = this[PropertySymbol.indeterminate];

// The checkbox or radio button has to be checked before the click event is dispatched, so that event listeners can check the checked value.
// However, the value has to be restored if preventDefault() is called on the click event.
Expand All @@ -1326,6 +1324,9 @@ export default class HTMLInputElement extends HTMLElement {
if (inputType === 'checkbox' || inputType === 'radio') {
previousCheckedValue = this.checked;
this.#setChecked(inputType === 'checkbox' ? !previousCheckedValue : true);
if (inputType === 'checkbox') {
this[PropertySymbol.indeterminate] = false;
}
}
}

Expand Down Expand Up @@ -1370,6 +1371,7 @@ export default class HTMLInputElement extends HTMLElement {
const inputType = this.type;
if (inputType === 'checkbox' || inputType === 'radio') {
this.#setChecked(previousCheckedValue);
this[PropertySymbol.indeterminate] = previousIndeterminateValue;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -645,14 +645,7 @@ describe('HTMLInputElement', () => {
});
});

for (const property of [
'disabled',
'autofocus',
'required',
'indeterminate',
'multiple',
'readOnly'
]) {
for (const property of ['disabled', 'autofocus', 'required', 'multiple', 'readOnly']) {
describe(`get ${property}()`, () => {
it('Returns attribute value.', () => {
expect(element[property]).toBe(false);
Expand Down Expand Up @@ -836,6 +829,23 @@ describe('HTMLInputElement', () => {
});
});

describe('get indeterminate()', () => {
it('Returns indeterminate value.', () => {
element.type = 'checkbox';
expect(element.indeterminate).toBe(false);
expect(element.hasAttribute('indeterminate')).toBe(false);
});
});

describe('set indeterminate()', () => {
it('Sets indeterminate value.', () => {
element.type = 'checkbox';
element.indeterminate = true;
expect(element.indeterminate).toBe(true);
expect(element.hasAttribute('indeterminate')).toBe(false);
});
});

describe('get size()', () => {
it('Returns attribute value.', () => {
expect(element.size).toBe(20);
Expand Down Expand Up @@ -1171,6 +1181,26 @@ describe('HTMLInputElement', () => {
expect(element.checked).toBe(true);
});

it('Switch "checked" to "true" or "false" and "indeterminate" to "false" if type is "checkbox" and "indeterminate" is "true" and is a "click" event.', () => {
element.type = 'checkbox';
element.indeterminate = true;

// "input" and "change" events should only be triggered if connected to DOM
document.body.appendChild(element);

element.dispatchEvent(new MouseEvent('click'));

expect(element.checked).toBe(true);
expect(element.indeterminate).toBe(false);

element.indeterminate = true;

element.dispatchEvent(new MouseEvent('click'));

expect(element.checked).toBe(false);
expect(element.indeterminate).toBe(false);
});

it('Sets "checked" to "true" if type is "radio" and is a "click" event.', () => {
let isInputTriggered = false;
let isChangeTriggered = false;
Expand Down
Loading