diff --git a/src/locales/en.json b/src/locales/en.json index e33cdef8bbb..a8602ad35e0 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -141,6 +141,12 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", + "pad.importExport.exportetherpada.title": "Export as Etherpad", + "pad.importExport.exporthtmla.title": "Export as HTML", + "pad.importExport.exportplaina.title": "Export as plain text", + "pad.importExport.exportworda.title": "Export as Microsoft Word", + "pad.importExport.exportpdfa.title": "Export as PDF", + "pad.importExport.exportopena.title": "Export as ODF (Open Document Format)", "pad.importExport.noConverter.innerHTML": "You can only import from plain text or HTML formats. For more advanced import features, please install LibreOffice.", "pad.modals.connected": "Connected.", diff --git a/src/templates/pad.html b/src/templates/pad.html index 5212b004606..8a4e3ac3c80 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -323,23 +323,23 @@

<% e.begin_block("exportColumn"); %> - - + + - - + + - - + + - - + + - - + + - - + + <% e.end_block(); %>
diff --git a/src/tests/frontend-new/specs/a11y_dialogs.spec.ts b/src/tests/frontend-new/specs/a11y_dialogs.spec.ts index c5891ae1f06..ec6406a3205 100644 --- a/src/tests/frontend-new/specs/a11y_dialogs.spec.ts +++ b/src/tests/frontend-new/specs/a11y_dialogs.spec.ts @@ -1,6 +1,12 @@ import {expect, test} from '@playwright/test'; import {goToNewPad} from '../helper/padHelper'; +// Pin browser locale so html10n picks the English bundle. Several +// assertions in this file compare against specific English strings +// (e.g. "Close chat", "Export as Etherpad"); without this, translatewiki +// updates would localise those strings and break the suite. +test.use({locale: 'en-US'}); + test.beforeEach(async ({page}) => { await goToNewPad(page); }); @@ -65,28 +71,31 @@ test('users popup closes on Escape even when focus is outside the popup', async await expect(dialog).not.toHaveClass(/popup-show/); }); -test('export links have an accessible name from their localized content', async ({page}) => { +test('export links have a localized aria-label and matching title', async ({page}) => { await page.locator('button[data-l10n-id="pad.toolbar.import_export.title"]').click(); // The Word/PDF/ODF export links are removed client-side by pad_impexp.ts // when soffice is not configured, so only assert on links that the - // environment actually renders. For the ones that are present, their - // accessible name comes from the localized child span (data-l10n-id - // pad.importExport.exportetherpad etc.), not a hard-coded English - // aria-label. Assert the visible text is non-empty, which is what a - // screen reader will announce. - const ids = [ - '#exportetherpada', - '#exporthtmla', - '#exportplaina', - '#exportworda', - '#exportpdfa', - '#exportopena', + // environment actually renders. Each anchor carries + // data-l10n-id="pad.importExport.exporta.title", which html10n + // expands into both `title` and `aria-label` from the same translation + // (e.g. "Export as Etherpad"). The inner icon span is aria-hidden so a + // screen reader announces the anchor's label once, not twice. + const cases: Array<[string, string]> = [ + ['#exportetherpada', 'Export as Etherpad'], + ['#exporthtmla', 'Export as HTML'], + ['#exportplaina', 'Export as plain text'], + ['#exportworda', 'Export as Microsoft Word'], + ['#exportpdfa', 'Export as PDF'], + ['#exportopena', 'Export as ODF (Open Document Format)'], ]; - for (const id of ids) { + for (const [id, expected] of cases) { const locator = page.locator(id); if ((await locator.count()) === 0) continue; - const text = (await locator.innerText()).trim(); - expect(text.length).toBeGreaterThan(0); + await expect(locator).toHaveAttribute('aria-label', expected); + await expect(locator).toHaveAttribute('title', expected); + await expect(locator).toHaveAttribute('rel', 'noopener'); + const innerSpan = locator.locator('span.exporttype'); + await expect(innerSpan).toHaveAttribute('aria-hidden', 'true'); } });