✨(frontend) fix major accessibility issues found by wave and axe
improves a11y by fixing multiple critical validation errors Signed-off-by: Cyril <c.gromoff@gmail.com>
This commit is contained in:
@@ -20,6 +20,9 @@ and this project adheres to
|
|||||||
|
|
||||||
- 🔒️(backend) configure throttle on every viewsets #1343
|
- 🔒️(backend) configure throttle on every viewsets #1343
|
||||||
- ⬆️ Bump eslint to V9 #1071
|
- ⬆️ Bump eslint to V9 #1071
|
||||||
|
- ♿(frontend) improve accessibility:
|
||||||
|
- ♿(frontend) fix major accessibility issues reported by wave and axe #1344
|
||||||
|
- #1341
|
||||||
|
|
||||||
## [3.6.0] - 2025-09-04
|
## [3.6.0] - 2025-09-04
|
||||||
|
|
||||||
|
|||||||
@@ -226,9 +226,13 @@ test.describe('Doc Editor', () => {
|
|||||||
await editor.fill('Hello World Doc persisted 2');
|
await editor.fill('Hello World Doc persisted 2');
|
||||||
await expect(editor.getByText('Hello World Doc persisted 2')).toBeVisible();
|
await expect(editor.getByText('Hello World Doc persisted 2')).toBeVisible();
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
const urlDoc = page.url();
|
const urlDoc = page.url();
|
||||||
await page.goto(urlDoc);
|
await page.goto(urlDoc);
|
||||||
|
|
||||||
|
// Wait for editor to load
|
||||||
|
await expect(editor).toBeVisible();
|
||||||
await expect(editor.getByText('Hello World Doc persisted 2')).toBeVisible();
|
await expect(editor.getByText('Hello World Doc persisted 2')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -80,9 +80,7 @@ test.describe('Documents Grid mobile', () => {
|
|||||||
hasText: 'My mocked document',
|
hasText: 'My mocked document',
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(
|
await expect(row.getByTestId('doc-title')).toHaveText('My mocked document');
|
||||||
row.locator('[aria-describedby="doc-title"]').nth(0),
|
|
||||||
).toHaveText('My mocked document');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -295,7 +293,7 @@ test.describe('Documents Grid', () => {
|
|||||||
docs = result.results as SmallDoc[];
|
docs = result.results as SmallDoc[];
|
||||||
|
|
||||||
await expect(page.getByTestId('grid-loader')).toBeHidden();
|
await expect(page.getByTestId('grid-loader')).toBeHidden();
|
||||||
await expect(page.locator('h4').getByText('All docs')).toBeVisible();
|
await expect(page.locator('h2').getByText('All docs')).toBeVisible();
|
||||||
|
|
||||||
const thead = page.getByTestId('docs-grid-header');
|
const thead = page.getByTestId('docs-grid-header');
|
||||||
await expect(thead.getByText(/Name/i)).toBeVisible();
|
await expect(thead.getByText(/Name/i)).toBeVisible();
|
||||||
|
|||||||
@@ -173,12 +173,13 @@ test.describe('Document search', () => {
|
|||||||
.getByRole('combobox', { name: 'Quick search input' })
|
.getByRole('combobox', { name: 'Quick search input' })
|
||||||
.fill('sub page search');
|
.fill('sub page search');
|
||||||
|
|
||||||
// Expect to find the first doc
|
// Expect to find the first and second docs in the results list
|
||||||
|
const resultsList = page.getByRole('listbox');
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('presentation').getByLabel(firstDocTitle),
|
resultsList.getByRole('option', { name: firstDocTitle }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('presentation').getByLabel(secondDocTitle),
|
resultsList.getByRole('option', { name: secondDocTitle }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'close' }).click();
|
await page.getByRole('button', { name: 'close' }).click();
|
||||||
@@ -195,14 +196,15 @@ test.describe('Document search', () => {
|
|||||||
.fill('second');
|
.fill('second');
|
||||||
|
|
||||||
// Now there is a sub page - expect to have the focus on the current doc
|
// Now there is a sub page - expect to have the focus on the current doc
|
||||||
|
const updatedResultsList = page.getByRole('listbox');
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('presentation').getByLabel(secondDocTitle),
|
updatedResultsList.getByRole('option', { name: secondDocTitle }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('presentation').getByLabel(secondChildDocTitle),
|
updatedResultsList.getByRole('option', { name: secondChildDocTitle }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('presentation').getByLabel(firstDocTitle),
|
updatedResultsList.getByRole('option', { name: firstDocTitle }),
|
||||||
).toBeHidden();
|
).toBeHidden();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ export const goToGridDoc = async (
|
|||||||
|
|
||||||
await expect(row).toBeVisible();
|
await expect(row).toBeVisible();
|
||||||
|
|
||||||
const docTitleContent = row.locator('[aria-describedby="doc-title"]').first();
|
const docTitleContent = row.getByTestId('doc-title').first();
|
||||||
const docTitle = await docTitleContent.textContent();
|
const docTitle = await docTitleContent.textContent();
|
||||||
expect(docTitle).toBeDefined();
|
expect(docTitle).toBeDefined();
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ export const DropdownMenu = ({
|
|||||||
$direction="row"
|
$direction="row"
|
||||||
$align="center"
|
$align="center"
|
||||||
$position="relative"
|
$position="relative"
|
||||||
aria-controls="menu"
|
|
||||||
>
|
>
|
||||||
<Box>{children}</Box>
|
<Box>{children}</Box>
|
||||||
<Icon
|
<Icon
|
||||||
@@ -125,9 +124,7 @@ export const DropdownMenu = ({
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box ref={blockButtonRef} aria-controls="menu">
|
<Box ref={blockButtonRef}>{children}</Box>
|
||||||
{children}
|
|
||||||
</Box>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -82,12 +82,11 @@ export const SimpleDocItem = ({
|
|||||||
</Box>
|
</Box>
|
||||||
<Box $justify="center" $overflow="auto">
|
<Box $justify="center" $overflow="auto">
|
||||||
<Text
|
<Text
|
||||||
aria-describedby="doc-title"
|
|
||||||
aria-label={doc.title || untitledDocument}
|
|
||||||
$size="sm"
|
$size="sm"
|
||||||
$variation="1000"
|
$variation="1000"
|
||||||
$weight="500"
|
$weight="500"
|
||||||
$css={ItemTextCss}
|
$css={ItemTextCss}
|
||||||
|
data-testid="doc-title"
|
||||||
>
|
>
|
||||||
{displayTitle}
|
{displayTitle}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ export const DocsGrid = ({
|
|||||||
>
|
>
|
||||||
<DocsGridLoader isLoading={isRefetching || loading} />
|
<DocsGridLoader isLoading={isRefetching || loading} />
|
||||||
<Card
|
<Card
|
||||||
role="grid"
|
|
||||||
data-testid="docs-grid"
|
data-testid="docs-grid"
|
||||||
$height="100%"
|
$height="100%"
|
||||||
$width="100%"
|
$width="100%"
|
||||||
@@ -84,7 +83,7 @@ export const DocsGrid = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
as="h4"
|
as="h2"
|
||||||
$size="h4"
|
$size="h4"
|
||||||
$variation="1000"
|
$variation="1000"
|
||||||
$margin={{ top: '0px', bottom: '10px' }}
|
$margin={{ top: '0px', bottom: '10px' }}
|
||||||
@@ -101,32 +100,40 @@ export const DocsGrid = ({
|
|||||||
)}
|
)}
|
||||||
{hasDocs && (
|
{hasDocs && (
|
||||||
<Box $gap="6px" $overflow="auto">
|
<Box $gap="6px" $overflow="auto">
|
||||||
|
<Box role="grid">
|
||||||
|
<Box role="rowgroup">
|
||||||
<Box
|
<Box
|
||||||
$direction="row"
|
$direction="row"
|
||||||
$padding={{ horizontal: 'xs' }}
|
$padding={{ horizontal: 'xs' }}
|
||||||
$gap="10px"
|
$gap="10px"
|
||||||
data-testid="docs-grid-header"
|
data-testid="docs-grid-header"
|
||||||
|
role="row"
|
||||||
>
|
>
|
||||||
<Box $flex={flexLeft} $padding="3xs">
|
<Box $flex={flexLeft} $padding="3xs" role="columnheader">
|
||||||
<Text $size="xs" $variation="600" $weight="500">
|
<Text $size="xs" $variation="600" $weight="500">
|
||||||
{t('Name')}
|
{t('Name')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{isDesktop && (
|
{isDesktop && (
|
||||||
<Box $flex={flexRight} $padding={{ vertical: '3xs' }}>
|
<Box
|
||||||
|
$flex={flexRight}
|
||||||
|
$padding={{ vertical: '3xs' }}
|
||||||
|
role="columnheader"
|
||||||
|
>
|
||||||
<Text $size="xs" $weight="500" $variation="600">
|
<Text $size="xs" $weight="500" $variation="600">
|
||||||
{t('Updated at')}
|
{t('Updated at')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box role="rowgroup">
|
||||||
{isDesktop ? (
|
{isDesktop ? (
|
||||||
<DraggableDocGridContentList docs={docs} />
|
<DraggableDocGridContentList docs={docs} />
|
||||||
) : (
|
) : (
|
||||||
<DocGridContentList docs={docs} />
|
<DocGridContentList docs={docs} />
|
||||||
)}
|
)}
|
||||||
|
</Box>
|
||||||
{hasNextPage && !loading && (
|
{hasNextPage && !loading && (
|
||||||
<InView
|
<InView
|
||||||
data-testid="infinite-scroll-trigger"
|
data-testid="infinite-scroll-trigger"
|
||||||
@@ -144,6 +151,7 @@ export const DocsGrid = ({
|
|||||||
</InView>
|
</InView>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -52,10 +52,18 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
className="--docs--doc-grid-item"
|
className="--docs--doc-grid-item"
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
$flex={flexLeft}
|
||||||
|
role="gridcell"
|
||||||
|
$css={css`
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
`}
|
||||||
>
|
>
|
||||||
<StyledLink
|
<StyledLink
|
||||||
$css={css`
|
$css={css`
|
||||||
flex: ${flexLeft};
|
width: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
`}
|
`}
|
||||||
@@ -114,6 +122,7 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
$flex={flexRight}
|
$flex={flexRight}
|
||||||
@@ -121,6 +130,7 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
|
|||||||
$align="center"
|
$align="center"
|
||||||
$justify={isDesktop ? 'space-between' : 'flex-end'}
|
$justify={isDesktop ? 'space-between' : 'flex-end'}
|
||||||
$gap="32px"
|
$gap="32px"
|
||||||
|
role="gridcell"
|
||||||
>
|
>
|
||||||
{isDesktop && (
|
{isDesktop && (
|
||||||
<StyledLink href={`/docs/${doc.id}`}>
|
<StyledLink href={`/docs/${doc.id}`}>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Loader } from '@openfun/cunningham-react';
|
import { Loader } from '@openfun/cunningham-react';
|
||||||
import { createGlobalStyle } from 'styled-components';
|
import { createGlobalStyle, css } from 'styled-components';
|
||||||
|
|
||||||
import { Box } from '@/components';
|
import { Box } from '@/components';
|
||||||
|
|
||||||
@@ -32,6 +32,9 @@ export const DocsGridLoader = ({ isLoading }: DocsGridLoaderProps) => {
|
|||||||
$zIndex={998}
|
$zIndex={998}
|
||||||
$position="absolute"
|
$position="absolute"
|
||||||
className="--docs--doc-grid-loader"
|
className="--docs--doc-grid-loader"
|
||||||
|
$css={css`
|
||||||
|
pointer-events: none;
|
||||||
|
`}
|
||||||
>
|
>
|
||||||
<Loader />
|
<Loader />
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export const Draggable = <T,>(props: DraggableProps<T>) => {
|
|||||||
{...attributes}
|
{...attributes}
|
||||||
data-testid={`draggable-doc-${props.id}`}
|
data-testid={`draggable-doc-${props.id}`}
|
||||||
className="--docs--grid-draggable"
|
className="--docs--grid-draggable"
|
||||||
|
role="presentation"
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export const Droppable = ({
|
|||||||
<Box
|
<Box
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
data-testid={`droppable-doc-${id}`}
|
data-testid={`droppable-doc-${id}`}
|
||||||
|
role="presentation"
|
||||||
$css={css`
|
$css={css`
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: ${enableHover
|
background-color: ${enableHover
|
||||||
|
|||||||
@@ -44,17 +44,21 @@ export const LeftPanelFavorites = () => {
|
|||||||
>
|
>
|
||||||
{t('Pinned documents')}
|
{t('Pinned documents')}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Box>
|
||||||
|
<Box as="ul" $padding="none">
|
||||||
|
{favoriteDocs.map((doc) => (
|
||||||
|
<LeftPanelFavoriteItem key={doc.id} doc={doc} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
{docs.hasNextPage && (
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
as="ul"
|
|
||||||
hasMore={docs.hasNextPage}
|
hasMore={docs.hasNextPage}
|
||||||
isLoading={docs.isFetchingNextPage}
|
isLoading={docs.isFetchingNextPage}
|
||||||
next={() => void docs.fetchNextPage()}
|
next={() => void docs.fetchNextPage()}
|
||||||
$padding="none"
|
$padding="none"
|
||||||
>
|
/>
|
||||||
{favoriteDocs.map((doc) => (
|
)}
|
||||||
<LeftPanelFavoriteItem key={doc.id} doc={doc} />
|
</Box>
|
||||||
))}
|
|
||||||
</InfiniteScroll>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user