From 7ca0dd57cf612f7c8613615fd49d12e5ffcbecb4 Mon Sep 17 00:00:00 2001 From: Dusan Kosanovic Date: Mon, 20 Jan 2025 12:11:58 +0100 Subject: [PATCH 1/9] [MWPW-165791] Table select aria-label and column cell role added --- libs/blocks/table/table.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/libs/blocks/table/table.js b/libs/blocks/table/table.js index 4c279ce07d..b0805ecca1 100644 --- a/libs/blocks/table/table.js +++ b/libs/blocks/table/table.js @@ -1,7 +1,8 @@ /* eslint-disable no-plusplus */ -import { createTag, MILO_EVENTS } from '../../utils/utils.js'; +import { createTag, getConfig, MILO_EVENTS } from '../../utils/utils.js'; import { decorateButtons } from '../../utils/decorate.js'; import { debounce } from '../../utils/action.js'; +import { replaceKey } from '../../features/placeholders.js'; const DESKTOP_SIZE = 900; const MOBILE_SIZE = 768; @@ -63,7 +64,7 @@ function handleHeading(table, headingCols) { }); const headingContent = createTag('div', { class: 'heading-content' }); - const headingButton = createTag('div', { class: 'heading-button' }); + const headingButton = createTag('div', { class: 'heading-button', role: 'cell' }); [...elements].forEach((e) => { if (e.classList.contains('pricing') && isPriceBottom) headingButton.appendChild(e); @@ -448,6 +449,13 @@ function applyStylesBasedOnScreenSize(table, originTable) { if ((!isMerch && !table.querySelector('.col-3')) || (isMerch && !table.querySelector('.col-2'))) return; + async function setAriaLabelsForElements(first, second) { + const config = getConfig(); + const ariaLabel = await replaceKey('choose-table-column', config); + first.setAttribute('aria-label', ariaLabel); + second.setAttribute('aria-label', ariaLabel); + } + const filterChangeEvent = () => { table.innerHTML = originTable.innerHTML; reAssignEvents(table); @@ -508,6 +516,7 @@ function applyStylesBasedOnScreenSize(table, originTable) { table.parentElement.insertBefore(filters, table); table.parentElement.classList.add(`table-${table.classList.contains('merch') ? 'merch-' : ''}section`); filterChangeEvent(); + setAriaLabelsForElements(colSelect0, colSelect1); } }; From 65d7353029fb546118cd183171153e05222e967b Mon Sep 17 00:00:00 2001 From: Dusan Kosanovic Date: Mon, 20 Jan 2025 16:50:40 +0100 Subject: [PATCH 2/9] [MWPW-165791] test coverage fix --- test/blocks/table/table.test.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/blocks/table/table.test.js b/test/blocks/table/table.test.js index 0245bba663..8eea4b8086 100644 --- a/test/blocks/table/table.test.js +++ b/test/blocks/table/table.test.js @@ -1,10 +1,13 @@ import { readFile, sendMouse, sendKeys, resetMouse } from '@web/test-runner-commands'; import { expect } from 'chai'; -import { MILO_EVENTS } from '../../../libs/utils/utils.js'; +import { getConfig, MILO_EVENTS, setConfig } from '../../../libs/utils/utils.js'; import { delay, waitForElement } from '../../helpers/waitfor.js'; document.body.innerHTML = await readFile({ path: './mocks/body.html' }); const { default: init } = await import('../../../libs/blocks/table/table.js'); +const config = getConfig(); +config.env = { locale: { contentRoot: `${window.location.origin}` } }; +setConfig(config); describe('table and tablemetadata', () => { beforeEach(() => { @@ -111,5 +114,15 @@ describe('table and tablemetadata', () => { expect(tooltipHeading.childNodes.length).to.equal(2); expect(tooltipHeading.querySelector('.milo-tooltip, .icon-tooltip')).to.exist; }); + + it('should apply aria-label to all selects within .filters on mobile', async () => { + window.innerWidth = 375; + window.dispatchEvent(new Event('resize')); + await delay(500); + const selectElements = document.querySelectorAll('.filters select'); + selectElements.forEach((selectElement) => { + expect(selectElement.getAttribute('aria-label')).to.equal('choose table column'); + }); + }); }); }); From ce394bdbbfe6edd2a8f3468ff45295dc01f5d149 Mon Sep 17 00:00:00 2001 From: Dusan Kosanovic Date: Tue, 21 Jan 2025 13:56:51 +0100 Subject: [PATCH 3/9] [MWPW-165791] future proofing function, mocking placeholders for test --- libs/blocks/table/table.js | 10 ++++++---- test/blocks/table/mocks/placeholders.json | 8 ++++++++ test/blocks/table/table.test.js | 12 ++++++++---- 3 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 test/blocks/table/mocks/placeholders.json diff --git a/libs/blocks/table/table.js b/libs/blocks/table/table.js index b0805ecca1..c8f285a192 100644 --- a/libs/blocks/table/table.js +++ b/libs/blocks/table/table.js @@ -449,11 +449,13 @@ function applyStylesBasedOnScreenSize(table, originTable) { if ((!isMerch && !table.querySelector('.col-3')) || (isMerch && !table.querySelector('.col-2'))) return; - async function setAriaLabelsForElements(first, second) { + async function setAriaLabelsForElements(selectArray) { const config = getConfig(); const ariaLabel = await replaceKey('choose-table-column', config); - first.setAttribute('aria-label', ariaLabel); - second.setAttribute('aria-label', ariaLabel); + + selectArray.forEach((item) => { + item.setAttribute('aria-label', ariaLabel); + }); } const filterChangeEvent = () => { @@ -516,7 +518,7 @@ function applyStylesBasedOnScreenSize(table, originTable) { table.parentElement.insertBefore(filters, table); table.parentElement.classList.add(`table-${table.classList.contains('merch') ? 'merch-' : ''}section`); filterChangeEvent(); - setAriaLabelsForElements(colSelect0, colSelect1); + setAriaLabelsForElements([colSelect0, colSelect1]); } }; diff --git a/test/blocks/table/mocks/placeholders.json b/test/blocks/table/mocks/placeholders.json new file mode 100644 index 0000000000..80487985c9 --- /dev/null +++ b/test/blocks/table/mocks/placeholders.json @@ -0,0 +1,8 @@ +{ + "data": [ + { + "key": "choose-table-column", + "value": "choose table column" + } + ] +} \ No newline at end of file diff --git a/test/blocks/table/table.test.js b/test/blocks/table/table.test.js index 8eea4b8086..ca8cd23fc9 100644 --- a/test/blocks/table/table.test.js +++ b/test/blocks/table/table.test.js @@ -2,12 +2,14 @@ import { readFile, sendMouse, sendKeys, resetMouse } from '@web/test-runner-comm import { expect } from 'chai'; import { getConfig, MILO_EVENTS, setConfig } from '../../../libs/utils/utils.js'; import { delay, waitForElement } from '../../helpers/waitfor.js'; +import { replaceKey } from '../../../libs/features/placeholders.js'; document.body.innerHTML = await readFile({ path: './mocks/body.html' }); const { default: init } = await import('../../../libs/blocks/table/table.js'); +const locales = { '': { ietf: 'en-US', tk: 'hah7vzn.css' } }; +const conf = { locales }; +setConfig(conf); const config = getConfig(); -config.env = { locale: { contentRoot: `${window.location.origin}` } }; -setConfig(config); describe('table and tablemetadata', () => { beforeEach(() => { @@ -116,12 +118,14 @@ describe('table and tablemetadata', () => { }); it('should apply aria-label to all selects within .filters on mobile', async () => { + config.locale.contentRoot = '/test/blocks/table/mocks'; window.innerWidth = 375; window.dispatchEvent(new Event('resize')); - await delay(500); + const ariaLabel = await replaceKey('choose-table-column', config); const selectElements = document.querySelectorAll('.filters select'); + selectElements.forEach((selectElement) => { - expect(selectElement.getAttribute('aria-label')).to.equal('choose table column'); + expect(selectElement.getAttribute('aria-label')).to.equal(ariaLabel); }); }); }); From 4d7a01e23dd849e71e7d6cd2d5e7d43ca534deb2 Mon Sep 17 00:00:00 2001 From: Dusan Kosanovic Date: Tue, 21 Jan 2025 14:14:56 +0100 Subject: [PATCH 4/9] [MWPW-165791] move contentRoot to config setup --- test/blocks/table/table.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/blocks/table/table.test.js b/test/blocks/table/table.test.js index ca8cd23fc9..6b0d400cda 100644 --- a/test/blocks/table/table.test.js +++ b/test/blocks/table/table.test.js @@ -7,7 +7,7 @@ import { replaceKey } from '../../../libs/features/placeholders.js'; document.body.innerHTML = await readFile({ path: './mocks/body.html' }); const { default: init } = await import('../../../libs/blocks/table/table.js'); const locales = { '': { ietf: 'en-US', tk: 'hah7vzn.css' } }; -const conf = { locales }; +const conf = { locales, contentRoot: '/test/blocks/table/mocks' }; setConfig(conf); const config = getConfig(); @@ -118,7 +118,6 @@ describe('table and tablemetadata', () => { }); it('should apply aria-label to all selects within .filters on mobile', async () => { - config.locale.contentRoot = '/test/blocks/table/mocks'; window.innerWidth = 375; window.dispatchEvent(new Event('resize')); const ariaLabel = await replaceKey('choose-table-column', config); From 9342f3bde54cc9c846de32ff3a5b1ef09349dace Mon Sep 17 00:00:00 2001 From: Dusan Kosanovic Date: Tue, 21 Jan 2025 18:18:43 +0100 Subject: [PATCH 5/9] [MWPW-165791] change of approach for cell, expandable icon a11y fixed --- libs/blocks/table/table.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/libs/blocks/table/table.js b/libs/blocks/table/table.js index c8f285a192..e99247aced 100644 --- a/libs/blocks/table/table.js +++ b/libs/blocks/table/table.js @@ -64,7 +64,7 @@ function handleHeading(table, headingCols) { }); const headingContent = createTag('div', { class: 'heading-content' }); - const headingButton = createTag('div', { class: 'heading-button', role: 'cell' }); + const headingButton = createTag('div', { class: 'heading-button' }); [...elements].forEach((e) => { if (e.classList.contains('pricing') && isPriceBottom) headingButton.appendChild(e); @@ -91,10 +91,9 @@ function handleHeading(table, headingCols) { const describedBy = `${headerBody?.id ?? ''} ${headerPricing?.id ?? ''}`.trim(); trackingHeader.setAttribute('aria-describedby', describedBy); - col.removeAttribute('role'); + col.setAttribute('role', 'columnheader'); } - nodeToApplyRoleScope.setAttribute('role', 'columnheader'); nodeToApplyRoleScope.setAttribute('scope', 'col'); }); } @@ -154,6 +153,15 @@ function handleAddOnContent(table) { table.addEventListener('mas:resolved', debounce(() => { handleEqualHeight(table, '.row-heading'); })); } +async function setAriaLabelsForExpandableIcons() { + const config = getConfig(); + const ariaLabel = await replaceKey('toggle-row', config); + const icons = document.querySelectorAll('.icon.expand[role="button"]'); + icons.forEach((icon) => { + icon.setAttribute('aria-label', ariaLabel); + }); +} + function handleHighlight(table) { const isHighlightTable = table.classList.contains('highlight'); const firstRow = table.querySelector('.row-1'); @@ -256,7 +264,7 @@ function handleSection(sectionParams) { } if (isCollapseTable) { - const iconTag = createTag('span', { class: 'icon expand' }); + const iconTag = createTag('span', { class: 'icon expand', role: 'button' }); sectionHeadTitle.appendChild(iconTag); if (expandSection) { @@ -577,6 +585,7 @@ export default function init(el) { }); handleHighlight(el); + setAriaLabelsForExpandableIcons(); if (isMerch) formatMerchTable(el); let isDecorated = false; From ad81ecbec9d3878c03c896c638e967df5f0bc7de Mon Sep 17 00:00:00 2001 From: Dusan Kosanovic Date: Wed, 22 Jan 2025 13:19:25 +0100 Subject: [PATCH 6/9] [MWPW-165791] adding end of file --- test/blocks/table/mocks/placeholders.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/blocks/table/mocks/placeholders.json b/test/blocks/table/mocks/placeholders.json index 80487985c9..1ff68100ae 100644 --- a/test/blocks/table/mocks/placeholders.json +++ b/test/blocks/table/mocks/placeholders.json @@ -5,4 +5,4 @@ "value": "choose table column" } ] -} \ No newline at end of file +} From 1879c4e49458d60a88531770d9faff68ed8ac11d Mon Sep 17 00:00:00 2001 From: Dusan Kosanovic Date: Fri, 24 Jan 2025 14:00:18 +0100 Subject: [PATCH 7/9] [MWPW-165791] aria label code optimization --- libs/blocks/table/table.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/libs/blocks/table/table.js b/libs/blocks/table/table.js index e99247aced..34a55d4b10 100644 --- a/libs/blocks/table/table.js +++ b/libs/blocks/table/table.js @@ -2,7 +2,7 @@ import { createTag, getConfig, MILO_EVENTS } from '../../utils/utils.js'; import { decorateButtons } from '../../utils/decorate.js'; import { debounce } from '../../utils/action.js'; -import { replaceKey } from '../../features/placeholders.js'; +import { replaceKey, replaceKeyArray } from '../../features/placeholders.js'; const DESKTOP_SIZE = 900; const MOBILE_SIZE = 768; @@ -153,12 +153,22 @@ function handleAddOnContent(table) { table.addEventListener('mas:resolved', debounce(() => { handleEqualHeight(table, '.row-heading'); })); } -async function setAriaLabelsForExpandableIcons() { +async function setAriaLabelForIcons(el) { const config = getConfig(); - const ariaLabel = await replaceKey('toggle-row', config); - const icons = document.querySelectorAll('.icon.expand[role="button"]'); - icons.forEach((icon) => { - icon.setAttribute('aria-label', ariaLabel); + + const expendableIcons = el.querySelectorAll('.icon.expand[role="button"]'); + const selectFilters = document.querySelectorAll('.filters .filter'); + const ariaLabelElements = [...selectFilters, ...expendableIcons]; + + if (!ariaLabelElements.length) { + return; + } + + const ariaLabels = await replaceKeyArray(['toggle-row', 'choose-table-column'], config); + + ariaLabelElements.forEach((element) => { + const labelIndex = element.classList.contains('filter') ? 1 : 0; + element.setAttribute('aria-label', ariaLabels[labelIndex]); }); } @@ -457,15 +467,6 @@ function applyStylesBasedOnScreenSize(table, originTable) { if ((!isMerch && !table.querySelector('.col-3')) || (isMerch && !table.querySelector('.col-2'))) return; - async function setAriaLabelsForElements(selectArray) { - const config = getConfig(); - const ariaLabel = await replaceKey('choose-table-column', config); - - selectArray.forEach((item) => { - item.setAttribute('aria-label', ariaLabel); - }); - } - const filterChangeEvent = () => { table.innerHTML = originTable.innerHTML; reAssignEvents(table); @@ -526,7 +527,6 @@ function applyStylesBasedOnScreenSize(table, originTable) { table.parentElement.insertBefore(filters, table); table.parentElement.classList.add(`table-${table.classList.contains('merch') ? 'merch-' : ''}section`); filterChangeEvent(); - setAriaLabelsForElements([colSelect0, colSelect1]); } }; @@ -585,7 +585,6 @@ export default function init(el) { }); handleHighlight(el); - setAriaLabelsForExpandableIcons(); if (isMerch) formatMerchTable(el); let isDecorated = false; @@ -620,6 +619,7 @@ export default function init(el) { }); isDecorated = true; + setAriaLabelForIcons(el); }; window.addEventListener(MILO_EVENTS.DEFERRED, () => { From 79e98092268d4714d9050c6ace7325f52ec9ade5 Mon Sep 17 00:00:00 2001 From: Dusan Kosanovic Date: Fri, 24 Jan 2025 14:01:32 +0100 Subject: [PATCH 8/9] [MWPW-165791] lint issue fix --- libs/blocks/table/table.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/blocks/table/table.js b/libs/blocks/table/table.js index 34a55d4b10..9f8d1e48dc 100644 --- a/libs/blocks/table/table.js +++ b/libs/blocks/table/table.js @@ -2,7 +2,7 @@ import { createTag, getConfig, MILO_EVENTS } from '../../utils/utils.js'; import { decorateButtons } from '../../utils/decorate.js'; import { debounce } from '../../utils/action.js'; -import { replaceKey, replaceKeyArray } from '../../features/placeholders.js'; +import { replaceKeyArray } from '../../features/placeholders.js'; const DESKTOP_SIZE = 900; const MOBILE_SIZE = 768; From 3d358d3d6e1cc3c6f198d08edb72f891d3a38cc3 Mon Sep 17 00:00:00 2001 From: Dusan Kosanovic Date: Fri, 24 Jan 2025 15:32:30 +0100 Subject: [PATCH 9/9] [MWPW-165791] optimization --- libs/blocks/table/table.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/blocks/table/table.js b/libs/blocks/table/table.js index 9f8d1e48dc..5ff71e2da7 100644 --- a/libs/blocks/table/table.js +++ b/libs/blocks/table/table.js @@ -155,9 +155,8 @@ function handleAddOnContent(table) { async function setAriaLabelForIcons(el) { const config = getConfig(); - const expendableIcons = el.querySelectorAll('.icon.expand[role="button"]'); - const selectFilters = document.querySelectorAll('.filters .filter'); + const selectFilters = el.parentElement.querySelectorAll('.filters .filter'); const ariaLabelElements = [...selectFilters, ...expendableIcons]; if (!ariaLabelElements.length) {