Repository: Hacker0x01/react-datepicker
Pull request #4702 (2024-04-17 14:06:48)

feature: add aria-disabled to Month component
## Summary
This PR addresses an issue where the Month component is missing the attribute aria-disabled on its disabled items.

## Changes made
- Updated the Month component to add the aria-disabled attribute over its disabled items
- Made the logic to find the disabled month unit shareable in a reusable helper function
- Updated the corresponding test cases to also check for the existence of the aria-disabled attribute along with the classname


Patch:
diff --git a/src/month.jsx b/src/month.jsx
index 66842f2f..a225eeac 100644
--- a/src/month.jsx
+++ b/src/month.jsx
@@ -545,31 +545,27 @@ export default class Month extends React.Component {
     }
   };
 
+  isMonthDisabled = (month) => {
+    const { day, minDate, maxDate, excludeDates, includeDates } = this.props;
+    const labelDate = utils.setMonth(day, month);
+    return (
+      (minDate || maxDate || excludeDates || includeDates) &&
+      utils.isMonthDisabled(labelDate, this.props)
+    );
+  };
+
   getMonthClassNames = (m) => {
-    const {
-      day,
-      startDate,
-      endDate,
-      selected,
-      minDate,
-      maxDate,
-      preSelection,
-      monthClassName,
-      excludeDates,
-      includeDates,
-    } = this.props;
+    const { day, startDate, endDate, selected, preSelection, monthClassName } =
+      this.props;
     const _monthClassName = monthClassName
       ? monthClassName(utils.setMonth(day, m))
       : undefined;
-    const labelDate = utils.setMonth(day, m);
     return clsx(
       "react-datepicker__month-text",
       `react-datepicker__month-${m}`,
       _monthClassName,
       {
-        "react-datepicker__month-text--disabled":
-          (minDate || maxDate || excludeDates || includeDates) &&
-          utils.isMonthDisabled(labelDate, this.props),
+        "react-datepicker__month-text--disabled": this.isMonthDisabled(m),
         "react-datepicker__month-text--selected": this.isSelectedMonth(
           day,
           m,
@@ -737,6 +733,7 @@ export default class Month extends React.Component {
             }
             tabIndex={this.getTabIndex(m)}
             className={this.getMonthClassNames(m)}
+            aria-disabled={this.isMonthDisabled(m)}
             role="option"
             aria-label={this.getAriaLabel(m)}
             aria-current={this.isCurrentMonth(day, m) ? "date" : undefined}


Test patch:
diff --git a/test/month_test.test.js b/test/month_test.test.js
index b8d5a30b..8786d9f6 100644
--- a/test/month_test.test.js
+++ b/test/month_test.test.js
@@ -430,7 +430,7 @@ describe("Month", () => {
     expect(utils.getMonth(monthClicked)).toBe(6);
   });
 
-  it("should return disabled class if current date is out of bound of minDate and maxDate", () => {
+  it("should return disabled month if current date is out of bound of minDate and maxDate", () => {
     const onDayClickSpy = jest.fn();
     const onDayMouseEnterSpy = jest.fn();
     const { container } = render(
@@ -449,13 +449,14 @@ describe("Month", () => {
     expect(
       month.classList.contains("react-datepicker__month-text--disabled"),
     ).toBe(true);
+    expect(month.getAttribute("aria-disabled")).toBe("true");
     fireEvent.click(month);
     expect(onDayClickSpy).not.toHaveBeenCalled();
     fireEvent.mouseEnter(month);
     expect(onDayMouseEnterSpy).not.toHaveBeenCalled();
   });
 
-  it("should not return disabled class if current date is before minDate but same month", () => {
+  it("should not return disabled month if current date is before minDate but same month", () => {
     const onDayClickSpy = jest.fn();
     const onDayMouseEnterSpy = jest.fn();
     const { container } = render(
@@ -473,13 +474,14 @@ describe("Month", () => {
     expect(
       month.classList.contains("react-datepicker__month-text--disabled"),
     ).not.toBe(true);
+    expect(month.getAttribute("aria-disabled")).not.toBe("true");
     fireEvent.click(month);
     expect(onDayClickSpy).toHaveBeenCalled();
     fireEvent.mouseEnter(month);
     expect(onDayMouseEnterSpy).toHaveBeenCalled();
   });
 
-  it("should not return disabled class if current date is after maxDate but same month", () => {
+  it("should not return disabled month if current date is after maxDate but same month", () => {
     const onDayClickSpy = jest.fn();
     const onDayMouseEnterSpy = jest.fn();
     const { container } = render(
@@ -497,13 +499,14 @@ describe("Month", () => {
     expect(
       month.classList.contains("react-datepicker__month-text--disabled"),
     ).not.toBe(true);
+    expect(month.getAttribute("aria-disabled")).not.toBe("true");
     fireEvent.click(month);
     expect(onDayClickSpy).toHaveBeenCalled();
     fireEvent.mouseEnter(month);
     expect(onDayMouseEnterSpy).toHaveBeenCalled();
   });
 
-  it("should return disabled class if specified excludeDate", () => {
+  it("should return disabled month if specified excludeDate", () => {
     const onDayClickSpy = jest.fn();
     const onDayMouseEnterSpy = jest.fn();
     const { container } = render(
@@ -530,6 +533,7 @@ describe("Month", () => {
       expect(
         month.classList.contains("react-datepicker__month-text--disabled"),
       ).toBe(true);
+      expect(month.getAttribute("aria-disabled")).toBe("true");
       fireEvent.click(month);
       expect(onDayClickSpy).not.toHaveBeenCalled();
       fireEvent.mouseEnter(month);
@@ -537,7 +541,7 @@ describe("Month", () => {
     });
   });
 
-  it("should return disabled class if specified includeDate", () => {
+  it("should return disabled month if specified includeDate", () => {
     const { container } = render(
       <Month
         day={utils.newDate("2015-01-01")}
@@ -566,6 +570,7 @@ describe("Month", () => {
       expect(
         month.classList.contains("react-datepicker__month-text--disabled"),
       ).toBe(true);
+      expect(month.getAttribute("aria-disabled")).toBe("true");
     }
   });