Skip to content

Commit

Permalink
Merge pull request #373 from material-components/radio-checked-bug
Browse files Browse the repository at this point in the history
Fix broken checked property in <mwc-radio>
  • Loading branch information
aomarks committed Aug 15, 2019
2 parents 2a274b8 + dbb4fcf commit 40f5e43
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 12 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Improve README for `<mwc-button>`
- Improve README for `<mwc-icon-button>`
- Split toggling icon button out into `@material/mwc-icon-button-toggle` with tag name `<mwc-icon-button-toggle>`
- Fix bug where setting the `checked` property on an `<mwc-radio>` did not
result in the other radios in the group becoming unchecked.

## [0.6.0] - 2019-06-05
- Upgrade lerna to 3.x
Expand Down
48 changes: 43 additions & 5 deletions packages/radio/src/mwc-radio-base.ts
Expand Up @@ -24,11 +24,46 @@ export class RadioBase extends FormElement {

@query('input') protected formElement!: HTMLInputElement;

@property({type: Boolean})
@observer(function(this: RadioBase, checked: boolean) {
this.formElement.checked = checked;
})
checked = false;
private _checked = false;

@property({type: Boolean, reflect: true})
get checked() {
return this._checked;
}

/**
* We define our own getter/setter for `checked` because we need to track
* changes to it synchronously.
*
* The order in which the `checked` property is set across radio buttons
* within the same group is very important. However, we can't rely on
* UpdatingElement's `updated` callback to observe these changes (which is
* also what the `@observer` decorator uses), because it batches changes to
* all properties.
*
* Consider:
*
* radio1.disabled = true;
* radio2.checked = true;
* radio1.checked = true;
*
* In this case we'd first see all changes for radio1, and then for radio2,
* and we couldn't tell that radio1 was the most recently checked.
*/
set checked(checked: boolean) {
const oldValue = this._checked;
if (!!checked === !!oldValue) {
return;
}
this._checked = checked;
if (this.formElement) {
this.formElement.checked = checked;
}
if (this._selectionController) {
this._selectionController.update(this);
}
this.requestUpdate('checked', oldValue);
}

@property({type: Boolean})
@observer(function(this: RadioBase, disabled: boolean) {
Expand Down Expand Up @@ -127,6 +162,9 @@ export class RadioBase extends FormElement {

firstUpdated() {
super.firstUpdated();
// We might not have been able to synchronize this from the checked setter
// earlier, if checked was set before the input was stamped.
this.formElement.checked = this.checked;
if (this._selectionController) {
this._selectionController.update(this);
}
Expand Down
57 changes: 50 additions & 7 deletions test/unit/mwc-radio.test.js
Expand Up @@ -14,22 +14,65 @@
* limitations under the License.
*/

import {assert} from 'chai';
import {Radio} from '@material/mwc-radio';
import {assert} from 'chai';
import {html, render} from 'lit-html';

let element;
let container;

suite('mwc-radio');

beforeEach(() => {
element = document.createElement('mwc-radio');
document.body.appendChild(element);
before(() => {
container = document.createElement('main');
document.body.appendChild(container);
});

afterEach(() => {
document.body.removeChild(element);
render(html``, container);
});

after(() => {
document.body.removeChild(container);
});

test('initializes as an mwc-radio', () => {
assert.instanceOf(element, Radio);
const radio = document.createElement('mwc-radio');
container.appendChild(radio);
assert.instanceOf(radio, Radio);
});

test('radio group', async () => {
render(
html`
<mwc-radio name="a"></mwc-group>
<mwc-radio name="a"></mwc-group>
<mwc-radio name="b"></mwc-group>
`,
container);

const [a1, a2, b1] = [...container.querySelectorAll('mwc-radio')];
assert.isFalse(a1.checked);
assert.isFalse(a2.checked);
assert.isFalse(b1.checked);

a1.checked = true;
assert.isTrue(a1.checked);
assert.isFalse(a2.checked);
assert.isFalse(b1.checked);

a2.checked = true;
assert.isFalse(a1.checked);
assert.isTrue(a2.checked);
assert.isFalse(b1.checked);

b1.checked = true;
assert.isFalse(a1.checked);
assert.isTrue(a2.checked);
assert.isTrue(b1.checked);

a2.checked = false;
b1.checked = false;
assert.isFalse(a1.checked);
assert.isFalse(a2.checked);
assert.isFalse(b1.checked);
});

0 comments on commit 40f5e43

Please sign in to comment.