diff --git a/packages/popover/src/vaadin-popover.js b/packages/popover/src/vaadin-popover.js index a2efbae9f6d..abd62401e7d 100644 --- a/packages/popover/src/vaadin-popover.js +++ b/packages/popover/src/vaadin-popover.js @@ -409,8 +409,7 @@ class Popover extends PopoverPositionMixin( return [ '__updateContentHeight(contentHeight, _overlayElement)', '__updateContentWidth(contentWidth, _overlayElement)', - '__openedOrTargetChanged(opened, target)', - '__overlayRoleOrTargetChanged(overlayRole, target)', + '__updateAriaAttributes(opened, overlayRole, target)', ]; } @@ -578,27 +577,27 @@ class Popover extends PopoverPositionMixin( } /** @private */ - __openedOrTargetChanged(opened, target) { - if (target) { - target.setAttribute('aria-expanded', opened ? 'true' : 'false'); - - if (opened) { - target.setAttribute('aria-controls', this.__overlayId); - } else { - target.removeAttribute('aria-controls'); - } - } - } - - /** @private */ - __overlayRoleOrTargetChanged(overlayRole, target) { + __updateAriaAttributes(opened, overlayRole, target) { if (this.__oldTarget) { - this.__oldTarget.removeAttribute('aria-haspopup'); + const oldEffectiveTarget = this.__oldTarget.ariaTarget || this.__oldTarget; + oldEffectiveTarget.removeAttribute('aria-haspopup'); + oldEffectiveTarget.removeAttribute('aria-expanded'); + oldEffectiveTarget.removeAttribute('aria-controls'); } if (target) { + const effectiveTarget = target.ariaTarget || target; + const isDialog = overlayRole === 'dialog' || overlayRole === 'alertdialog'; - target.setAttribute('aria-haspopup', isDialog ? 'dialog' : 'true'); + effectiveTarget.setAttribute('aria-haspopup', isDialog ? 'dialog' : 'true'); + + effectiveTarget.setAttribute('aria-expanded', opened ? 'true' : 'false'); + + if (opened) { + effectiveTarget.setAttribute('aria-controls', this.__overlayId); + } else { + effectiveTarget.removeAttribute('aria-controls'); + } this.__oldTarget = target; } diff --git a/packages/popover/test/a11y.test.js b/packages/popover/test/a11y.test.js index b49b4facf31..023d2e0d6eb 100644 --- a/packages/popover/test/a11y.test.js +++ b/packages/popover/test/a11y.test.js @@ -49,57 +49,84 @@ describe('a11y', () => { expect(overlay.getAttribute('role')).to.equal('alertdialog'); }); - it('should set aria-haspopup attribute on the target', () => { - expect(target.getAttribute('aria-haspopup')).to.equal('dialog'); - }); - - it('should keep aria-haspopup attribute when overlayRole is set to alertdialog', async () => { - popover.overlayRole = 'alertdialog'; - await nextUpdate(popover); - expect(target.getAttribute('aria-haspopup')).to.equal('dialog'); - }); - - it('should update aria-haspopup attribute when overlayRole is set to different value', async () => { - popover.overlayRole = 'menu'; - await nextUpdate(popover); - expect(target.getAttribute('aria-haspopup')).to.equal('true'); - }); - - it('should remove aria-haspopup attribute when target is cleared', async () => { - popover.target = null; - await nextUpdate(popover); - expect(target.hasAttribute('aria-haspopup')).to.be.false; - }); - - it('should remove aria-controls attribute when target is cleared', async () => { - popover.target = null; - await nextUpdate(popover); - expect(target.hasAttribute('aria-haspopup')).to.be.false; - }); - - it('should set aria-expanded attribute on the target when closed', () => { - expect(target.getAttribute('aria-expanded')).to.equal('false'); - }); - - it('should set aria-expanded attribute on the target when opened', async () => { - popover.opened = true; - await nextRender(); - expect(target.getAttribute('aria-expanded')).to.equal('true'); - }); - - it('should set aria-controls attribute on the target when opened', async () => { - popover.opened = true; - await nextRender(); - expect(target.getAttribute('aria-controls')).to.equal(overlay.id); - }); - - it('should remove aria-controls attribute from the target when closed', async () => { - popover.opened = true; - await nextRender(); - - popover.opened = false; - await nextUpdate(popover); - expect(target.hasAttribute('aria-controls')).to.be.false; + ['target', 'ariaTarget'].forEach((prop) => { + describe(prop, () => { + let element; + + beforeEach(async () => { + if (prop === 'ariaTarget') { + target = fixtureSync('
'); + popover.target = target; + element = target.firstElementChild; + target.ariaTarget = element; + await nextUpdate(popover); + } else { + element = target; + } + }); + + it(`should set aria-haspopup attribute on the ${prop}`, () => { + expect(element.getAttribute('aria-haspopup')).to.equal('dialog'); + }); + + it(`should keep aria-haspopup attribute on the ${prop} when overlayRole is set to alertdialog`, async () => { + popover.overlayRole = 'alertdialog'; + await nextUpdate(popover); + expect(element.getAttribute('aria-haspopup')).to.equal('dialog'); + }); + + it(`should update aria-haspopup attribute on the ${prop} when overlayRole is set to different value`, async () => { + popover.overlayRole = 'menu'; + await nextUpdate(popover); + expect(element.getAttribute('aria-haspopup')).to.equal('true'); + }); + + it(`should set aria-expanded attribute on the ${prop} when closed`, () => { + expect(element.getAttribute('aria-expanded')).to.equal('false'); + }); + + it(`should set aria-expanded attribute on the ${prop} when opened`, async () => { + popover.opened = true; + await nextRender(); + expect(element.getAttribute('aria-expanded')).to.equal('true'); + }); + + it(`should set aria-controls attribute on the ${prop} when opened`, async () => { + popover.opened = true; + await nextRender(); + expect(element.getAttribute('aria-controls')).to.equal(overlay.id); + }); + + it(`should remove aria-controls attribute from the ${prop} when closed`, async () => { + popover.opened = true; + await nextRender(); + + popover.opened = false; + await nextUpdate(popover); + expect(element.hasAttribute('aria-controls')).to.be.false; + }); + + it(`should remove aria-haspopup attribute from ${prop} when target is cleared`, async () => { + popover.target = null; + await nextUpdate(popover); + expect(element.hasAttribute('aria-haspopup')).to.be.false; + }); + + it(`should remove aria-controls attribute from ${prop} when target is cleared`, async () => { + popover.opened = true; + await nextRender(); + + popover.target = null; + await nextUpdate(popover); + expect(element.hasAttribute('aria-controls')).to.be.false; + }); + + it(`should remove aria-expanded attribute from ${prop} when target is cleared`, async () => { + popover.target = null; + await nextUpdate(popover); + expect(element.hasAttribute('aria-expanded')).to.be.false; + }); + }); }); });