diff --git a/src/test/calendar_test.test.tsx b/src/test/calendar_test.test.tsx index 81ac627b5d..8d2814668f 100644 --- a/src/test/calendar_test.test.tsx +++ b/src/test/calendar_test.test.tsx @@ -45,6 +45,7 @@ import { SafeElementWrapper, safeQuerySelector, safeQuerySelectorAll, + setupMockResizeObserver, } from "./test_utils"; import type { ReactDatePickerCustomHeaderProps } from "../calendar"; @@ -136,6 +137,10 @@ describe("Calendar", () => { }; } + beforeAll(() => { + setupMockResizeObserver(); + }); + it("should start with the current date in view if no date range", () => { const now = newDate(); const { instance } = getCalendar(); diff --git a/src/test/datepicker_test.test.tsx b/src/test/datepicker_test.test.tsx index eeb3d83976..21548de05e 100644 --- a/src/test/datepicker_test.test.tsx +++ b/src/test/datepicker_test.test.tsx @@ -29,7 +29,11 @@ import DatePicker, { registerLocale } from "../index"; import CustomInput from "./helper_components/custom_input"; import ShadowRoot from "./helper_components/shadow_root"; import TestWrapper from "./helper_components/test_wrapper"; -import { getKey, safeQuerySelector } from "./test_utils"; +import { + getKey, + safeQuerySelector, + setupMockResizeObserver, +} from "./test_utils"; function getSelectedDayNode(container: HTMLElement) { return ( @@ -83,6 +87,10 @@ const showDocument = (calendarInput: HTMLElement) => { }; describe("DatePicker", () => { + beforeEach(() => { + setupMockResizeObserver(); + }); + afterEach(() => { jest.resetAllMocks(); }); diff --git a/src/test/exclude_time_period_test.test.tsx b/src/test/exclude_time_period_test.test.tsx index 6531bf649f..68bc3acc0f 100644 --- a/src/test/exclude_time_period_test.test.tsx +++ b/src/test/exclude_time_period_test.test.tsx @@ -2,9 +2,14 @@ import { render } from "@testing-library/react"; import React from "react"; import { newDate, setTime } from "../date_utils"; +import { setupMockResizeObserver } from "./test_utils"; import DatePicker from "../index"; describe("DatePicker", () => { + beforeAll(() => { + setupMockResizeObserver(); + }); + it("should only display times between minTime and maxTime", () => { const now = newDate(); const { container } = render( diff --git a/src/test/exclude_times_test.test.tsx b/src/test/exclude_times_test.test.tsx index 69745a3e3a..d477d80893 100644 --- a/src/test/exclude_times_test.test.tsx +++ b/src/test/exclude_times_test.test.tsx @@ -2,11 +2,16 @@ import { render } from "@testing-library/react"; import React from "react"; import { setTime, newDate } from "../date_utils"; +import { setupMockResizeObserver } from "./test_utils"; import DatePicker from "../index"; describe("DatePicker", () => { let now: Date, excludeTimes: Date[]; + beforeAll(() => { + setupMockResizeObserver(); + }); + beforeEach(() => { now = newDate(); excludeTimes = [ diff --git a/src/test/min_time_test.test.tsx b/src/test/min_time_test.test.tsx index 2eaf280c74..d705695294 100644 --- a/src/test/min_time_test.test.tsx +++ b/src/test/min_time_test.test.tsx @@ -3,7 +3,7 @@ import React, { useState } from "react"; import DatePicker from "../index"; -import { safeQuerySelector } from "./test_utils"; +import { safeQuerySelector, setupMockResizeObserver } from "./test_utils"; import type { DatePickerProps } from "../index"; @@ -44,6 +44,10 @@ const DatePickerWithState = ( }; describe("Datepicker minTime", () => { + beforeAll(() => { + setupMockResizeObserver(); + }); + it("should select time 12:00 AM when no minTime constraint is set.", () => { const { getByText, container } = render(); diff --git a/src/test/show_time_test.test.tsx b/src/test/show_time_test.test.tsx index 0fdfbf442d..a64a4785e6 100644 --- a/src/test/show_time_test.test.tsx +++ b/src/test/show_time_test.test.tsx @@ -4,9 +4,13 @@ import React from "react"; import DatePicker from "../index"; import TimeComponent from "../time"; -import { safeQuerySelector } from "./test_utils"; +import { safeQuerySelector, setupMockResizeObserver } from "./test_utils"; describe("DatePicker", () => { + beforeAll(() => { + setupMockResizeObserver(); + }); + it("should show time component when showTimeSelect prop is present", () => { const { container } = render(); const timeComponent = container.querySelector( diff --git a/src/test/test_utils.ts b/src/test/test_utils.ts index 54f8073c57..83e075a00d 100644 --- a/src/test/test_utils.ts +++ b/src/test/test_utils.ts @@ -123,6 +123,37 @@ export const safeQuerySelectorAll = ( return elements; }; +let _resizeObserverCallbackFn: ResizeObserverCallback | null; + +export const getResizeObserverCallback = () => _resizeObserverCallbackFn; + +export const setupMockResizeObserver = () => { + const mockObserve = jest.fn(); + const mockDisconnect = jest.fn(); + const mockUnobserve = jest.fn(); + + const ResizeObserverMock = jest.fn((fn) => { + _resizeObserverCallbackFn = fn; + + return { + observe: mockObserve, + disconnect: () => { + _resizeObserverCallbackFn = null; + mockDisconnect(); + }, + unobserve: jest.fn(), + }; + }); + + global.ResizeObserver = ResizeObserverMock; + + return { + observe: mockObserve, + disconnect: mockDisconnect, + unobserve: mockUnobserve, + }; +}; + export class SafeElementWrapper { constructor(private element: T) {} diff --git a/src/test/time_input_test.test.tsx b/src/test/time_input_test.test.tsx index bc332753ec..ef4bf1d7bc 100644 --- a/src/test/time_input_test.test.tsx +++ b/src/test/time_input_test.test.tsx @@ -5,9 +5,13 @@ import DatePicker from "../index"; import InputTimeComponent from "../input_time"; import CustomTimeInput from "./helper_components/custom_time_input"; -import { safeQuerySelector } from "./test_utils"; +import { safeQuerySelector, setupMockResizeObserver } from "./test_utils"; describe("timeInput", () => { + beforeEach(() => { + setupMockResizeObserver(); + }); + afterEach(() => { jest.resetAllMocks(); }); diff --git a/src/test/timepicker_test.test.tsx b/src/test/timepicker_test.test.tsx index 3ed6d043e5..b4c8010f44 100644 --- a/src/test/timepicker_test.test.tsx +++ b/src/test/timepicker_test.test.tsx @@ -6,9 +6,11 @@ import DatePicker from "../index"; import { getKey, + getResizeObserverCallback, SafeElementWrapper, safeQuerySelector, safeQuerySelectorAll, + setupMockResizeObserver, } from "./test_utils"; const MIN_TIME_LI_LEN = 2; @@ -19,10 +21,65 @@ describe("TimePicker", () => { let onChangeMoment: Date | undefined; let instance: DatePicker | null = null; + let mockObserve: jest.Mock, mockDisconnect: jest.Mock; + + beforeAll(() => { + const { observe, disconnect } = setupMockResizeObserver(); + mockObserve = observe; + mockDisconnect = disconnect; + }); + beforeEach(() => { div = document.createElement("div"); }); + describe("Re-adjust height on Calendar Height Change", () => { + beforeEach(() => { + mockObserve.mockReset(); + mockDisconnect.mockReset(); + }); + + it("calls observe on mount", async () => { + render( + , + ); + await waitFor(() => { + expect(mockObserve).toHaveBeenCalledTimes(1); + + const resizeObserverCallback = getResizeObserverCallback(); + const mockObserveElement = mockObserve.mock.calls[0][0]; + expect(typeof resizeObserverCallback).toBe("function"); + + if (resizeObserverCallback) { + resizeObserverCallback([], mockObserveElement); + } + }); + }); + + it("calls disconnect on unmount", async () => { + const component = render( + , + ); + + component.unmount(); + await waitFor(() => { + expect(mockDisconnect).toHaveBeenCalledTimes(1); + const resizeObserverCallback = getResizeObserverCallback(); + expect(resizeObserverCallback).toBe(null); + }); + }); + }); + it("should update on input time change", () => { renderDatePicker("February 28, 2018 4:43 PM"); expect(getInputString()).toBe("February 28, 2018 4:43 PM"); diff --git a/src/time.tsx b/src/time.tsx index 4e1bb3d513..c6ed61e4b1 100644 --- a/src/time.tsx +++ b/src/time.tsx @@ -62,6 +62,7 @@ export default class Time extends Component { ); }; + private resizeObserver?: ResizeObserver; state: TimeState = { height: null, }; @@ -69,11 +70,11 @@ export default class Time extends Component { componentDidMount(): void { // code to ensure selected time will always be in focus within time window when it first appears this.scrollToTheSelectedTime(); - if (this.props.monthRef && this.header) { - this.setState({ - height: this.props.monthRef.clientHeight - this.header.clientHeight, - }); - } + this.observeDatePickerHeightChanges(); + } + + componentWillUnmount(): void { + this.resizeObserver?.disconnect(); } private header?: HTMLDivElement; @@ -82,6 +83,27 @@ export default class Time extends Component { private centerLi?: HTMLLIElement; + private observeDatePickerHeightChanges(): void { + const { monthRef } = this.props; + this.updateContainerHeight(); + + if (monthRef) { + this.resizeObserver = new ResizeObserver(() => { + this.updateContainerHeight(); + }); + + this.resizeObserver.observe(monthRef); + } + } + + private updateContainerHeight(): void { + if (this.props.monthRef && this.header) { + this.setState({ + height: this.props.monthRef.clientHeight - this.header.clientHeight, + }); + } + } + scrollToTheSelectedTime = (): void => { requestAnimationFrame((): void => { if (!this.list) return;