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 change
This commit is contained in:
parent
0fd7b4e969
commit
ce059a909b
|
@ -39,6 +39,7 @@ const MENU_ITEMS = '[role^="menuitem"]';
|
|||
export default Component.extend({
|
||||
tagName: '',
|
||||
dom: service('dom'),
|
||||
router: service('router'),
|
||||
guid: '',
|
||||
expanded: false,
|
||||
orientation: 'vertical',
|
||||
|
@ -47,6 +48,7 @@ export default Component.extend({
|
|||
this._super(...arguments);
|
||||
set(this, 'guid', this.dom.guid(this));
|
||||
this._listeners = this.dom.listeners();
|
||||
this._routelisteners = this.dom.listeners();
|
||||
},
|
||||
didInsertElement: function() {
|
||||
// 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}`);
|
||||
const labelledBy = this.$menu.getAttribute('aria-labelledby');
|
||||
this.$trigger = this.dom.element(`#${labelledBy}`);
|
||||
this._routelisteners.add(this.router, {
|
||||
routeWillChange: () => this.actions.close.apply(this, [{}]),
|
||||
});
|
||||
},
|
||||
willDestroyElement: function() {
|
||||
this._super(...arguments);
|
||||
this._listeners.remove();
|
||||
this._routelisteners.remove();
|
||||
},
|
||||
actions: {
|
||||
keypressClick: function(e) {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
{{#if dc}}
|
||||
<ul>
|
||||
{{#if (and (env 'CONSUL_NSPACES_ENABLED') (gt nspaces.length 0))}}
|
||||
<li>
|
||||
<li data-test-nspace-menu>
|
||||
{{#if (and (eq nspaces.length 1) (not canManageNspaces)) }}
|
||||
<span data-test-nspace-selected={{nspace.Name}}>{{nspace.Name}}</span>
|
||||
{{ else }}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{{yield (concat 'popover-menu-' guid)}}
|
||||
{{#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}}>
|
||||
{{#yield-slot name='trigger'}}
|
||||
{{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 = {
|
||||
navigation: ['services', 'nodes', 'kvs', 'acls', 'intentions', 'docs', 'settings'].reduce(
|
||||
function(prev, item, i, arr) {
|
||||
|
@ -24,4 +24,10 @@ const page = {
|
|||
),
|
||||
};
|
||||
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;
|
||||
|
|
|
@ -1,4 +1,30 @@
|
|||
/* 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) {
|
||||
scenario
|
||||
.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) {
|
||||
// Collection
|
||||
var obj;
|
||||
const message = `Expected to not see ${property} on ${component}`;
|
||||
// Cope with collections
|
||||
let obj;
|
||||
if (typeof currentPage()[component].objectAt === 'function') {
|
||||
obj = currentPage()[component].objectAt(0);
|
||||
} else {
|
||||
obj = currentPage()[component];
|
||||
}
|
||||
assert.throws(
|
||||
function() {
|
||||
const func = obj[property].bind(obj);
|
||||
func();
|
||||
},
|
||||
function(e) {
|
||||
return e.message.startsWith('Element not found');
|
||||
},
|
||||
`Expected to not see ${property} on ${component}`
|
||||
);
|
||||
let prop;
|
||||
try {
|
||||
prop = obj[property];
|
||||
} catch (e) {
|
||||
if (isExpectedError(e)) {
|
||||
assert.ok(true, message);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
assert.throws(
|
||||
|
|
Loading…
Reference in New Issue