Optimise dropdowns for accessibility - Profile page (#13916)

* use initializedropdown in profileDropdown

* improve aria in html

* add cypress tests, stop buttons and dropdown initializing twice or more

* make sure dropdown shows when logged out
This commit is contained in:
Suzanne Aitchison 2021-06-14 12:31:23 +01:00 committed by GitHub
parent a7159b84f8
commit f607cf8722
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 156 additions and 44 deletions

View file

@ -7,7 +7,7 @@
initializeCommentPreview, initializeRuntimeBanner,
initializeTimeFixer, initializeDashboardSort, initializePWAFunctionality,
initializeArchivedPostFilter, initializeCreditsPage,
initializeUserProfilePage, initializeProfileInfoToggle, initializePodcastPlayback,
initializeProfileInfoToggle, initializePodcastPlayback,
initializeVideoPlayback, initializeDrawerSliders, initializeProfileBadgesToggle,
initializeHeroBannerClose, initializeOnboardingTaskCard, initScrolling,
nextPage:writable, fetching:writable, done:writable, adClicked:writable,
@ -31,7 +31,6 @@ function callInitializers() {
initializePWAFunctionality();
initializeArchivedPostFilter();
initializeCreditsPage();
initializeUserProfilePage();
initializeProfileInfoToggle();
initializeProfileBadgesToggle();
initializePodcastPlayback();

View file

@ -1,39 +1,5 @@
'use strict';
function initializeUserProfilePage() {
const profileDropdownDiv = document.getElementsByClassName(
'profile-dropdown',
)[0];
if (profileDropdownDiv) {
const currentUser = userData();
if (
currentUser &&
currentUser.username === profileDropdownDiv.dataset.username
) {
profileDropdownDiv.hidden = true;
} else {
profileDropdownDiv.hidden = false;
const userProfileDropdownButton = document.getElementById(
'user-profile-dropdown',
);
if (userProfileDropdownButton) {
const userProfileDropdownMenu = document.getElementById(
'user-profile-dropdownmenu',
);
userProfileDropdownButton.addEventListener('click', () => {
userProfileDropdownMenu.classList.toggle('block');
// Add actual link location (SEO doesn't like these "useless" links, so adding in here instead of in HTML)
var reportAbuseLink = profileDropdownDiv.getElementsByClassName(
'report-abuse-link-wrapper',
)[0];
reportAbuseLink.innerHTML = `<a href="${reportAbuseLink.dataset.path}" class="crayons-link crayons-link--block">Report Abuse</a>`;
});
}
}
}
}
function initializeProfileInfoToggle() {
const infoPanels = document.getElementsByClassName('js-user-info')[0];
const trigger = document.getElementsByClassName('js-user-info-trigger')[0];

View file

@ -1,13 +1,46 @@
import { initBlock } from '../profileDropdown/blockButton';
import { initFlag } from '../profileDropdown/flagButton';
import { initializeDropdown } from '@utilities/dropdownUtils';
/* global userData */
function initButtons() {
initBlock();
initFlag();
}
window.InstantClick.on('change', () => {
initButtons();
});
function initDropdown() {
const profileDropdownDiv = document.querySelector('.profile-dropdown');
initButtons();
if (profileDropdownDiv.dataset.dropdownInitialized === 'true') {
return;
}
const currentUser = userData();
if (
!profileDropdownDiv ||
(currentUser &&
currentUser.username === profileDropdownDiv.dataset.username)
) {
// Hide this menu when user views their own profile
return;
}
profileDropdownDiv.classList.remove('hidden');
initializeDropdown({
triggerElementId: 'user-profile-dropdown',
dropdownContentId: 'user-profile-dropdownmenu',
});
// Add actual link location (SEO doesn't like these "useless" links, so adding in here instead of in HTML)
const reportAbuseLink = profileDropdownDiv.querySelector(
'.report-abuse-link-wrapper',
);
reportAbuseLink.innerHTML = `<a href="${reportAbuseLink.dataset.path}" class="crayons-link crayons-link--block">Report Abuse</a>`;
initButtons();
profileDropdownDiv.dataset.dropdownInitialized = true;
}
initDropdown();

View file

@ -27,12 +27,13 @@
</span>
<div class="profile-header__actions">
<div class="profile-dropdown mr-2 relative" data-username="<%= @user.username %>">
<button id="user-profile-dropdown" class="crayons-btn crayons-btn--ghost-dimmed crayons-btn--icon">
<%= inline_svg_tag("overflow-horizontal.svg", class: "dropdown-icon crayons-icon", aria: true, title: "Toggle dropdown menu") %>
<div class="profile-dropdown mr-2 relative hidden" data-username="<%= @user.username %>">
<button id="user-profile-dropdown" aria-expanded="false" aria-controls="user-profile-dropdownmenu" aria-haspopup="true" class="crayons-btn crayons-btn--ghost-dimmed crayons-btn--icon">
<%= inline_svg_tag("overflow-horizontal.svg", class: "dropdown-icon crayons-icon", aria: true, title: "User actions") %>
</button>
<div id="user-profile-dropdownmenu" class="crayons-dropdown top-100 right-0 p-1">
<%= javascript_packs_with_chunks_tag "profileDropdown", defer: true %>
<% if user_signed_in? %>
<a href="javascript:void(0);" id="user-profile-dropdownmenu-block-button" data-profile-user-id="<%= @user.id %>" class="border-none crayons-link crayons-link--block">Block @<%= @user.username %></a>
<a
@ -44,7 +45,6 @@
class="border-none crayons-link crayons-link--block">
<%= @flag_status ? "Unflag" : "Flag" %> @<%= @user.username %>
</a>
<%= javascript_packs_with_chunks_tag "profileDropdown", defer: true %>
<% end %>
<span class="report-abuse-link-wrapper" data-path="/report-abuse?url=<%= user_url(@user) %>"></span>
</div>

View file

@ -0,0 +1,114 @@
describe('Profile User Actions Menu', () => {
describe('Logged in users', () => {
beforeEach(() => {
cy.testSetup();
cy.fixture('users/adminUser.json').as('user');
cy.get('@user').then((user) => {
cy.loginUser(user).then(() => {
cy.visit('/');
});
});
});
it("should show a dropdown menu when a user views another user's profile", () => {
cy.visit('/article_editor_v1_user');
cy.findByRole('button', { name: 'User actions' }).as('dropdownButton');
cy.get('@dropdownButton').click();
cy.findByRole('link', { name: 'Block @article_editor_v1_user' }).should(
'have.focus',
);
cy.findByRole('link', { name: 'Flag @article_editor_v1_user' });
cy.findByRole('link', { name: 'Report Abuse' });
// Check the menu can be closed by click
cy.get('@dropdownButton').click();
cy.findByRole('link', { name: 'Block @article_editor_v1_user' }).should(
'not.exist',
);
// Check the menu can be closed by escape press
cy.get('@dropdownButton').click();
cy.findByRole('link', { name: 'Block @article_editor_v1_user' });
cy.get('body').type('{esc}');
cy.findByRole('link', { name: 'Block @article_editor_v1_user' }).should(
'not.exist',
);
cy.get('@dropdownButton').should('have.focus');
});
it('should not show a dropdown menu when a user views their own profile', () => {
cy.visit('/admin_mcadmin');
cy.findByRole('button', { name: 'User actions' }).should('not.exist');
});
it('should block and unblock a user', () => {
// Always accept the confirmation that pops up
cy.on('window:confirm', () => true);
cy.visit('/article_editor_v1_user');
cy.findByRole('button', { name: 'User actions' }).click();
cy.findByRole('link', { name: 'Block @article_editor_v1_user' }).click();
// Check that the menu option has updated, and unblock user
cy.findByRole('link', { name: 'Block @article_editor_v1_user' }).should(
'not.exist',
);
cy.findByRole('link', { name: 'Unblock' }).click();
cy.findByRole('link', { name: 'Unblock' }).should('not.exist');
cy.findByRole('link', { name: 'Block' });
});
it('should flag and unflag a user', () => {
// Always accept the confirmation that pops up
cy.on('window:confirm', () => true);
cy.visit('/article_editor_v1_user');
cy.findByRole('button', { name: 'User actions' }).click();
cy.findByRole('link', { name: 'Flag @article_editor_v1_user' }).click();
// Check that the menu option has updated
cy.findByRole('link', { name: 'Flag @article_editor_v1_user' }).should(
'not.exist',
);
cy.findByRole('link', { name: 'Unflag @article_editor_v1_user' }).click();
// Check that the menu option has updated
cy.findByRole('link', { name: 'Unflag @article_editor_v1_user' }).should(
'not.exist',
);
cy.findByRole('link', { name: 'Flag @article_editor_v1_user' });
});
it('should link to report abuse from user', () => {
cy.visit('/article_editor_v1_user');
cy.findByRole('button', { name: 'User actions' }).click();
cy.findByRole('link', { name: 'Report Abuse' }).click();
cy.url().should('contain', 'report-abuse');
cy.url().should('contain', 'article_editor_v1_user');
});
});
describe('Logged out users', () => {
beforeEach(() => {
cy.testSetup();
});
it('should show a dropdown menu with only the Report Abuse link when the user is not logged in', () => {
cy.visit('/article_editor_v1_user');
cy.findByRole('button', { name: 'User actions' }).click();
cy.findByRole('link', { name: 'Report Abuse' }).should('have.focus');
cy.findByRole('link', { name: 'Block @article_editor_v1_user' }).should(
'not.exist',
);
cy.findByRole('link', { name: 'Flag @article_editor_v1_user' }).should(
'not.exist',
);
});
});
});