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(),
+ },
+});