Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove UserMenu component render modifier #8179

Merged
Merged
2 changes: 0 additions & 2 deletions packages/frontend/.lint-todo
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
add|ember-template-lint|no-at-ember-render-modifiers|5|2|5|2|23cd787c79c34a628dadb6e96dd4004d42eebb79|1722902400000|1730682000000|1754006400000|app/components/new-directory-user.hbs
add|ember-template-lint|no-at-ember-render-modifiers|28|43|28|43|5c127b0b8124a93b18177d7580bcc47dbb8ebbff|1722902400000|1730682000000|1754006400000|app/components/user-menu.hbs
add|ember-template-lint|no-at-ember-render-modifiers|5|4|5|4|1459921678f69a3a659ce69b1da303bc8e96b104|1725580800000|1728172800000|1730768400000|app/components/user-menu.hbs
add|ember-template-lint|no-at-ember-render-modifiers|4|2|4|2|23cd787c79c34a628dadb6e96dd4004d42eebb79|1722902400000|1730682000000|1754006400000|app/components/user-profile-bio.hbs
add|ember-template-lint|no-at-ember-render-modifiers|5|2|5|2|f53982efe02d2bef9e7f12b5b862288c594579c2|1722902400000|1730682000000|1754006400000|app/components/user-profile-bio.hbs
add|ember-template-lint|no-at-ember-render-modifiers|4|2|4|2|23cd787c79c34a628dadb6e96dd4004d42eebb79|1722902400000|1730682000000|1754006400000|app/components/user-profile-roles.hbs
Expand Down
10 changes: 7 additions & 3 deletions packages/frontend/app/components/user-menu.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
<nav
class="user-menu{{if this.isOpen ' is-open'}}"
aria-label={{t "general.userMenu"}}
{{did-insert (set this "element")}}
{{! template-lint-disable no-invalid-interactive }}
{{on "keyup" this.keyUp}}
data-test-user-menu
Expand All @@ -25,17 +24,22 @@
{{#if this.isOpen}}
<div {{on-click-outside (set this "isOpen" false)}}>
<ul class="menu">
<li tabindex="-1" data-test-item {{did-insert this.focus}}>
<li tabindex="-1" data-test-item {{focus}}>
<LinkToWithAction
@route="myprofile"
@action={{set this "isOpen" false}}
@queryParams={{hash invalidatetokens=null newtoken=null}}
data-test-item-link
>
{{t "general.myProfile"}}
</LinkToWithAction>
</li>
<li tabindex="-1" data-test-item>
<LinkToWithAction @route="logout" @action={{set this "isOpen" false}}>
<LinkToWithAction
@route="logout"
@action={{set this "isOpen" false}}
data-test-item-link
>
{{t "general.logout"}}
</LinkToWithAction>
</li>
Expand Down
32 changes: 17 additions & 15 deletions packages/frontend/app/components/user-menu.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import Component from '@glimmer/component';
import { schedule } from '@ember/runloop';
import { service } from '@ember/service';
import { cached, tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { task, timeout } from 'ember-concurrency';
import { TrackedAsyncData } from 'ember-async-data';

export default class UserMenuComponent extends Component {
@service intl;
@service currentUser;
@tracked isOpen = false;
@tracked element;

userModel = new TrackedAsyncData(this.currentUser.getModel());

Expand All @@ -18,9 +17,18 @@ export default class UserMenuComponent extends Component {
return this.userModel.isResolved ? this.userModel.value : null;
}

focusFirstLink = task(async () => {
await timeout(1);
document.querySelector('.user-menu .menu a:first-of-type').focus();
});

@action
toggleMenu() {
async toggleMenu() {
this.isOpen = !this.isOpen;

if (this.isOpen) {
await this.focusFirstLink.perform();
}
}

@action
Expand Down Expand Up @@ -49,30 +57,24 @@ export default class UserMenuComponent extends Component {
return true;
}

handleArrowDown(evt, item) {
if (evt.target.tagName.toLowerCase() === 'button') {
async handleArrowDown(event, item) {
if (event.target.tagName.toLowerCase() === 'button') {
this.isOpen = true;
await this.focusFirstLink.perform();
} else {
if (item.nextElementSibling) {
item.nextElementSibling.querySelector('a').focus();
} else {
// eslint-disable-next-line ember/no-runloop
schedule('afterRender', () => {
this.element.querySelector('.menu li:nth-of-type(1) a').focus();
});
item.parentElement.firstElementChild.querySelector('a').focus();
}
}
}

handleArrowUp(item) {
if (item.previousElementSibling) {
if (item?.previousElementSibling) {
item.previousElementSibling.querySelector('a').focus();
} else {
this.element.querySelector('.menu li:last-of-type a').focus();
item?.parentElement.lastElementChild.querySelector('a').focus();
}
}

focus(el) {
el.focus();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'frontend/tests/helpers';
import { render } from '@ember/test-helpers';
import { render, waitFor } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import component from 'frontend/tests/pages/components/user-menu';
import a11yAudit from 'ember-a11y-testing/test-support/audit';
Expand All @@ -11,6 +11,9 @@ module('Integration | Component | user-menu', function (hooks) {
setupRenderingTest(hooks);
setupMirage(hooks);

// My Profile and Logout
const linkCount = 2;
michaelchadwick marked this conversation as resolved.
Show resolved Hide resolved

hooks.beforeEach(async function () {
await setupAuthentication();
});
Expand All @@ -32,22 +35,22 @@ module('Integration | Component | user-menu', function (hooks) {

assert.strictEqual(component.links.length, 0);
await component.toggle.click();
assert.strictEqual(component.links.length, 2);
assert.strictEqual(component.links.length, linkCount);
});

test('down opens menu', async function (assert) {
await render(hbs`<UserMenu />`);

assert.strictEqual(component.links.length, 0);
await component.toggle.down();
assert.strictEqual(component.links.length, 2);
assert.strictEqual(component.links.length, linkCount);
});

test('escape closes menu', async function (assert) {
await render(hbs`<UserMenu />`);

await component.toggle.down();
assert.strictEqual(component.links.length, 2);
assert.strictEqual(component.links.length, linkCount);
await component.toggle.esc();
assert.strictEqual(component.links.length, 0);
});
Expand All @@ -56,8 +59,62 @@ module('Integration | Component | user-menu', function (hooks) {
await render(hbs`<UserMenu />`);

await component.toggle.down();
assert.strictEqual(component.links.length, 2);
assert.strictEqual(component.links.length, linkCount);
await component.toggle.click();
assert.strictEqual(component.links.length, 0);
});

test('keyboard navigation', async function (assert) {
michaelchadwick marked this conversation as resolved.
Show resolved Hide resolved
await render(hbs`<UserMenu />`);
await component.toggle.click();
assert.strictEqual(component.links.length, linkCount, `has ${linkCount} links`);

// slight delay to allow for proper loading of popup menu
await waitFor('.user-menu .menu');

assert.ok(component.links[0].link.hasFocus, 'first link has focus');
assert.notOk(component.links[1].link.hasFocus, 'second link does NOT have focus');

// regular down/up navigation
await component.links[0].down();
assert.notOk(
component.links[0].link.hasFocus,
'after moving down from first link, it DOES not have focus',
);
assert.ok(component.links[1].link.hasFocus, 'second link has focus');
await component.links[1].up();
assert.notOk(
component.links[1].link.hasFocus,
'after moving up from second link, it does not have focus',
);
assert.ok(component.links[0].link.hasFocus, 'first link has focus');

// wrap-around navigation from first to last menu item
await component.links[0].up();
assert.notOk(component.links[0].link.hasFocus);
assert.ok(component.links[1].link.hasFocus);
// wrap-around navigation from last to first menu item
await component.links[1].down();
assert.ok(component.links[0].link.hasFocus);
assert.notOk(component.links[1].link.hasFocus);

// close menu on escape, left, right, and tab keys.
await component.links[0].esc();
assert.strictEqual(component.links.length, 0);
await component.toggle.click();
assert.strictEqual(component.links.length, linkCount);
assert.ok(component.links[0].link.hasFocus);
await component.links[0].left();
assert.strictEqual(component.links.length, 0);
await component.toggle.click();
assert.strictEqual(component.links.length, linkCount);
assert.ok(component.links[0].link.hasFocus);
await component.links[0].right();
assert.strictEqual(component.links.length, 0);
await component.toggle.click();
assert.strictEqual(component.links.length, linkCount);
assert.ok(component.links[0].link.hasFocus);
await component.links[0].tab();
assert.strictEqual(component.links.length, 0);
});
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { attribute, clickable, create, hasClass } from 'ember-cli-page-object';
import { hasFocus } from 'ilios-common';

const definition = {
scope: '[data-test-link-to-with-action]',
url: attribute('href'),
hasFocus: hasFocus(),
isActive: hasClass('active'),
click: clickable(),
};
Expand Down
7 changes: 7 additions & 0 deletions packages/frontend/tests/pages/components/user-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,12 @@ export default create({
},
links: collection('[data-test-item]', {
link: linkToWithAction,
mouseEnter: triggerable('mouseenter'),
down: triggerable('keyup', '', { eventProperties: { key: 'ArrowDown' } }),
esc: triggerable('keyup', '', { eventProperties: { key: 'Escape' } }),
left: triggerable('keyup', '', { eventProperties: { key: 'ArrowLeft' } }),
right: triggerable('keyup', '', { eventProperties: { key: 'ArrowRight' } }),
tab: triggerable('keyup', '', { eventProperties: { key: 'Tab' } }),
up: triggerable('keyup', '', { eventProperties: { key: 'ArrowUp' } }),
}),
});