diff --git a/ui/app/components/popover-menu.js b/ui/app/components/popover-menu.js index 850a53d6b..856f26945 100644 --- a/ui/app/components/popover-menu.js +++ b/ui/app/components/popover-menu.js @@ -2,7 +2,6 @@ import Component from '@ember/component'; import { run } from '@ember/runloop'; const TAB = 9; -// const ESC = 27; const ARROW_DOWN = 40; const FOCUSABLE = [ 'a:not([disabled])', @@ -16,8 +15,9 @@ export default Component.extend({ classnames: ['popover'], triggerClass: '', - isOpen: false, + label: '', + dropdown: null, capture(dropdown) { diff --git a/ui/app/templates/components/popover-menu.hbs b/ui/app/templates/components/popover-menu.hbs index 8d7a80066..bebdb18fd 100644 --- a/ui/app/templates/components/popover-menu.hbs +++ b/ui/app/templates/components/popover-menu.hbs @@ -6,7 +6,7 @@ {{label}} {{x-icon "chevron-down" class="is-text"}} - + {{yield dd}} diff --git a/ui/tests/integration/popover-menu-test.js b/ui/tests/integration/popover-menu-test.js new file mode 100644 index 000000000..2f42b73fa --- /dev/null +++ b/ui/tests/integration/popover-menu-test.js @@ -0,0 +1,115 @@ +import { find, click } from '@ember/test-helpers'; +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; +import { create } from 'ember-cli-page-object'; +import popoverMenuPageObject from 'nomad-ui/tests/pages/components/popover-menu'; + +const PopoverMenu = create(popoverMenuPageObject()); + +module('Integration | Component | popover-menu', function(hooks) { + setupRenderingTest(hooks); + + const commonProperties = overrides => + Object.assign( + { + triggerClass: '', + label: 'Trigger Label', + }, + overrides + ); + + const commonTemplate = hbs` + {{#popover-menu + isOpen=isOpen + label=label + triggerClass=triggerClass as |m|}} +

This is a heading

+ + + {{/popover-menu}} + `; + + test('presents as a button with a chevron-down icon', async function(assert) { + const props = commonProperties(); + this.setProperties(props); + await this.render(commonTemplate); + + assert.ok(PopoverMenu.isPresent); + assert.ok(PopoverMenu.labelHasIcon); + assert.notOk(PopoverMenu.menu.isOpen); + assert.equal(PopoverMenu.label, props.label); + }); + + test('clicking the trigger button toggles the popover menu', async function(assert) { + const props = commonProperties(); + this.setProperties(props); + await this.render(commonTemplate); + assert.notOk(PopoverMenu.menu.isOpen); + + await PopoverMenu.toggle(); + + assert.ok(PopoverMenu.menu.isOpen); + }); + + test('the trigger gets the triggerClass prop assigned as a class', async function(assert) { + const specialClass = 'is-special'; + const props = commonProperties({ triggerClass: specialClass }); + this.setProperties(props); + await this.render(commonTemplate); + + assert.ok(Array.from(find('[data-test-popover-trigger]').classList).includes('is-special')); + }); + + test('pressing DOWN ARROW when the trigger is focused opens the popover menu', async function(assert) { + const props = commonProperties(); + this.setProperties(props); + await this.render(commonTemplate); + assert.notOk(PopoverMenu.menu.isOpen); + + await PopoverMenu.focus(); + await PopoverMenu.downArrow(); + + assert.ok(PopoverMenu.menu.isOpen); + }); + + test('pressing TAB when the trigger button is focused and the menu is open focuses the first focusable element in the popover menu', async function(assert) { + const props = commonProperties(); + this.setProperties(props); + await this.render(commonTemplate); + + await PopoverMenu.focus(); + await PopoverMenu.downArrow(); + + assert.equal(document.activeElement, find('[data-test-popover-trigger]')); + + await PopoverMenu.focusNext(); + + assert.equal(document.activeElement, find('#mock-input-for-test')); + }); + + test('pressing ESC when the popover menu is open closes the menu and returns focus to the trigger button', async function(assert) { + const props = commonProperties(); + this.setProperties(props); + await this.render(commonTemplate); + + await PopoverMenu.toggle(); + assert.ok(PopoverMenu.menu.isOpen); + + await PopoverMenu.esc(); + + assert.notOk(PopoverMenu.menu.isOpen); + }); + + test('the ember-basic-dropdown object is yielded as context, including the close action', async function(assert) { + const props = commonProperties(); + this.setProperties(props); + await this.render(commonTemplate); + + await PopoverMenu.toggle(); + assert.ok(PopoverMenu.menu.isOpen); + + await click('#mock-button-for-test'); + assert.notOk(PopoverMenu.menu.isOpen); + }); +}); diff --git a/ui/tests/pages/components/popover-menu.js b/ui/tests/pages/components/popover-menu.js new file mode 100644 index 000000000..e156198aa --- /dev/null +++ b/ui/tests/pages/components/popover-menu.js @@ -0,0 +1,32 @@ +import { clickable, focusable, isPresent, text, triggerable } from 'ember-cli-page-object'; + +const ARROW_DOWN = 40; +const ESC = 27; +const TAB = 9; + +export default scope => ({ + scope, + + isPresent: isPresent(), + label: text('[data-test-popover-trigger]'), + labelHasIcon: isPresent('[data-test-popover-trigger] svg.icon'), + + toggle: clickable('[data-test-popover-trigger]'), + focus: focusable('[data-test-popover-trigger]'), + downArrow: triggerable('keydown', '[data-test-popover-trigger]', { + eventProperties: { keyCode: ARROW_DOWN }, + }), + focusNext: triggerable('keydown', '[data-test-popover-trigger]', { + eventProperties: { keyCode: TAB }, + }), + esc: triggerable('keydown', '[data-test-popover-trigger]', { + eventProperties: { keyCode: ESC }, + }), + + menu: { + scope: '[data-test-popover-menu]', + testContainer: '#ember-testing', + resetScope: true, + isOpen: isPresent(), + }, +});