Skip to content
Open
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
8 changes: 7 additions & 1 deletion src/month_dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,14 @@ export default class MonthDropdown extends Component<
style={{ visibility: visible ? "visible" : "hidden" }}
className="react-datepicker__month-read-view"
onClick={this.toggleDropdown}
aria-expanded={this.state.dropdownVisible}
aria-haspopup="listbox"
>
<span className="react-datepicker__month-read-view--down-arrow" />
<span
className="react-datepicker__month-read-view--down-arrow"
aria-hidden="true"
/>
<span className="react-datepicker__sr-only">Month</span>
<span className="react-datepicker__month-read-view--selected-month">
{monthNames[this.props.month]}
</span>
Expand Down
19 changes: 12 additions & 7 deletions src/month_dropdown_options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface MonthDropdownOptionsProps {
}

export default class MonthDropdownOptions extends Component<MonthDropdownOptionsProps> {
monthOptionButtonsRef: Record<number, HTMLDivElement | null> = {};
monthOptionButtonsRef: Record<number, HTMLLIElement | null> = {};

isSelectedMonth = (i: number): boolean => this.props.month === i;

Expand Down Expand Up @@ -42,14 +42,14 @@ export default class MonthDropdownOptions extends Component<MonthDropdownOptions

return this.props.monthNames.map<React.ReactElement>(
(month: string, i: number): React.ReactElement => (
<div
<li
ref={(el) => {
this.monthOptionButtonsRef[i] = el;
if (this.isSelectedMonth(i)) {
el?.focus();
}
}}
role="button"
role="option"
tabIndex={0}
className={
this.isSelectedMonth(i)
Expand All @@ -59,15 +59,20 @@ export default class MonthDropdownOptions extends Component<MonthDropdownOptions
key={month}
onClick={this.onChange.bind(this, i)}
onKeyDown={this.handleOptionKeyDown.bind(this, i)}
aria-selected={this.isSelectedMonth(i) ? "true" : undefined}
aria-selected={this.isSelectedMonth(i)}
>
{this.isSelectedMonth(i) ? (
<span className="react-datepicker__month-option--selected">✓</span>
<span
className="react-datepicker__month-option--selected"
aria-hidden="true"
>
</span>
) : (
""
)}
{month}
</div>
</li>
),
);
};
Expand All @@ -82,7 +87,7 @@ export default class MonthDropdownOptions extends Component<MonthDropdownOptions
className="react-datepicker__month-dropdown"
onClickOutside={this.handleClickOutside}
>
{this.renderOptions()}
<div role="listbox">{this.renderOptions()}</div>
</ClickOutsideWrapper>
);
}
Expand Down
7 changes: 7 additions & 0 deletions src/stylesheets/datepicker.scss
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ h2.react-datepicker__current-month {
display: block;
margin-left: auto;
margin-right: auto;
width: 100%;

&-previous {
top: 4px;
Expand Down Expand Up @@ -676,6 +677,12 @@ h2.react-datepicker__current-month {
}
}

.react-datepicker__year-options-list {
margin: 0;
padding: 0;
list-style: none;
}

.react-datepicker__year-option,
.react-datepicker__month-option,
.react-datepicker__month-year-option {
Expand Down
96 changes: 92 additions & 4 deletions src/test/month_dropdown_test.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,100 @@ describe("MonthDropdown", () => {
});

describe("scroll mode", () => {
const selectedMonthIndex = 11;

beforeEach(() => {
monthDropdown = getMonthDropdown();
monthDropdown = getMonthDropdown({
month: selectedMonthIndex,
});
});

it("sets proper ARIA on read view button and toggles aria-expanded", () => {
const monthReadView = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view",
);
expect(monthReadView.getAttribute("aria-haspopup")).toBe("listbox");
expect(monthReadView.getAttribute("aria-expanded")).toBe("false");

fireEvent.click(monthReadView);

const monthReadViewAfterOpen = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view",
);
expect(monthReadViewAfterOpen.getAttribute("aria-expanded")).toBe("true");
});

it("marks the down arrow as aria-hidden so it is excluded from the accessibility tree", () => {
const downArrow = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view--down-arrow",
);

expect(downArrow.getAttribute("aria-hidden")).toBe("true");
expect(downArrow.textContent).toBe("");
});

it("renders a sr-only Month label for screen readers inside the read view button", () => {
const monthReadView = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view",
);
const srOnlyLabel = safeQuerySelector(
monthReadView,
".react-datepicker__sr-only",
);

expect(srOnlyLabel.textContent).toBe("Month");
expect(srOnlyLabel.classList.contains("react-datepicker__sr-only")).toBe(
true,
);
expect(srOnlyLabel.getAttribute("aria-hidden")).not.toBe("true");
});

it("applies aria-selected to the selected month option in scroll dropdown", () => {
const monthReadView = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view",
);
fireEvent.click(monthReadView);

const allMonthOptions = safeQuerySelectorAll(
monthDropdown,
".react-datepicker__month-option",
);
allMonthOptions.forEach((option, idx) => {
expect(option.getAttribute("aria-selected")).toBe(
idx === selectedMonthIndex ? "true" : "false",
);
});
});

it("applies aria-hidden to the selected month option's check mark in scroll dropdown", () => {
const monthReadView = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view",
);
fireEvent.click(monthReadView);
const allMonthOptions = safeQuerySelectorAll(
monthDropdown,
".react-datepicker__month-option",
);

const selectedMonthOption = allMonthOptions[selectedMonthIndex];
expect(selectedMonthOption).not.toBeNull();

const checkSpan = selectedMonthOption?.querySelector<HTMLSpanElement>(
"span.react-datepicker__month-option--selected",
);
expect(checkSpan).not.toBeNull();
expect(checkSpan?.getAttribute("aria-hidden")).toBe("true");
});

it("shows the selected month in the initial view", () => {
expect(monthDropdown?.textContent).toContain("December");
const expectedMonthName = getMonthInLocale(selectedMonthIndex);
expect(monthDropdown?.textContent).toContain(expectedMonthName);
});

it("opens a list when read view is clicked", () => {
Expand Down Expand Up @@ -102,9 +190,9 @@ describe("MonthDropdown", () => {
expect(notSelectedMonth?.textContent).not.toContain("December");
});

it("does not add aria-selected property to the selected month", () => {
it("should have aria-selected set to false", () => {
const ariaSelected = notSelectedMonth?.getAttribute("aria-selected");
expect(ariaSelected).toBeNull();
expect(ariaSelected).toBe("false");
});
});

Expand Down
Loading
Loading