Skip to content

Commit cf8640b

Browse files
feat: add jest-axe accessibility testing and fix ARIA structure issues
- Add jest-axe dependency and comprehensive accessibility test suite - Fix ARIA structure violations in calendar components: * Update Day component: role="option" → role="gridcell" * Add Week component: role="row" for proper table structure * Update WeekNumber component: add role="gridcell" * Enhance Month component with conditional roles: - Use role="listbox" for month/year/quarter pickers - Use role="table" structure for regular calendar view * Restructure Calendar to use proper table hierarchy - Add comprehensive test coverage for all DatePicker variants - Ensure compatibility with screen readers and assistive technologies - All 22 accessibility tests now pass 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent c7a14dd commit cf8640b

File tree

9 files changed

+480
-29
lines changed

9 files changed

+480
-29
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"@testing-library/user-event": "14.6.1",
5656
"@types/eslint": "^9.6.1",
5757
"@types/jest": "^30.0.0",
58+
"@types/jest-axe": "^3.5.9",
5859
"@types/node": "22.15.30",
5960
"@types/react": "^19.1.0",
6061
"@types/react-dom": "^19.1.2",
@@ -73,6 +74,7 @@
7374
"eslint-plugin-unused-imports": "^4.1.4",
7475
"husky": "9.1.7",
7576
"jest": "^30.0.5",
77+
"jest-axe": "^10.0.0",
7678
"jest-canvas-mock": "^2.5.2",
7779
"jest-environment-jsdom": "^29.7.0",
7880
"lint-staged": "^16.0.0",

src/calendar.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,12 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
839839
);
840840
};
841841

842+
renderDayNamesHeader = (monthDate: Date) => (
843+
<div className="react-datepicker__day-names" role="row">
844+
{this.header(monthDate)}
845+
</div>
846+
);
847+
842848
renderDefaultHeader = ({ monthDate, i }: { monthDate: Date; i: number }) => (
843849
<div
844850
className={`react-datepicker__header ${
@@ -856,9 +862,6 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
856862
{this.renderMonthYearDropdown(i !== 0)}
857863
{this.renderYearDropdown(i !== 0)}
858864
</div>
859-
<div className="react-datepicker__day-names" role="row">
860-
{this.header(monthDate)}
861-
</div>
862865
</div>
863866
);
864867

@@ -1030,6 +1033,7 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
10301033
selectingDate={this.state.selectingDate}
10311034
monthShowsDuplicateDaysEnd={monthShowsDuplicateDaysEnd}
10321035
monthShowsDuplicateDaysStart={monthShowsDuplicateDaysStart}
1036+
dayNamesHeader={this.renderDayNamesHeader(monthDate)}
10331037
/>
10341038
</div>,
10351039
);

src/day.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ export default class Day extends Component<DayProps> {
597597
}
598598
tabIndex={this.getTabIndex()}
599599
aria-label={this.getAriaLabel()}
600-
role="option"
600+
role="gridcell"
601601
title={this.getTitle()}
602602
aria-disabled={this.isDisabled()}
603603
aria-current={this.isCurrentDay() ? "date" : undefined}

src/month.tsx

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ interface MonthProps
140140
weekAriaLabelPrefix?: WeekProps["ariaLabelPrefix"];
141141
chooseDayAriaLabelPrefix?: WeekProps["chooseDayAriaLabelPrefix"];
142142
disabledDayAriaLabelPrefix?: WeekProps["disabledDayAriaLabelPrefix"];
143+
dayNamesHeader?: React.ReactNode;
143144
}
144145

145146
/**
@@ -1101,23 +1102,47 @@ export default class Month extends Component<MonthProps> {
11011102
? ariaLabelPrefix.trim() + " "
11021103
: "";
11031104

1105+
const shouldUseListboxRole = showMonthYearPicker || showQuarterYearPicker;
1106+
1107+
if (shouldUseListboxRole) {
1108+
return (
1109+
<div
1110+
className={this.getClassNames()}
1111+
onMouseLeave={
1112+
!this.props.usePointerEvent ? this.handleMouseLeave : undefined
1113+
}
1114+
onPointerLeave={
1115+
this.props.usePointerEvent ? this.handleMouseLeave : undefined
1116+
}
1117+
aria-label={`${formattedAriaLabelPrefix}${formatDate(day, "MMMM, yyyy", this.props.locale)}`}
1118+
role="listbox"
1119+
>
1120+
{showMonthYearPicker ? this.renderMonths() : this.renderQuarters()}
1121+
</div>
1122+
);
1123+
}
1124+
1125+
// For regular calendar view, use table structure
11041126
return (
11051127
<div
1106-
className={this.getClassNames()}
1107-
onMouseLeave={
1108-
!this.props.usePointerEvent ? this.handleMouseLeave : undefined
1109-
}
1110-
onPointerLeave={
1111-
this.props.usePointerEvent ? this.handleMouseLeave : undefined
1112-
}
1128+
role="table"
11131129
aria-label={`${formattedAriaLabelPrefix}${formatDate(day, "MMMM, yyyy", this.props.locale)}`}
1114-
role="listbox"
11151130
>
1116-
{showMonthYearPicker
1117-
? this.renderMonths()
1118-
: showQuarterYearPicker
1119-
? this.renderQuarters()
1120-
: this.renderWeeks()}
1131+
{this.props.dayNamesHeader && (
1132+
<div role="rowgroup">{this.props.dayNamesHeader}</div>
1133+
)}
1134+
<div
1135+
className={this.getClassNames()}
1136+
onMouseLeave={
1137+
!this.props.usePointerEvent ? this.handleMouseLeave : undefined
1138+
}
1139+
onPointerLeave={
1140+
this.props.usePointerEvent ? this.handleMouseLeave : undefined
1141+
}
1142+
role="rowgroup"
1143+
>
1144+
{this.renderWeeks()}
1145+
</div>
11211146
</div>
11221147
);
11231148
}

0 commit comments

Comments
 (0)