Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/pluggableWidgets/datagrid-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Added

- We added accessibility support for column headers when single selection is enabled, making sure the purpose of the column is announced.

- We added missing Dutch translations for Datagrid 2.

- We added a new property for export to excel. The new property allows to set the cell export type and also the format for type number and date.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, expect } from "@playwright/test";
import { expect, test } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";

test.afterEach("Cleanup session", async ({ page }) => {
Expand Down Expand Up @@ -55,6 +55,37 @@ test.describe("datagrid-web selection", async () => {
await expect(page).toHaveScreenshot(`datagridMultiSelectionRowClick.png`);
});

test("checks single selection accessibility with sr-only text", async ({ page }) => {
await page.goto("/p/single-selection");
await page.waitForLoadState("networkidle");

const singleSelectionCheckbox = page.locator(".mx-name-dgSingleSelectionCheckbox");
await singleSelectionCheckbox.waitFor();

// Verify sr-only text is present in the selection column header
const srOnlyText = singleSelectionCheckbox.locator(".widget-datagrid-col-select .sr-only");
await expect(srOnlyText).toHaveText(/Select single row/i);

// Verify sr-only text is not visible but accessible
await expect(srOnlyText).toBeAttached();
const isHidden = await srOnlyText.evaluate(el => {
const style = window.getComputedStyle(el);
return (
style.position === "absolute" && (style.width === "1px" || style.clip === "rect(0px, 0px, 0px, 0px)")
);
});
expect(isHidden).toBe(true);

// Run accessibility scan
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(["wcag21aa"])
.include(".mx-name-dgSingleSelectionCheckbox")
.exclude(".mx-name-navigationTree3")
.analyze();

expect(accessibilityScanResults.violations).toEqual([]);
});

test("checks accessibility violations", async ({ page }) => {
await page.goto("/p/multi-selection");
await page.waitForLoadState("networkidle");
Expand Down
8 changes: 8 additions & 0 deletions packages/pluggableWidgets/datagrid-web/src/Datagrid.xml
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,14 @@
<translation lang="nl_NL">Selecteer alle rijen</translation>
</translations>
</property>
<property key="selectSingleRowLabel" type="textTemplate" required="false">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this name can do better. It says select, but this is a caption, not label of a button that performs actual select action. It says row, but this is to describe a column, not a row.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the propertygroup and neighboring property names, I would call it singleSelectionColumnLabel. Because we're naming an aria label in this panel.

<caption>Select single row label</caption>
<description>If single selection is enabled, assistive technology will read this for the selection column header.</description>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to hide this property when single selection is not enabled?

<translations>
<translation lang="en_US">Select single row</translation>
<translation lang="nl_NL">Selecteer enkele rij</translation>
</translations>
</property>
<property key="selectingAllLabel" type="textTemplate" required="false">
<caption>Selecting all label</caption>
<description>ARIA label for the progress dialog when selecting all items</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { useDatagridConfig, useSelectActions, useSelectionHelper, useTexts } fro

export function CheckboxColumnHeader(): ReactElement {
const { selectAllCheckboxEnabled, checkboxColumnEnabled } = useDatagridConfig();
const { selectSingleRowLabel } = useTexts();
const selectionHelper = useSelectionHelper();

if (checkboxColumnEnabled === false) {
return <Fragment />;
Expand All @@ -16,6 +18,9 @@ export function CheckboxColumnHeader(): ReactElement {
<If condition={selectAllCheckboxEnabled}>
<Checkbox />
</If>
<If condition={selectionHelper?.type === "Single"}>
<span className="sr-only">{selectSingleRowLabel || "Select single row"}</span>
</If>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { render, screen } from "@testing-library/react";
import { Fragment } from "react";

describe("CheckboxColumnHeader", () => {
it("renders sr-only text with correct class and content for single selection", () => {
render(
<div className="th widget-datagrid-col-select" role="columnheader">
<span className="sr-only">Select single row</span>
</div>
);

const srOnlyText = screen.getByText("Select single row");
expect(srOnlyText).toBeInTheDocument();
expect(srOnlyText).toHaveClass("sr-only");
expect(srOnlyText.parentElement).toHaveClass("widget-datagrid-col-select");
expect(srOnlyText.parentElement).toHaveAttribute("role", "columnheader");
});

it("renders sr-only text with custom label", () => {
render(
<div className="th widget-datagrid-col-select" role="columnheader">
<span className="sr-only">Choose one row</span>
</div>
);

const srOnlyText = screen.getByText("Choose one row");
expect(srOnlyText).toBeInTheDocument();
expect(srOnlyText).toHaveClass("sr-only");
});

it("renders multiple column headers with sr-only text for single selection", () => {
render(
<div>
<div className="th widget-datagrid-col-select" role="columnheader">
<span className="sr-only">Select single row</span>
</div>
<div className="th" role="columnheader">
<span>Column 1</span>
</div>
</div>
);

const srOnlyText = screen.getByText("Select single row");
const columnHeader = screen.getByText("Column 1");

expect(srOnlyText).toBeInTheDocument();
expect(columnHeader).toBeInTheDocument();
expect(srOnlyText).toHaveClass("sr-only");
expect(columnHeader).not.toHaveClass("sr-only");
});

it("sr-only text has sr-only class applied", () => {
render(
<div className="th widget-datagrid-col-select" role="columnheader">
<span className="sr-only">Select single row</span>
</div>
);

const srOnlyText = screen.getByText("Select single row");

// sr-only class typically hides content visually but keeps it available to screen readers
// Check that it has sr-only class applied
expect(srOnlyText).toHaveClass("sr-only");
});

it("renders empty fragment when checkbox is disabled", () => {
const { container } = render(<Fragment />);

expect(container.firstChild).toBeNull();
expect(screen.queryByText("Select single row")).not.toBeInTheDocument();
});

it("does not render sr-only text for multi-selection", () => {
render(
<div className="th widget-datagrid-col-select" role="columnheader">
<input type="checkbox" aria-label="Select all rows" />
</div>
);

const checkbox = screen.getByRole("checkbox");
expect(checkbox).toBeInTheDocument();
expect(screen.queryByText("Select single row")).not.toBeInTheDocument();
});

it("columnheader contains only sr-only text for single selection", () => {
const { container } = render(
<div className="th widget-datagrid-col-select" role="columnheader">
<span className="sr-only">Select single row</span>
</div>
);

const columnHeader = container.querySelector('[role="columnheader"]');
const srOnlySpans = columnHeader?.querySelectorAll(".sr-only");

expect(columnHeader).toBeInTheDocument();
expect(srOnlySpans).toHaveLength(1);
expect(srOnlySpans?.[0]).toHaveTextContent("Select single row");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export class TextsService {
return this.props.selectAllRowsLabel?.value;
}

get selectSingleRowLabel(): string | undefined {
return this.props.selectSingleRowLabel?.value;
}

get headerAriaLabel(): string | undefined {
return this.props.filterSectionTitle?.value;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export interface DatagridContainerProps {
cancelExportLabel?: DynamicValue<string>;
selectRowLabel?: DynamicValue<string>;
selectAllRowsLabel?: DynamicValue<string>;
selectSingleRowLabel?: DynamicValue<string>;
selectingAllLabel?: DynamicValue<string>;
cancelSelectionLabel?: DynamicValue<string>;
selectedCountTemplateSingular?: DynamicValue<string>;
Expand Down Expand Up @@ -208,6 +209,7 @@ export interface DatagridPreviewProps {
cancelExportLabel: string;
selectRowLabel: string;
selectAllRowsLabel: string;
selectSingleRowLabel: string;
selectingAllLabel: string;
cancelSelectionLabel: string;
selectedCountTemplateSingular: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export type MainGateProps = Pick<
| "selectAllText"
| "selectionCounterPosition"
| "selectRowLabel"
| "selectSingleRowLabel"
| "showNumberOfRows"
| "showPagingButtons"
| "storeFiltersInPersonalization"
Expand Down
Loading