diff --git a/anypoint-radio-group.js b/anypoint-radio-group.js index dfa21cd8..35a67802 100644 --- a/anypoint-radio-group.js +++ b/anypoint-radio-group.js @@ -56,7 +56,7 @@ class AnypointRadioGroup extends HTMLElement { connectedCallback() { this.style.display = 'inline-block'; this.style.verticalAlign = 'middle'; - + this.setAttribute('role', 'radiogroup'); const config = { attributes: true, childList: true, @@ -75,20 +75,21 @@ class AnypointRadioGroup extends HTMLElement { /** * Processes mutations to the light DOM of this element. * Processes added and removed nodes and changes to attributes. - * @param {Array} changes List of changes discovered by + * @param {Array} mutationsList List of changes discovered by * `MutationObserver` */ - _nodesChanged(changes) { - const record = changes[0]; - switch (record.type) { - case 'attributes': - this._processNodeAttributeChange(record); - break; - case 'childList': - this._processAddedNodes(record.addedNodes); - this._processRemovedNodes(record.removedNodes); - this._manageNodesSelection(record.addedNodes); - break; + _nodesChanged(mutationsList) { + for (const mutation of mutationsList) { + switch (mutation.type) { + case 'attributes': + this._processNodeAttributeChange(mutation); + break; + case 'childList': + this._processAddedNodes(mutation.addedNodes); + this._processRemovedNodes(mutation.removedNodes); + this._manageNodesSelection(mutation.addedNodes); + break; + } } } /** @@ -257,7 +258,7 @@ class AnypointRadioGroup extends HTMLElement { const nodes = this.elements; for (let i = 0, len = nodes.length; i < len; i++) { const node = nodes[i]; - if (node.name === name && node !== target && node.checked) { + if (node.name !== name && node !== target && node.checked) { node.checked = false; } } diff --git a/test/anypoint-radio-button.test.js b/test/anypoint-radio-button.test.js index a6e0ec7b..0d645f22 100644 --- a/test/anypoint-radio-button.test.js +++ b/test/anypoint-radio-button.test.js @@ -1,9 +1,7 @@ import { fixture, assert, aTimeout } from '@open-wc/testing'; import sinon from 'sinon/pkg/sinon-esm.js'; import '../anypoint-radio-button.js'; -import '@polymer/iron-test-helpers/mock-interactions.js'; - -/* global MockInteractions */ +import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions.js'; describe('', function() { async function basicFixture() { diff --git a/test/anypoint-radio-group.test.js b/test/anypoint-radio-group.test.js new file mode 100644 index 00000000..692b156b --- /dev/null +++ b/test/anypoint-radio-group.test.js @@ -0,0 +1,174 @@ +import { fixture, assert, nextFrame } from '@open-wc/testing'; +import '../anypoint-radio-button.js'; +import '../anypoint-radio-group.js'; +import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions.js'; + +describe('', function() { + async function basicFixture() { + return (await fixture(` + Apple + Banana + Orange + `)); + } + + async function selectedFixture() { + return (await fixture(` + Apple + Banana + Orange + `)); + } + + async function ignoredFixture() { + return (await fixture(` + Apple + Banana + Orange +
Strawberry
+
`)); + } + + async function mixedFixture() { + return (await fixture(` + Apple + Banana + Orange + + `)); + } + + describe('Selection states', () => { + it('sets selected property when selection changes', async () => { + const element = await basicFixture(); + const node = element.querySelector('anypoint-radio-button'); + MockInteractions.tap(node); + assert.equal(element.selected, node); + }); + + it('deselects old node', async () => { + const element = await selectedFixture(); + const oldSelected = element.querySelector('anypoint-radio-button[name="a"]'); + const node = element.querySelector('anypoint-radio-button[name="c"]'); + MockInteractions.tap(node); + assert.isFalse(oldSelected.checked); + }); + + it('selects new node', async () => { + const element = await selectedFixture(); + const node = element.querySelector('anypoint-radio-button[name="c"]'); + MockInteractions.tap(node); + assert.isTrue(node.checked); + }); + + it('selected property changes after selection change', async () => { + const element = await selectedFixture(); + const node = element.querySelector('anypoint-radio-button[name="c"]'); + MockInteractions.tap(node); + assert.equal(element.selected, node); + }); + + it('accespts dynamic node', async () => { + const element = await selectedFixture(); + const node = document.createElement('anypoint-radio-button'); + node.name = 'd'; + node.innerText = 'Dino'; + element.appendChild(node); + await nextFrame(); + MockInteractions.tap(node); + assert.equal(element.selected, node); + }); + + it('ignores nodes that are not role radio', async () => { + const element = await ignoredFixture(); + const node = element.querySelector('div[name="d"]'); + MockInteractions.tap(node); + assert.isUndefined(element.selected); + }); + + it('ignores removed nodes', async () => { + const element = await basicFixture(); + const node = element.querySelector('anypoint-radio-button[name="a"]'); + element.removeChild(node); + await nextFrame(); + MockInteractions.tap(node); + assert.isUndefined(element.selected); + }); + + it('ignores nodes with changed role', async () => { + const element = await basicFixture(); + const node = element.querySelector('anypoint-radio-button[name="a"]'); + node.setAttribute('role', 'input'); + await nextFrame(); + MockInteractions.tap(node); + assert.isUndefined(element.selected); + }); + + it('removes selection when removing selected node', async () => { + const element = await selectedFixture(); + const node = element.querySelector('anypoint-radio-button[name="a"]'); + element.removeChild(node); + await nextFrame(); + assert.isUndefined(element.selected); + }); + + it('accepts input radio', async () => { + const element = await mixedFixture(); + const node = element.querySelector('input[name="d"]'); + MockInteractions.tap(node); + assert.equal(element.selected, node); + }); + }); + + describe('_isRadioButton()', () => { + let element; + beforeEach(async () => { + element = await basicFixture(); + }); + + it('return true when has radio role', () => { + const node = document.createElement('span'); + node.setAttribute('role', 'radio'); + const result = element._isRadioButton(node); + assert.isTrue(result); + }); + + it('return true for radio input', () => { + const node = document.createElement('input'); + node.type = 'radio'; + const result = element._isRadioButton(node); + assert.isTrue(result); + }); + + it('resturns false for other roles', () => { + const node = document.createElement('span'); + node.setAttribute('role', 'display'); + const result = element._isRadioButton(node); + assert.isFalse(result); + }); + + it('resturns false for other inputs', () => { + const node = document.createElement('input'); + node.type = 'checkbox'; + const result = element._isRadioButton(node); + assert.isFalse(result); + }); + }); + + describe('a11y', () => { + it('has role', async () => { + const element = await basicFixture(); + assert.equal(element.getAttribute('role'), 'radiogroup'); + }); + + it('is accessible when no selection', async () => { + const element = await basicFixture(); + await assert.isAccessible(element); + }); + + it('is accessible when selected', async () => { + const element = await selectedFixture(); + await assert.isAccessible(element); + }); + }); +});