From f607cf8722988ed09bb3eae96c332fb1a612205b Mon Sep 17 00:00:00 2001 From: Suzanne Aitchison Date: Mon, 14 Jun 2021 12:31:23 +0100 Subject: [PATCH] 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 --- app/assets/javascripts/initializePage.js | 3 +- .../initializers/initializeUserProfilePage.js | 34 ------ app/javascript/packs/profileDropdown.js | 41 ++++++- app/views/users/show.html.erb | 8 +- .../profileUserActionsMenu.spec.js | 114 ++++++++++++++++++ 5 files changed, 156 insertions(+), 44 deletions(-) create mode 100644 cypress/integration/profileFlows/profileUserActionsMenu.spec.js diff --git a/app/assets/javascripts/initializePage.js b/app/assets/javascripts/initializePage.js index 8e96a05da..ba2e336d7 100644 --- a/app/assets/javascripts/initializePage.js +++ b/app/assets/javascripts/initializePage.js @@ -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(); diff --git a/app/assets/javascripts/initializers/initializeUserProfilePage.js b/app/assets/javascripts/initializers/initializeUserProfilePage.js index 3b343b55e..7ab8ffd05 100644 --- a/app/assets/javascripts/initializers/initializeUserProfilePage.js +++ b/app/assets/javascripts/initializers/initializeUserProfilePage.js @@ -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 = `Report Abuse`; - }); - } - } - } -} - function initializeProfileInfoToggle() { const infoPanels = document.getElementsByClassName('js-user-info')[0]; const trigger = document.getElementsByClassName('js-user-info-trigger')[0]; diff --git a/app/javascript/packs/profileDropdown.js b/app/javascript/packs/profileDropdown.js index 6e0c35664..55d1d408e 100644 --- a/app/javascript/packs/profileDropdown.js +++ b/app/javascript/packs/profileDropdown.js @@ -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 = `Report Abuse`; + + initButtons(); + profileDropdownDiv.dataset.dropdownInitialized = true; +} + +initDropdown(); diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 4b72ff38e..aa9097687 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -27,12 +27,13 @@
-
-
+ <%= javascript_packs_with_chunks_tag "profileDropdown", defer: true %> <% if user_signed_in? %> Block @<%= @user.username %> <%= @flag_status ? "Unflag" : "Flag" %> @<%= @user.username %> - <%= javascript_packs_with_chunks_tag "profileDropdown", defer: true %> <% end %>
diff --git a/cypress/integration/profileFlows/profileUserActionsMenu.spec.js b/cypress/integration/profileFlows/profileUserActionsMenu.spec.js new file mode 100644 index 000000000..f52f35d92 --- /dev/null +++ b/cypress/integration/profileFlows/profileUserActionsMenu.spec.js @@ -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', + ); + }); + }); +});