Skip to content

Commit abf75ae

Browse files
committed
✨(frontend) make delete buttons nvda-accessible
add aria-labels and include close button in title prop so NVDA announces actions Signed-off-by: Cyril <c.gromoff@gmail.com>
1 parent 9135dff commit abf75ae

File tree

15 files changed

+168
-101
lines changed

15 files changed

+168
-101
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ and this project adheres to
2727
- ♿️(frontend) keyboard interaction with menu #1244
2828
- ♿(frontend) improve header accessibility #1270
2929
- ♿(frontend) improve accessibility for decorative images in editor #1282
30+
- #1281
3031
- ♻️(backend) fallback to email identifier when no name #1298
3132
- 🐛(backend) allow ASCII characters in user sub field #1295
3233
- ⚡️(frontend) improve fallback width calculation #1333

src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts

Lines changed: 14 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,7 @@ test.describe('Doc Export', () => {
2929
})
3030
.click();
3131

32-
await expect(
33-
page
34-
.locator('div')
35-
.filter({ hasText: /^Download$/ })
36-
.first(),
37-
).toBeVisible();
32+
await expect(page.getByTestId('modal-export-title')).toBeVisible();
3833
await expect(
3934
page.getByText('Download your document in a .docx or .pdf format.'),
4035
).toBeVisible();
@@ -45,7 +40,7 @@ test.describe('Doc Export', () => {
4540
await expect(
4641
page.getByRole('button', { name: 'Close the modal' }),
4742
).toBeVisible();
48-
await expect(page.getByRole('button', { name: 'Download' })).toBeVisible();
43+
await expect(page.getByTestId('modal-download-button')).toBeVisible();
4944
});
5045

5146
test('it exports the doc with pdf line break', async ({
@@ -136,23 +131,13 @@ test.describe('Doc Export', () => {
136131
await page.getByRole('combobox', { name: 'Format' }).click();
137132
await page.getByRole('option', { name: 'Docx' }).click();
138133

139-
await expect(
140-
page.getByRole('button', {
141-
name: 'Download',
142-
exact: true,
143-
}),
144-
).toBeVisible();
134+
await expect(page.getByTestId('modal-download-button')).toBeVisible();
145135

146136
const downloadPromise = page.waitForEvent('download', (download) => {
147137
return download.suggestedFilename().includes(`${randomDoc}.docx`);
148138
});
149139

150-
void page
151-
.getByRole('button', {
152-
name: 'Download',
153-
exact: true,
154-
})
155-
.click();
140+
void page.getByTestId('modal-download-button').click();
156141

157142
const download = await downloadPromise;
158143
expect(download.suggestedFilename()).toBe(`${randomDoc}.docx`);
@@ -218,11 +203,7 @@ test.describe('Doc Export', () => {
218203

219204
await new Promise((resolve) => setTimeout(resolve, 1000));
220205

221-
await expect(
222-
page.getByRole('button', {
223-
name: 'Download',
224-
}),
225-
).toBeVisible();
206+
await expect(page.getByTestId('modal-download-button')).toBeVisible();
226207

227208
const responseCorsPromise = page.waitForResponse(
228209
(response) =>
@@ -233,11 +214,7 @@ test.describe('Doc Export', () => {
233214
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
234215
});
235216

236-
void page
237-
.getByRole('button', {
238-
name: 'Download',
239-
})
240-
.click();
217+
void page.getByTestId('modal-download-button').click();
241218

242219
const responseCors = await responseCorsPromise;
243220
expect(responseCors.ok()).toBe(true);
@@ -279,21 +256,13 @@ test.describe('Doc Export', () => {
279256
})
280257
.click();
281258

282-
await expect(
283-
page.getByRole('button', {
284-
name: 'Download',
285-
}),
286-
).toBeVisible();
259+
await expect(page.getByTestId('modal-download-button')).toBeVisible();
287260

288261
const downloadPromise = page.waitForEvent('download', (download) => {
289262
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
290263
});
291264

292-
void page
293-
.getByRole('button', {
294-
name: 'Download',
295-
})
296-
.click();
265+
void page.getByTestId('modal-download-button').click();
297266

298267
const download = await downloadPromise;
299268
expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
@@ -329,23 +298,13 @@ test.describe('Doc Export', () => {
329298
})
330299
.click();
331300

332-
await expect(
333-
page.getByRole('button', {
334-
name: 'Download',
335-
exact: true,
336-
}),
337-
).toBeVisible();
301+
await expect(page.getByTestId('modal-download-button')).toBeVisible();
338302

339303
const downloadPromise = page.waitForEvent('download', (download) => {
340304
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
341305
});
342306

343-
void page
344-
.getByRole('button', {
345-
name: 'Download',
346-
exact: true,
347-
})
348-
.click();
307+
void page.getByTestId('modal-download-button').click();
349308

350309
const download = await downloadPromise;
351310
expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
@@ -391,23 +350,13 @@ test.describe('Doc Export', () => {
391350
})
392351
.click();
393352

394-
await expect(
395-
page.getByRole('button', {
396-
name: 'Download',
397-
exact: true,
398-
}),
399-
).toBeVisible();
353+
await expect(page.getByTestId('modal-download-button')).toBeVisible();
400354

401355
const downloadPromise = page.waitForEvent('download', (download) => {
402356
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
403357
});
404358

405-
void page
406-
.getByRole('button', {
407-
name: 'Download',
408-
exact: true,
409-
})
410-
.click();
359+
void page.getByTestId('modal-download-button').click();
411360

412361
const download = await downloadPromise;
413362
expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
@@ -469,12 +418,7 @@ test.describe('Doc Export', () => {
469418
return download.suggestedFilename().includes(`${randomDocFrench}.pdf`);
470419
});
471420

472-
void page
473-
.getByRole('button', {
474-
name: 'Télécharger',
475-
exact: true,
476-
})
477-
.click();
421+
void page.getByTestId('modal-download-button').click();
478422

479423
const download = await downloadPromise;
480424
expect(download.suggestedFilename()).toBe(`${randomDocFrench}.pdf`);
@@ -536,12 +480,7 @@ test.describe('Doc Export', () => {
536480
})
537481
.click();
538482

539-
void page
540-
.getByRole('button', {
541-
name: 'Download',
542-
exact: true,
543-
})
544-
.click();
483+
void page.getByTestId('modal-download-button').click();
545484

546485
const download = await downloadPromise;
547486
expect(download.suggestedFilename()).toBe(`${docChild}.pdf`);

src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ test.describe('Doc Header', () => {
155155

156156
await page.getByRole('button', { name: 'Share' }).click();
157157

158-
const shareModal = page.getByLabel('Share modal');
158+
const shareModal = page.getByRole('dialog', {
159+
name: 'Share modal content',
160+
});
159161
await expect(shareModal).toBeVisible();
160162
await expect(page.getByText('Share the document')).toBeVisible();
161163

@@ -581,7 +583,10 @@ test.describe('Documents Header mobile', () => {
581583
await page.getByLabel('Open the document options').click();
582584
await page.getByLabel('Share').click();
583585

584-
await expect(page.getByLabel('Share modal')).toBeVisible();
586+
const shareModal = page.getByRole('dialog', {
587+
name: 'Share modal content',
588+
});
589+
await expect(shareModal).toBeVisible();
585590
await page.getByRole('button', { name: 'close' }).click();
586591
await expect(page.getByLabel('Share modal')).toBeHidden();
587592
});

src/frontend/apps/e2e/__tests__/app-impress/home.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ test.describe('Home page', () => {
7272
await page.waitForLoadState('domcontentloaded');
7373

7474
// Wait a bit more for the responsive store to be initialized
75+
// eslint-disable-next-line playwright/no-wait-for-timeout
7576
await page.waitForTimeout(500);
7677

7778
// Check header content

src/frontend/apps/impress/src/components/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export * from './AlertModal';
1+
export * from './modal/AlertModal';
22
export * from './Box';
33
export * from './BoxButton';
44
export * from './Card';
@@ -9,7 +9,7 @@ export * from './Icon';
99
export * from './InfiniteScroll';
1010
export * from './Link';
1111
export * from './Loading';
12-
export * from './SideModal';
12+
export * from './modal/SideModal';
1313
export * from './separators';
1414
export * from './Text';
1515
export * from './TextErrors';

src/frontend/apps/impress/src/components/AlertModal.tsx renamed to src/frontend/apps/impress/src/components/modal/AlertModal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { Button, Modal, ModalSize } from '@openfun/cunningham-react';
22
import { ReactNode } from 'react';
33
import { useTranslation } from 'react-i18next';
44

5-
import { Box } from './Box';
6-
import { Text } from './Text';
5+
import { Box } from '../Box';
6+
import { Text } from '../Text';
77

88
export type AlertModalProps = {
99
description: ReactNode;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Button, type ButtonProps } from '@openfun/cunningham-react';
2+
import React from 'react';
3+
4+
import { Box } from '@/components';
5+
6+
const ButtonCloseModal = (props: ButtonProps) => {
7+
return (
8+
<Button
9+
type="button"
10+
size="small"
11+
color="primary-text"
12+
icon={
13+
<Box as="span" aria-hidden="true" className="material-icons-filled">
14+
close
15+
</Box>
16+
}
17+
{...props}
18+
/>
19+
);
20+
};
21+
22+
export default ButtonCloseModal;

src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { useTranslation } from 'react-i18next';
1616
import { css } from 'styled-components';
1717

1818
import { Box, Text } from '@/components';
19+
import ButtonCloseModal from '@/components/modal/ButtonCloseModal';
1920
import { useEditorStore } from '@/docs/doc-editor';
2021
import { Doc, useTrans } from '@/docs/doc-management';
2122

@@ -131,6 +132,7 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
131132
isOpen
132133
closeOnClickOutside
133134
onClose={() => onClose()}
135+
hideCloseButton
134136
rightActions={
135137
<>
136138
<Button
@@ -148,16 +150,34 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
148150
fullWidth
149151
onClick={() => void onSubmit()}
150152
disabled={isExporting}
153+
data-testid="modal-download-button"
151154
>
152155
{t('Download')}
153156
</Button>
154157
</>
155158
}
156159
size={ModalSize.MEDIUM}
157160
title={
158-
<Text $size="h6" $variation="1000" $align="flex-start">
159-
{t('Download')}
160-
</Text>
161+
<Box
162+
$direction="row"
163+
$justify="space-between"
164+
$align="center"
165+
$width="100%"
166+
>
167+
<Text
168+
$size="h6"
169+
$variation="1000"
170+
$align="flex-start"
171+
data-testid="modal-export-title"
172+
>
173+
{t('Download')}
174+
</Text>
175+
<ButtonCloseModal
176+
aria-label={t('Close the download modal')}
177+
onClick={() => onClose()}
178+
disabled={isExporting}
179+
/>
180+
</Box>
161181
}
162182
>
163183
<Box

src/frontend/apps/impress/src/features/docs/doc-management/components/ModalRemoveDoc.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { useRouter } from 'next/router';
1010
import { Trans, useTranslation } from 'react-i18next';
1111

1212
import { Box, Text, TextErrors } from '@/components';
13+
import ButtonCloseModal from '@/components/modal/ButtonCloseModal';
1314

1415
import { useRemoveDoc } from '../api/useRemoveDoc';
1516
import { Doc } from '../types';
@@ -53,11 +54,12 @@ export const ModalRemoveDoc = ({
5354
<Modal
5455
isOpen
5556
closeOnClickOutside
57+
hideCloseButton
5658
onClose={() => onClose()}
5759
rightActions={
5860
<>
5961
<Button
60-
aria-label={t('Close the modal')}
62+
aria-label={t('Close the delete modal')}
6163
color="secondary"
6264
fullWidth
6365
onClick={() => onClose()}
@@ -80,15 +82,26 @@ export const ModalRemoveDoc = ({
8082
}
8183
size={ModalSize.MEDIUM}
8284
title={
83-
<Text
84-
$size="h6"
85-
as="h6"
86-
$margin={{ all: '0' }}
87-
$align="flex-start"
88-
$variation="1000"
85+
<Box
86+
$direction="row"
87+
$justify="space-between"
88+
$align="center"
89+
$width="100%"
8990
>
90-
{t('Delete a doc')}
91-
</Text>
91+
<Text
92+
$size="h6"
93+
as="h6"
94+
$margin={{ all: '0' }}
95+
$align="flex-start"
96+
$variation="1000"
97+
>
98+
{t('Delete a doc')}
99+
</Text>
100+
<ButtonCloseModal
101+
aria-label={t('Close the delete modal')}
102+
onClick={() => onClose()}
103+
/>
104+
</Box>
92105
}
93106
>
94107
<Box

0 commit comments

Comments
 (0)