mirror of https://github.com/hashicorp/consul
ui: Fix sticky nspace menu (#7164)
* ui: Fix typo expanded > ariaExpanded * ui: Add the things we need to test this * ui: Add tests for testing the menu closes when clicked * ui: Ensure the aria-menu closes on route changepull/7190/head
parent
1b74a68780
commit
f45880bffe
|
@ -39,6 +39,7 @@ const MENU_ITEMS = '[role^="menuitem"]';
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
tagName: '',
|
tagName: '',
|
||||||
dom: service('dom'),
|
dom: service('dom'),
|
||||||
|
router: service('router'),
|
||||||
guid: '',
|
guid: '',
|
||||||
expanded: false,
|
expanded: false,
|
||||||
orientation: 'vertical',
|
orientation: 'vertical',
|
||||||
|
@ -47,6 +48,7 @@ export default Component.extend({
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
set(this, 'guid', this.dom.guid(this));
|
set(this, 'guid', this.dom.guid(this));
|
||||||
this._listeners = this.dom.listeners();
|
this._listeners = this.dom.listeners();
|
||||||
|
this._routelisteners = this.dom.listeners();
|
||||||
},
|
},
|
||||||
didInsertElement: function() {
|
didInsertElement: function() {
|
||||||
// TODO: How do you detect whether the children have changed?
|
// TODO: How do you detect whether the children have changed?
|
||||||
|
@ -54,10 +56,14 @@ export default Component.extend({
|
||||||
this.$menu = this.dom.element(`#${COMPONENT_ID}menu-${this.guid}`);
|
this.$menu = this.dom.element(`#${COMPONENT_ID}menu-${this.guid}`);
|
||||||
const labelledBy = this.$menu.getAttribute('aria-labelledby');
|
const labelledBy = this.$menu.getAttribute('aria-labelledby');
|
||||||
this.$trigger = this.dom.element(`#${labelledBy}`);
|
this.$trigger = this.dom.element(`#${labelledBy}`);
|
||||||
|
this._routelisteners.add(this.router, {
|
||||||
|
routeWillChange: () => this.actions.close.apply(this, [{}]),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
willDestroyElement: function() {
|
willDestroyElement: function() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this._listeners.remove();
|
this._listeners.remove();
|
||||||
|
this._routelisteners.remove();
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
keypressClick: function(e) {
|
keypressClick: function(e) {
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
{{#if dc}}
|
{{#if dc}}
|
||||||
<ul>
|
<ul>
|
||||||
{{#if (and (env 'CONSUL_NSPACES_ENABLED') (gt nspaces.length 0))}}
|
{{#if (and (env 'CONSUL_NSPACES_ENABLED') (gt nspaces.length 0))}}
|
||||||
<li>
|
<li data-test-nspace-menu>
|
||||||
{{#if (and (eq nspaces.length 1) (not canManageNspaces)) }}
|
{{#if (and (eq nspaces.length 1) (not canManageNspaces)) }}
|
||||||
<span data-test-nspace-selected={{nspace.Name}}>{{nspace.Name}}</span>
|
<span data-test-nspace-selected={{nspace.Name}}>{{nspace.Name}}</span>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{{yield (concat 'popover-menu-' guid)}}
|
{{yield (concat 'popover-menu-' guid)}}
|
||||||
{{#aria-menu keyboardAccess=keyboardAccess as |change keypress ariaLabelledBy ariaControls ariaExpanded keypressClick|}}
|
{{#aria-menu keyboardAccess=keyboardAccess as |change keypress ariaLabelledBy ariaControls ariaExpanded keypressClick|}}
|
||||||
{{#toggle-button checked=expanded onchange=(queue change (action 'change')) as |click|}}
|
{{#toggle-button checked=ariaExpanded onchange=(queue change (action 'change')) as |click|}}
|
||||||
<button type="button" aria-haspopup="menu" onkeydown={{keypress}} onclick={{click}} id={{ariaLabelledBy}} aria-controls={{ariaControls}}>
|
<button type="button" aria-haspopup="menu" onkeydown={{keypress}} onclick={{click}} id={{ariaLabelledBy}} aria-controls={{ariaControls}}>
|
||||||
{{#yield-slot name='trigger'}}
|
{{#yield-slot name='trigger'}}
|
||||||
{{yield}}
|
{{yield}}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
@setupApplicationTest
|
||||||
|
Feature: dc / nspaces / manage : Managing Namespaces
|
||||||
|
Scenario:
|
||||||
|
Given settings from yaml
|
||||||
|
---
|
||||||
|
consul:token:
|
||||||
|
SecretID: secret
|
||||||
|
AccessorID: accessor
|
||||||
|
Namespace: default
|
||||||
|
---
|
||||||
|
And 1 datacenter models from yaml
|
||||||
|
---
|
||||||
|
- dc-1
|
||||||
|
---
|
||||||
|
And 6 service models
|
||||||
|
When I visit the services page for yaml
|
||||||
|
---
|
||||||
|
dc: dc-1
|
||||||
|
---
|
||||||
|
Then the url should be /dc-1/services
|
||||||
|
Then I see 6 service models
|
||||||
|
# In order to test this properly you have to click around a few times
|
||||||
|
# between services and nspace management
|
||||||
|
When I click nspace on the navigation
|
||||||
|
And I click manageNspaces on the navigation
|
||||||
|
Then the url should be /dc-1/namespaces
|
||||||
|
And I don't see manageNspacesIsVisible on the navigation
|
||||||
|
When I click services on the navigation
|
||||||
|
Then the url should be /dc-1/services
|
||||||
|
When I click nspace on the navigation
|
||||||
|
And I click manageNspaces on the navigation
|
||||||
|
Then the url should be /dc-1/namespaces
|
||||||
|
And I don't see manageNspacesIsVisible on the navigation
|
|
@ -0,0 +1,10 @@
|
||||||
|
import steps from '../../steps';
|
||||||
|
|
||||||
|
// step definitions that are shared between features should be moved to the
|
||||||
|
// tests/acceptance/steps/steps.js file
|
||||||
|
|
||||||
|
export default function(assert) {
|
||||||
|
return steps(assert).then('I should find a file', function() {
|
||||||
|
assert.ok(true, this.step);
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { clickable } from 'ember-cli-page-object';
|
import { clickable, is } from 'ember-cli-page-object';
|
||||||
const page = {
|
const page = {
|
||||||
navigation: ['services', 'nodes', 'kvs', 'acls', 'intentions', 'docs', 'settings'].reduce(
|
navigation: ['services', 'nodes', 'kvs', 'acls', 'intentions', 'docs', 'settings'].reduce(
|
||||||
function(prev, item, i, arr) {
|
function(prev, item, i, arr) {
|
||||||
|
@ -24,4 +24,10 @@ const page = {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
page.navigation.dc = clickable('[data-test-datacenter-menu] button');
|
page.navigation.dc = clickable('[data-test-datacenter-menu] button');
|
||||||
|
page.navigation.nspace = clickable('[data-test-nspace-menu] button');
|
||||||
|
page.navigation.manageNspaces = clickable('[data-test-main-nav-nspaces] a');
|
||||||
|
page.navigation.manageNspacesIsVisible = is(
|
||||||
|
':checked',
|
||||||
|
'[data-test-nspace-menu] > input[type="checkbox"]'
|
||||||
|
);
|
||||||
export default page;
|
export default page;
|
||||||
|
|
|
@ -1,4 +1,30 @@
|
||||||
/* eslint no-console: "off" */
|
/* eslint no-console: "off" */
|
||||||
|
const notFound = 'Element not found';
|
||||||
|
const cannotDestructure = "Cannot destructure property 'context'";
|
||||||
|
const cannotReadContext = "Cannot read property 'context' of undefined";
|
||||||
|
// checking for existence of pageObjects is pretty difficult
|
||||||
|
// errors are thrown but we should check to make sure its the error that we
|
||||||
|
// want and not another real error
|
||||||
|
// to make things more difficult depending on how you reference the pageObject
|
||||||
|
// an error with a different message is thrown for example:
|
||||||
|
|
||||||
|
// pageObject[thing]() will give you a Element not found error
|
||||||
|
|
||||||
|
// whereas:
|
||||||
|
|
||||||
|
// const obj = pageObject[thing]; obj() will give you a 'cannot destructure error'
|
||||||
|
// and in CI it will give you a 'cannot read property' error
|
||||||
|
|
||||||
|
// the difference in CI could be a difference due to headless vs headed browser
|
||||||
|
// or difference in Chrome/browser versions
|
||||||
|
|
||||||
|
// ideally we wouldn't be checking on error messages at all, but we want to make sure
|
||||||
|
// that real errors are picked up by the tests, so if this gets unmanageable at any point
|
||||||
|
// look at checking for the instance of e being TypeError or similar
|
||||||
|
const isExpectedError = function(e) {
|
||||||
|
return [notFound, cannotDestructure, cannotReadContext].some(item => e.message.startsWith(item));
|
||||||
|
};
|
||||||
|
|
||||||
export default function(scenario, assert, find, currentPage) {
|
export default function(scenario, assert, find, currentPage) {
|
||||||
scenario
|
scenario
|
||||||
.then('I see $property on the $component like yaml\n$yaml', function(
|
.then('I see $property on the $component like yaml\n$yaml', function(
|
||||||
|
@ -64,23 +90,37 @@ export default function(scenario, assert, find, currentPage) {
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.then(["I don't see $property on the $component"], function(property, component) {
|
.then(["I don't see $property on the $component"], function(property, component) {
|
||||||
// Collection
|
const message = `Expected to not see ${property} on ${component}`;
|
||||||
var obj;
|
// Cope with collections
|
||||||
|
let obj;
|
||||||
if (typeof currentPage()[component].objectAt === 'function') {
|
if (typeof currentPage()[component].objectAt === 'function') {
|
||||||
obj = currentPage()[component].objectAt(0);
|
obj = currentPage()[component].objectAt(0);
|
||||||
} else {
|
} else {
|
||||||
obj = currentPage()[component];
|
obj = currentPage()[component];
|
||||||
}
|
}
|
||||||
assert.throws(
|
let prop;
|
||||||
function() {
|
try {
|
||||||
const func = obj[property].bind(obj);
|
prop = obj[property];
|
||||||
func();
|
} catch (e) {
|
||||||
},
|
if (isExpectedError(e)) {
|
||||||
function(e) {
|
assert.ok(true, message);
|
||||||
return e.message.startsWith('Element not found');
|
} else {
|
||||||
},
|
throw e;
|
||||||
`Expected to not see ${property} on ${component}`
|
}
|
||||||
);
|
}
|
||||||
|
if (typeof prop === 'function') {
|
||||||
|
assert.throws(
|
||||||
|
function() {
|
||||||
|
prop();
|
||||||
|
},
|
||||||
|
function(e) {
|
||||||
|
return isExpectedError(e);
|
||||||
|
},
|
||||||
|
message
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert.notOk(prop);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then(["I don't see $property"], function(property) {
|
.then(["I don't see $property"], function(property) {
|
||||||
assert.throws(
|
assert.throws(
|
||||||
|
|
Loading…
Reference in New Issue