Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(date): add aria labels to picker navigation buttons and fix other accessibility issues FE-4075 #6368

Merged
merged 4 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
264 changes: 252 additions & 12 deletions cypress/components/date/date.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import {
} from "../../locators/date-input";

import { getDataElementByValue, fieldHelpPreview } from "../../locators";
import { keyCode } from "../../support/helper";
import { KeyIds, keyCode } from "../../support/helper";
import {
verifyRequiredAsteriskForLabel,
assertCssValueIsApproximately,
Expand Down Expand Up @@ -75,7 +75,12 @@ const DDMMYYY_DATE_TO_ENTER_SHORT = "1,7,22";
const MMDDYYYY_DATE_TO_ENTER_SHORT = "7,1,22";
const YYYYMMDD_DATE_TO_ENTER_SHORT = "22,7,1";
const DATE_TO_VERIFY = "2022-05-12";
const keysToTrigger = ["rightarrow", "leftarrow"] as const;
const arrowKeys = [
Copy link
Contributor

Choose a reason for hiding this comment

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

praise: good improvement

"rightarrow",
"leftarrow",
"uparrow",
"downarrow",
] as KeyIds[];

context("Test for DateInput component", () => {
describe("check functionality for DateInput component", () => {
Expand Down Expand Up @@ -222,26 +227,239 @@ context("Test for DateInput component", () => {
}
);

it.each(arrowKeys)(
"should not change the displayed month when %s is pressed and next button is focused",
(key) => {
CypressMountWithProviders(<DateInputCustom />);
dateInputParent().click();
getDataElementByValue("chevron_right").parent().focus();
getDataElementByValue("chevron_right").trigger("keydown", keyCode(key));
dayPickerHeading().should("have.text", "May 2022");
}
);

it.each(arrowKeys)(
"should not change the displayed month when %s is pressed and previous button is focused",
(key) => {
CypressMountWithProviders(<DateInputCustom />);
dateInputParent().click();
getDataElementByValue("chevron_left").parent().focus();
getDataElementByValue("chevron_left").trigger("keydown", keyCode(key));
dayPickerHeading().should("have.text", "May 2022");
}
);

it("should allow a user to tab into the picker and through its controls", () => {
CypressMountWithProviders(<DateInputCustom value="12/12/2022" />);
cy.get("body").tab();
dateInput().should("be.focused");
cy.focused().tab();
getDataElementByValue("chevron_left").parent().should("be.focused");
cy.focused().tab();
getDataElementByValue("chevron_right").parent().should("be.focused");
cy.focused().tab();
cy.get(".DayPicker-Day--selected").should("be.focused");
});

it("should close the picker and focus the next element in the DOM when focus is on a day element and tab pressed", () => {
CypressMountWithProviders(
<>
<DateInputCustom value="12/12/2022" />
<button data-element="foo-button" type="button">
foo
</button>
</>
);
cy.get("body").tab();
dateInput().should("be.focused");
cy.focused().tab();
getDataElementByValue("chevron_left").parent().should("be.focused");
cy.focused().tab();
getDataElementByValue("chevron_right").parent().should("be.focused");
cy.focused().tab();
cy.get(".DayPicker-Day--selected").should("be.focused");
cy.focused().tab();
dayPickerWrapper().should("not.exist");
cy.get('[data-element="foo-button"]').should("be.focused");
});

it("should focus today's date if no day selected when tabbing to day elements", () => {
CypressMountWithProviders(<DateInputCustom value="" />);
cy.get("body").tab();
dateInput().should("be.focused");
cy.focused().tab();
getDataElementByValue("chevron_left").parent().should("be.focused");
cy.focused().tab();
getDataElementByValue("chevron_right").parent().should("be.focused");
cy.focused().tab();
cy.get(".DayPicker-Day--today").should("be.focused");
cy.focused().tab();
dayPickerWrapper().should("not.exist");
});

it("should navigate through the day elements using the arrow keys", () => {
CypressMountWithProviders(<DateInputCustom />);
cy.get("body").tab();
dateInput().should("be.focused");
cy.focused().tab();
cy.focused().tab();
cy.focused().tab();
cy.focused().trigger("keydown", keyCode("downarrow"));
cy.focused().should("have.text", "8");
cy.focused().trigger("keydown", keyCode("downarrow"));
cy.focused().should("have.text", "15");
cy.focused().trigger("keydown", keyCode("leftarrow"));
cy.focused().should("have.text", "14");
cy.focused().trigger("keydown", keyCode("leftarrow"));
cy.focused().should("have.text", "13");
cy.focused().trigger("keydown", keyCode("rightarrow"));
cy.focused().should("have.text", "14");
cy.focused().trigger("keydown", keyCode("rightarrow"));
cy.focused().should("have.text", "15");
cy.focused().trigger("keydown", keyCode("uparrow"));
cy.focused().should("have.text", "8");
cy.focused().trigger("keydown", keyCode("uparrow"));
cy.focused().should("have.text", "1");
});

it("should navigate to the previous month when left arrow pressed on first day element of a month", () => {
CypressMountWithProviders(<DateInputCustom />);
cy.get("body").tab();
dateInput().should("be.focused");
cy.focused().tab();
cy.focused().tab();
cy.focused().tab();
cy.focused().trigger("keydown", keyCode("leftarrow"));
cy.focused().should("have.text", "30");
dayPickerHeading().should("have.text", PREVIOUS_MONTH);
});

it.each([
["24", "1"],
["25", "2"],
["26", "3"],
["27", "4"],
["28", "5"],
["29", "6"],
["30", "7"],
])(
"should navigate to day %s of previous month when up arrow pressed on day %s of first week of current month",
(result, input) => {
CypressMountWithProviders(
<DateInputCustom value={`0${input}/05/2022`} />
);
cy.get("body").tab();
dateInput().should("be.focused");
cy.focused().tab();
cy.focused().tab();
cy.focused().tab();
cy.focused().trigger("keydown", keyCode("uparrow"));
cy.focused().should("have.text", result);
dayPickerHeading().should("have.text", PREVIOUS_MONTH);
}
);

it.each([
["7", "31"],
["6", "30"],
["5", "29"],
["4", "28"],
["3", "27"],
["2", "26"],
["1", "25"],
])(
"should navigate to day %s of next month when down arrow pressed on day %s of last week of current month",
(result, input) => {
CypressMountWithProviders(
<DateInputCustom value={`${input}/05/2022`} />
);
cy.get("body").tab();
dateInput().should("be.focused");
cy.focused().tab();
cy.focused().tab();
cy.focused().tab();
cy.focused().trigger("keydown", keyCode("downarrow"));
cy.focused().should("have.text", result);
dayPickerHeading().should("have.text", NEXT_MONTH);
}
);

it.each(["Enter", "Space"] as KeyIds[])(
"should update the selected date when %s pressed on a day element",
(key) => {
CypressMountWithProviders(<DateInputCustom />);
cy.get("body").tab();
dateInput().should("be.focused");
cy.focused().tab();
cy.focused().tab();
cy.focused().tab();
cy.focused().trigger("keydown", keyCode("leftarrow"));
cy.focused().should("have.text", "30");
cy.focused().trigger("keydown", keyCode(key));
getDataElementByValue("input").should("have.value", "30/04/2022");
}
);

it("should close the picker when escape is pressed and input focused", () => {
CypressMountWithProviders(<DateInputCustom />);
cy.get("body").tab();
dayPickerWrapper().should("exist");

cy.focused().trigger("keydown", keyCode("Esc"));
dayPickerWrapper().should("not.exist");
});

it("should close the picker when escape is pressed and focus is within the picker and refocus the input", () => {
CypressMountWithProviders(<DateInputCustom />);
cy.get("body").tab();
dayPickerWrapper().should("exist");
cy.focused().tab();
cy.focused().trigger("keydown", keyCode("Esc"));
dayPickerWrapper().should("not.exist");
getDataElementByValue("input").should("be.focused");
});

it("should close the picker when shift + tab is pressed and focus is on the previous month button in the picker and refocus the input", () => {
CypressMountWithProviders(<DateInputCustom />);
cy.get("body").tab();
dayPickerWrapper().should("exist");
cy.focused().tab();
cy.focused().tab({ shift: true });
dayPickerWrapper().should("not.exist");
getDataElementByValue("input").should("be.focused");
});

it("should navigate to the next month when right arrow pressed on last day element of a month", () => {
CypressMountWithProviders(<DateInputCustom value="31/05/2022" />);
cy.get("body").tab();
dateInput().should("be.focused");
cy.focused().tab();
cy.focused().tab();
cy.focused().tab();
cy.focused().trigger("keydown", keyCode("rightarrow"));
cy.focused().should("have.text", "1");
dayPickerHeading().should("have.text", NEXT_MONTH);
});

it.each([
["chevron_right", "next", keysToTrigger[0]],
["chevron_left", "previous", keysToTrigger[1]],
["enter", "next", "chevron_right"],
["space", "next", "chevron_right"],
["enter", "previous", "chevron_left"],
["space", "previous", "chevron_left"],
])(
"should trigger %s arrow in DayPicker to verify %s month is shown using %s keyboard key",
(arrow, month, key) => {
"should change the displayed month when %s is pressed and %s button is focused",
(key, month, arrow) => {
CypressMountWithProviders(<DateInputCustom />);

dateInput().clear().type(DATE_INPUT);
const keyToType = key === "space" ? " " : key;
dateInputParent().click();

dayPickerWrapper().focus();
getDataElementByValue(arrow).trigger("keydown", keyCode(key));
getDataElementByValue(arrow).parent().focus();
getDataElementByValue(arrow).type(`{${keyToType}}`);

if (month === "next") {
dayPickerHeading().should("have.text", NEXT_MONTH);
} else if (month === "previous") {
dayPickerHeading().should("have.text", PREVIOUS_MONTH);
} else {
throw new Error("Only Next or Previous month can be applied");
}
}
);
Expand Down Expand Up @@ -411,6 +629,10 @@ context("Test for DateInput component", () => {
locale: () => localeValue,
date: {
dateFnsLocale: () => dateFnsLocaleValue,
ariaLabels: {
previousMonthButton: () => "Previous month",
nextMonthButton: () => "Next month",
},
},
}
);
Expand Down Expand Up @@ -478,6 +700,10 @@ context("Test for DateInput component", () => {
locale: () => localeValue,
date: {
dateFnsLocale: () => dateFnsLocaleValue,
ariaLabels: {
previousMonthButton: () => "Previous month",
nextMonthButton: () => "Next month",
},
},
}
);
Expand Down Expand Up @@ -544,6 +770,10 @@ context("Test for DateInput component", () => {
locale: () => localeValue,
date: {
dateFnsLocale: () => dateFnsLocaleValue,
ariaLabels: {
previousMonthButton: () => "Previous month",
nextMonthButton: () => "Next month",
},
},
}
);
Expand Down Expand Up @@ -662,6 +892,8 @@ context("Test for DateInput component", () => {

dateInput().focus();

dayPickerParent().should("have.css", "margin-top", "4px");

dateInputParent()
.should(
"have.css",
Expand Down Expand Up @@ -864,4 +1096,12 @@ context("Test for DateInput component", () => {

cy.checkAccessibility();
});

it("should check accessibility when the picker is open", () => {
CypressMountWithProviders(<DateInputCustom />);

dateInputParent()
.click()
.then(() => cy.checkAccessibility());
});
});
7 changes: 5 additions & 2 deletions cypress/support/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const keys = {
uparrow: { key: "ArrowUp", keyCode: 38, which: 38 },
leftarrow: { key: "ArrowLeft", keyCode: 37, which: 37 },
rightarrow: { key: "ArrowRight", keyCode: 39, which: 39 },
Enter: { key: "Enter", keyCode: 13, which: 13 },
Enter: { key: "Enter", keyCode: 13, which: 13, bubbles: true },
EnterForce: { key: "Enter", keyCode: 13, which: 13, force: true },
Space: { key: " ", keyCode: 32, which: 32 },
Tab: { key: "Tab", keyCode: 9, which: 9 },
Expand All @@ -76,8 +76,11 @@ const keys = {
pagedown: { key: "PageDown", keyCode: 34, which: 34 },
pageup: { key: "PageUp", keyCode: 33, which: 33 },
};

export type KeyIds = keyof typeof keys;

export function keyCode(
type: keyof typeof keys
type: KeyIds
): {
key: string;
keyCode: number;
Expand Down
14 changes: 14 additions & 0 deletions src/components/date-range/date-range.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,19 @@ The following keys are available to override the translations for this component
type: "func",
returnType: "date-fns locale object",
},
{
name: "date.ariaLabels.previousMonthButton",
description:
"Aria label text for the previous month navigation button in the date picker",
type: "func",
returnType: "string",
},
{
name: "date.ariaLabels.nextMonthButton",
description:
"Aria label text for the next month navigation button in the date picker",
type: "func",
returnType: "string",
}
]}
/>
8 changes: 7 additions & 1 deletion src/components/date-range/date-range.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,13 @@ export const LocaleOverrideExampleImplementation: ComponentStory<
<I18nProvider
locale={{
locale: () => "fr-FR",
date: { dateFnsLocale: () => fr },
date: {
dateFnsLocale: () => fr,
ariaLabels: {
previousMonthButton: () => "Mois précédent",
nextMonthButton: () => "Mois prochain",
},
},
}}
>
<DateRange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ exports[`StyledDayPicker renders presentational div and context provider for its
height: 346px;
width: 352px;
z-index: 6000;
margin-top: var(--spacing050);
}

.c0 .DayPicker {
Expand Down
Loading