Files
karma/ui/src/Components/SilenceModal/DateTimeSelect/index.test.tsx
2026-05-18 09:19:33 +01:00

687 lines
20 KiB
TypeScript

import { act } from "react";
import { render, fireEvent } from "@testing-library/react";
import { addMinutes } from "date-fns/addMinutes";
import { addHours } from "date-fns/addHours";
import { differenceInMilliseconds } from "date-fns/differenceInMilliseconds";
import { format } from "date-fns";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import {
DateTimeSelect,
TabContentStart,
TabContentEnd,
TabContentDuration,
} from ".";
let silenceFormStore: SilenceFormStore;
beforeEach(() => {
jest.useFakeTimers();
silenceFormStore = new SilenceFormStore();
silenceFormStore.data.setStart(new Date(2060, 1, 1, 0, 0, 0));
silenceFormStore.data.setEnd(new Date(2061, 1, 1, 0, 0, 0));
});
afterEach(() => {
jest.useRealTimers();
});
const renderDateTimeSelect = () => {
return render(<DateTimeSelect silenceFormStore={silenceFormStore} />);
};
describe("<DateTimeSelect />", () => {
it("renders 3 tabs", () => {
const { container } = renderDateTimeSelect();
const tabs = container.querySelectorAll(".nav-link");
expect(tabs).toHaveLength(3);
});
it("renders 'Duration' tab by default", () => {
const { container } = renderDateTimeSelect();
const tab = container.querySelector(".nav-link.active");
expect(tab).toBeInTheDocument();
expect(tab?.textContent).toMatch(/Duration/);
expect(container.querySelector(".tab-content")?.textContent).toBe(
"366days0hours0minutes",
);
});
it("'Duration' tab matches snapshot", () => {
jest.setSystemTime(new Date(2060, 1, 1, 0, 0, 0).getTime());
const { asFragment } = renderDateTimeSelect();
expect(asFragment()).toMatchSnapshot();
});
it("'Duration' tab unmounts without crashing", () => {
const { unmount } = renderDateTimeSelect();
unmount();
});
it("clicking on the 'Starts' tab switches content to 'startsAt' selection", () => {
const { container } = renderDateTimeSelect();
const tabs = container.querySelectorAll(".nav-link");
expect(tabs[0].textContent).toMatch(/Starts/);
fireEvent.click(tabs[0]);
expect(container.querySelector(".tab-content")?.textContent).toMatch(
/2060/,
);
});
it("'Starts' tab matches snapshot", () => {
jest.setSystemTime(new Date(2060, 1, 1, 0, 0, 0).getTime());
const { container, asFragment } = renderDateTimeSelect();
fireEvent.click(container.querySelectorAll(".nav-link")[0]);
expect(asFragment()).toMatchSnapshot();
});
it("'Starts' tab unmounts without crashing", () => {
const { container, unmount } = renderDateTimeSelect();
fireEvent.click(container.querySelectorAll(".nav-link")[0]);
unmount();
});
it("clicking on the 'Ends' tab switches content to 'endsAt' selection", () => {
const { container } = renderDateTimeSelect();
const tabs = container.querySelectorAll(".nav-link");
expect(tabs[1].textContent).toMatch(/Ends/);
fireEvent.click(tabs[1]);
expect(container.querySelector(".tab-content")?.textContent).toMatch(
/2061/,
);
});
it("'Ends' tab matches snapshot", () => {
jest.setSystemTime(new Date(2060, 1, 1, 0, 0, 0).getTime());
const { container, asFragment } = renderDateTimeSelect();
fireEvent.click(container.querySelectorAll(".nav-link")[1]);
expect(asFragment()).toMatchSnapshot();
});
it("'Ends' tab unmounts without crashing", () => {
const { container, unmount } = renderDateTimeSelect();
fireEvent.click(container.querySelectorAll(".nav-link")[1]);
unmount();
});
it("clicking on the 'Duration' tabs switches content to duration selection", () => {
const { container } = renderDateTimeSelect();
const tabs = container.querySelectorAll(".nav-link");
fireEvent.click(tabs[0]);
expect(tabs[2].textContent).toMatch(/Duration/);
fireEvent.click(tabs[2]);
expect(container.querySelector(".tab-content")?.textContent).toBe(
"366days0hours0minutes",
);
});
it("'Ends' tab offset badge is updated after 1 minute", () => {
jest.useFakeTimers();
jest.setSystemTime(new Date(2060, 1, 1, 12, 0, 0).getTime());
silenceFormStore.data.setStart(new Date(2060, 1, 1, 12, 0, 0));
silenceFormStore.data.setEnd(new Date(2060, 1, 1, 13, 0, 0));
const { container } = renderDateTimeSelect();
expect(container.querySelectorAll(".nav-link")[1].textContent).toBe(
"Endsin 1h ",
);
jest.setSystemTime(new Date(2060, 1, 1, 12, 1, 0).getTime());
act(() => {
jest.runOnlyPendingTimers();
});
expect(container.querySelectorAll(".nav-link")[1].textContent).toBe(
"Endsin 59m ",
);
});
it("unmounts cleanly", () => {
const { unmount } = renderDateTimeSelect();
unmount();
});
});
const ValidateTimeButton = (
container: HTMLElement,
storeKey: "startsAt" | "endsAt",
elemIndex: number,
iconMatch: RegExp,
expectedDiff: number,
) => {
const buttons = container.querySelectorAll("td > span");
const button = buttons[elemIndex];
expect(button.innerHTML).toMatch(iconMatch);
const oldTimeValue = new Date(silenceFormStore.data[storeKey]);
fireEvent.click(button);
expect(silenceFormStore.data[storeKey].toISOString()).not.toBe(
oldTimeValue.toISOString(),
);
const diffMS = differenceInMilliseconds(
silenceFormStore.data[storeKey],
oldTimeValue,
);
expect(diffMS).toBe(expectedDiff);
};
const ValidateTimeWheel = (
container: HTMLElement,
storeKey: "startsAt" | "endsAt",
className: string,
deltaY: number,
expectedDiff: number,
) => {
const elem = container.querySelector(className);
const oldTimeValue = new Date(silenceFormStore.data[storeKey]);
fireEvent.wheel(elem!, { deltaY: deltaY });
// fire real event so cancel listener will trigger
const event = new WheelEvent("wheel", { deltaY: deltaY });
const hourMinute = container.querySelector("div.components-hour-minute");
hourMinute?.dispatchEvent(event);
expect(silenceFormStore.data[storeKey].toISOString()).not.toBe(
oldTimeValue.toISOString(),
);
const diffMS = differenceInMilliseconds(
silenceFormStore.data[storeKey],
oldTimeValue,
);
expect(diffMS).toBe(expectedDiff);
};
const renderTabContentStart = () => {
return render(<TabContentStart silenceFormStore={silenceFormStore} />);
};
describe("<TabContentStart />", () => {
it("selecting date on DayPicker updates startsAt", () => {
// Verifies clicking a day in the calendar updates the startsAt date
const { container } = renderTabContentStart();
expect(silenceFormStore.data.startsAt.toISOString()).toBe(
new Date(2060, 1, 1, 0, 0, 0).toISOString(),
);
// In v9, day buttons use .rdp-day_button class
const dayButtons = container.querySelectorAll("button.rdp-day_button");
fireEvent.click(dayButtons[17]);
expect(silenceFormStore.data.startsAt.toISOString()).toBe(
new Date(2060, 1, 18, 0, 0, 0).toISOString(),
);
});
it("clicking on the hour inc button adds 1h to startsAt", () => {
const { container } = renderTabContentStart();
ValidateTimeButton(container, "startsAt", 0, /angle-up/, 3600 * 1000);
});
it("Today button takes you back to the current month", () => {
const { container } = renderTabContentStart();
expect(silenceFormStore.data.startsAt.toISOString()).toBe(
new Date(2060, 1, 1, 0, 0, 0).toISOString(),
);
expect(container.querySelector(".rdp-month_caption")?.textContent).toBe(
"February 2060",
);
fireEvent.click(container.querySelector("button.rdp-button_next")!);
const captionsAfterNext = container.querySelectorAll(".rdp-month_caption");
expect(captionsAfterNext[captionsAfterNext.length - 1].textContent).toBe(
"March 2060",
);
fireEvent.click(container.querySelector("button.btn.btn-light.btn-sm")!);
const captionsAfterToday = container.querySelectorAll(".rdp-month_caption");
expect(captionsAfterToday[captionsAfterToday.length - 1].textContent).toBe(
format(new Date(), "LLLL yyyy"),
);
});
it("scrolling up on the hour button adds 1h to startsAt", () => {
const { container } = renderTabContentStart();
ValidateTimeWheel(
container,
"startsAt",
"td.components-hour-up",
-1,
3600 * 1000,
);
});
it("clicking on the minute inc button adds 1m to startsAt", () => {
const { container } = renderTabContentStart();
ValidateTimeButton(container, "startsAt", 1, /angle-up/, 60 * 1000);
});
it("scrolling up on the minute button adds 1m to startsAt", () => {
const { container } = renderTabContentStart();
ValidateTimeWheel(
container,
"startsAt",
"td.components-minute-up",
-2,
60 * 1000,
);
});
it("clicking on the hour dec button subtracts 1h from startsAt", () => {
const { container } = renderTabContentStart();
ValidateTimeButton(
container,
"startsAt",
2,
/angle-down/,
-1 * 3600 * 1000,
);
});
it("scrolling down on the hour button subtracts 1h from startsAt", () => {
const { container } = renderTabContentStart();
ValidateTimeWheel(
container,
"startsAt",
"td.components-hour-down",
1,
-1 * 3600 * 1000,
);
});
it("scrolling up on the minute adds 1m to startsAt", () => {
const { container } = renderTabContentStart();
ValidateTimeWheel(
container,
"startsAt",
"td.components-minute",
-2,
60 * 1000,
);
});
it("scrolling down on the minute subtracts 1m from startsAt", () => {
const { container } = renderTabContentStart();
ValidateTimeWheel(
container,
"startsAt",
"td.components-minute",
1,
-1 * 60 * 1000,
);
});
it("clicking on the minute dec button subtracts 1m from startsAt", () => {
const { container } = renderTabContentStart();
ValidateTimeButton(container, "startsAt", 3, /angle-down/, -1 * 60 * 1000);
});
it("scrolling down by deltaY=2 on the minute button subtracts 1m from startsAt", () => {
const { container } = renderTabContentStart();
ValidateTimeWheel(
container,
"startsAt",
"td.components-minute-down",
2,
-1 * 60 * 1000,
);
});
it("scrolling up on the minute subtracts 1m from startsAt", () => {
const { container } = renderTabContentStart();
ValidateTimeWheel(
container,
"startsAt",
"td.components-minute",
-50,
60 * 1000,
);
});
it("scrolling down by deltaY=1 on the minute subtracts 1m from startsAt", () => {
const { container } = renderTabContentStart();
ValidateTimeWheel(
container,
"startsAt",
"td.components-minute",
1,
-1 * 60 * 1000,
);
});
});
const renderTabContentEnd = () => {
return render(<TabContentEnd silenceFormStore={silenceFormStore} />);
};
describe("<TabContentEnd />", () => {
it("Selecting date on DayPicker updates endsAt", () => {
// Verifies clicking a day in the calendar updates the endsAt date
const { container } = renderTabContentEnd();
expect(silenceFormStore.data.endsAt.toISOString()).toBe(
new Date(2061, 1, 1, 0, 0, 0).toISOString(),
);
// In v9, day buttons use .rdp-day_button class
const dayButtons = container.querySelectorAll("button.rdp-day_button");
fireEvent.click(dayButtons[23]);
expect(silenceFormStore.data.endsAt.toISOString()).toBe(
new Date(2061, 1, 24, 0, 0, 0).toISOString(),
);
});
it("clicking on the hour inc button adds 1h to endsAt", () => {
const { container } = renderTabContentEnd();
ValidateTimeButton(container, "endsAt", 0, /angle-up/, 3600 * 1000);
});
it("Today button takes you back to the current month", () => {
const { container } = renderTabContentEnd();
expect(silenceFormStore.data.endsAt.toISOString()).toBe(
new Date(2061, 1, 1, 0, 0, 0).toISOString(),
);
expect(container.querySelector(".rdp-month_caption")?.textContent).toBe(
"February 2061",
);
fireEvent.click(container.querySelector("button.rdp-button_next")!);
const captionsAfterNext = container.querySelectorAll(".rdp-month_caption");
expect(captionsAfterNext[captionsAfterNext.length - 1].textContent).toBe(
"March 2061",
);
fireEvent.click(container.querySelector("button.btn.btn-light.btn-sm")!);
const captionsAfterToday = container.querySelectorAll(".rdp-month_caption");
expect(captionsAfterToday[captionsAfterToday.length - 1].textContent).toBe(
format(new Date(), "LLLL yyyy"),
);
});
it("scrolling up on the hour button adds 1h to endsAt", () => {
const { container } = renderTabContentEnd();
ValidateTimeWheel(
container,
"endsAt",
"td.components-hour-up",
-1,
3600 * 1000,
);
});
it("scrolling up on the hour adds 1h to endsAt", () => {
const { container } = renderTabContentEnd();
ValidateTimeWheel(
container,
"endsAt",
"td.components-hour",
-1,
3600 * 1000,
);
});
it("scrolling down on the hour subtracts 1h from endsAt", () => {
const { container } = renderTabContentEnd();
ValidateTimeWheel(
container,
"endsAt",
"td.components-hour",
1,
-1 * 3600 * 1000,
);
});
it("clicking on the minute inc button adds 1m to endsAt", () => {
const { container } = renderTabContentEnd();
ValidateTimeButton(container, "endsAt", 1, /angle-up/, 60 * 1000);
});
it("scrolling up on the minute button adds 1m to endsAt", () => {
const { container } = renderTabContentEnd();
ValidateTimeWheel(
container,
"endsAt",
"td.components-minute-up",
-10,
60 * 1000,
);
});
it("clicking on the hour dec button subtracts 1h from endsAt", () => {
const { container } = renderTabContentEnd();
ValidateTimeButton(container, "endsAt", 2, /angle-down/, -1 * 3600 * 1000);
});
it("scrolling down on the hour button subtracts 1h from endsAt", () => {
const { container } = renderTabContentEnd();
ValidateTimeWheel(
container,
"endsAt",
"td.components-hour-down",
1,
-1 * 3600 * 1000,
);
});
it("clicking on the minute dec button subtracts 1m from endsAt", () => {
const { container } = renderTabContentEnd();
ValidateTimeButton(container, "endsAt", 3, /angle-down/, -1 * 60 * 1000);
});
it("scrolling down on the minute button subtracts 1m from endsAt", () => {
const { container } = renderTabContentEnd();
ValidateTimeWheel(
container,
"endsAt",
"td.components-minute-down",
50,
-1 * 60 * 1000,
);
});
it("scrolling up on the minute adds 1m to endsAt", () => {
const { container } = renderTabContentEnd();
ValidateTimeWheel(
container,
"endsAt",
"td.components-minute",
-10,
60 * 1000,
);
});
it("scrolling down on the minute subtracts 1m from endsAt", () => {
const { container } = renderTabContentEnd();
ValidateTimeWheel(
container,
"endsAt",
"td.components-minute",
15,
-1 * 60 * 1000,
);
});
});
const ValidateDurationButton = (
elemIndex: number,
iconMatch: RegExp,
expectedDiff: number,
) => {
const { container } = render(
<TabContentDuration silenceFormStore={silenceFormStore} />,
);
const buttons = container.querySelectorAll("td > span");
const button = buttons[elemIndex];
expect(button.innerHTML).toMatch(iconMatch);
const oldEndsAt = new Date(silenceFormStore.data.endsAt);
fireEvent.click(button);
expect(silenceFormStore.data.endsAt.toISOString()).not.toBe(
oldEndsAt.toISOString(),
);
const diffMS = differenceInMilliseconds(
silenceFormStore.data.endsAt,
oldEndsAt,
);
expect(diffMS).toBe(expectedDiff);
};
const ValidateDurationWheel = (
elemIndex: number,
deltaY: number,
expectedDiff: number,
) => {
const { container } = render(
<TabContentDuration silenceFormStore={silenceFormStore} />,
);
const elems = container.querySelectorAll(".components-duration");
const elem = elems[elemIndex];
const oldEndsAt = new Date(silenceFormStore.data.endsAt);
fireEvent.wheel(elem, { deltaY: deltaY });
// fire real event so cancel listener will trigger
const event = new WheelEvent("wheel", { deltaY: deltaY });
elem.dispatchEvent(event);
expect(silenceFormStore.data.endsAt.toISOString()).not.toBe(
oldEndsAt.toISOString(),
);
const diffMS = differenceInMilliseconds(
silenceFormStore.data.endsAt,
oldEndsAt,
);
expect(diffMS).toBe(expectedDiff);
};
describe("<TabContentDuration />", () => {
it("clicking on the day inc button adds 1d to endsAt", () => {
ValidateDurationButton(0, /angle-up/, 24 * 3600 * 1000);
});
it("scrolling up on the day button adds 1d to endsAt", () => {
ValidateDurationWheel(0, -1, 24 * 3600 * 1000);
});
it("clicking on the day dec button subtracts 1d from endsAt", () => {
ValidateDurationButton(2, /angle-down/, -1 * 24 * 3600 * 1000);
});
it("scrolling down on the day button subtracts 1d from endsAt", () => {
ValidateDurationWheel(0, 1, -1 * 24 * 3600 * 1000);
});
it("clicking on the hour inc button adds 1h to endsAt", () => {
ValidateDurationButton(3, /angle-up/, 3600 * 1000);
});
it("scrolling up on the hour inc button adds 1h to endsAt", () => {
ValidateDurationWheel(1, -2, 3600 * 1000);
});
it("clicking on the hour dec button subtracts 1h from endsAt", () => {
ValidateDurationButton(5, /angle-down/, -1 * 3600 * 1000);
});
it("scrolling down on the hour dec button subtracts 1h from endsAt", () => {
ValidateDurationWheel(1, 2, -1 * 3600 * 1000);
});
it("clicking on the minute inc button adds 5m to endsAt", () => {
ValidateDurationButton(6, /angle-up/, 5 * 60 * 1000);
});
it("scrolling up on the minute inc button adds 5m to endsAt", () => {
ValidateDurationWheel(2, -1, 5 * 60 * 1000);
});
it("clicking on the minute dec button subtracts 5m from endsAt", () => {
ValidateDurationButton(8, /angle-down/, -1 * 5 * 60 * 1000);
});
it("scrolling down on the minute dec button subtracts 5m from endsAt", () => {
ValidateDurationWheel(2, 1, -1 * 5 * 60 * 1000);
});
});
const SetDurationTo = (hours: number, minutes: number) => {
const startsAt = new Date(2060, 1, 1, 0, 0, 0);
const endsAt = addMinutes(addHours(startsAt, hours), minutes);
silenceFormStore.data.setStart(startsAt);
silenceFormStore.data.setEnd(endsAt);
};
describe("<TabContentDuration /> inc minute CalculateChangeValue", () => {
it("inc on 0:1:0 duration sets 0:1:5", () => {
SetDurationTo(1, 0);
ValidateDurationButton(6, /angle-up/, 5 * 60 * 1000);
});
it("inc on 0:1:1 duration sets 0:1:2", () => {
SetDurationTo(1, 1);
ValidateDurationButton(6, /angle-up/, 60 * 1000);
});
it("inc on 0:1:4 duration sets 0:1:5", () => {
SetDurationTo(1, 4);
ValidateDurationButton(6, /angle-up/, 60 * 1000);
});
it("inc on 0:1:5 duration sets 0:1:10", () => {
SetDurationTo(1, 5);
ValidateDurationButton(6, /angle-up/, 5 * 60 * 1000);
});
it("inc on 0:1:6 duration sets 0:1:10", () => {
SetDurationTo(1, 6);
ValidateDurationButton(6, /angle-up/, 4 * 60 * 1000);
});
it("inc on 0:0:55 duration sets 0:1:0", () => {
SetDurationTo(0, 55);
ValidateDurationButton(6, /angle-up/, 5 * 60 * 1000);
});
});
describe("<TabContentDuration /> dec minute CalculateChangeValue", () => {
it("inc on 0:1:0 duration sets 0:0:55", () => {
SetDurationTo(1, 0);
ValidateDurationButton(8, /angle-down/, -5 * 60 * 1000);
});
it("inc on 0:0:59 duration sets 0:0:55", () => {
SetDurationTo(0, 59);
ValidateDurationButton(8, /angle-down/, -4 * 60 * 1000);
});
it("inc on 0:0:56 duration sets 0:0:55", () => {
SetDurationTo(0, 56);
ValidateDurationButton(8, /angle-down/, -1 * 60 * 1000);
});
it("inc on 0:0:55 duration sets 0:0:50", () => {
SetDurationTo(1, 0);
ValidateDurationButton(8, /angle-down/, -5 * 60 * 1000);
});
it("inc on 0:1:10 duration sets 0:1:5", () => {
SetDurationTo(1, 10);
ValidateDurationButton(8, /angle-down/, -5 * 60 * 1000);
});
it("inc on 0:1:6 duration sets 0:1:5", () => {
SetDurationTo(1, 6);
ValidateDurationButton(8, /angle-down/, -1 * 60 * 1000);
});
it("inc on 0:1:5 duration sets 0:1:0", () => {
SetDurationTo(1, 5);
ValidateDurationButton(8, /angle-down/, -5 * 60 * 1000);
});
it("inc on 0:1:4 duration sets 0:1:3", () => {
SetDurationTo(1, 4);
ValidateDurationButton(8, /angle-down/, -1 * 60 * 1000);
});
it("inc on 0:1:1 duration sets 0:1:0", () => {
SetDurationTo(1, 1);
ValidateDurationButton(8, /angle-down/, -1 * 60 * 1000);
});
});