Fixes #44805 Fixed a bug where the "Add custom variable" modal would clear entered values when switching focus to another browser tab or application window due to network refetches.
193 lines
6.1 KiB
TypeScript
193 lines
6.1 KiB
TypeScript
import React from "react";
|
|
import { noop } from "lodash";
|
|
import { render, screen, fireEvent, act } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
|
|
import Modal from "./Modal";
|
|
|
|
const clickBackground = async (background: Element | null) => {
|
|
if (!background) throw new Error("Background element not found");
|
|
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
|
|
await user.pointer([
|
|
{ keys: "[MouseLeft>]", target: background },
|
|
{ keys: "[/MouseLeft]", target: background },
|
|
]);
|
|
};
|
|
|
|
describe("Modal", () => {
|
|
beforeEach(() => jest.useFakeTimers());
|
|
afterEach(() => jest.useRealTimers());
|
|
|
|
it("renders title", () => {
|
|
render(
|
|
<Modal title="Foobar" onExit={noop}>
|
|
<div>test</div>
|
|
</Modal>
|
|
);
|
|
|
|
expect(screen.getByText("Foobar")).toBeVisible();
|
|
});
|
|
|
|
it("calls onExit when clicking the background overlay", async () => {
|
|
const onExit = jest.fn();
|
|
const { container } = render(
|
|
<Modal title="Test" onExit={onExit}>
|
|
<div>content</div>
|
|
</Modal>
|
|
);
|
|
|
|
const background = container.querySelector(".modal__background");
|
|
await clickBackground(background);
|
|
act(() => jest.runAllTimers());
|
|
expect(onExit).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("does not call onExit when clicking inside the modal container", () => {
|
|
const onExit = jest.fn();
|
|
const { container } = render(
|
|
<Modal title="Test" onExit={onExit}>
|
|
<div>content</div>
|
|
</Modal>
|
|
);
|
|
|
|
// Simulate drag that starts inside the container and releases on the background.
|
|
// The container stops mouseDown propagation so isDownOnBackgroundRef stays false,
|
|
// meaning the background's mouseUp handler must not fire onExit.
|
|
fireEvent.mouseDown(screen.getByText("content"));
|
|
const background = container.querySelector(".modal__background");
|
|
if (!background) throw new Error("Background element not found");
|
|
fireEvent.mouseUp(background);
|
|
expect(onExit).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("does not call onExit when clicking the background if a form inside has been interacted with", async () => {
|
|
const onExit = jest.fn();
|
|
const { container } = render(
|
|
<Modal title="Test" onExit={onExit}>
|
|
<form>
|
|
<input type="text" />
|
|
</form>
|
|
</Modal>
|
|
);
|
|
|
|
const input = screen.getByRole("textbox");
|
|
fireEvent.input(input, { target: { value: "hello" } });
|
|
|
|
const background = container.querySelector(".modal__background");
|
|
await clickBackground(background);
|
|
act(() => jest.runAllTimers());
|
|
expect(onExit).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("does not call onExit when clicking the background if a text input outside a form has been interacted with", async () => {
|
|
const onExit = jest.fn();
|
|
const { container } = render(
|
|
<Modal title="Test" onExit={onExit}>
|
|
<div>
|
|
<input type="text" />
|
|
</div>
|
|
</Modal>
|
|
);
|
|
|
|
const input = screen.getByRole("textbox");
|
|
fireEvent.input(input, { target: { value: "hello" } });
|
|
|
|
const background = container.querySelector(".modal__background");
|
|
await clickBackground(background);
|
|
act(() => jest.runAllTimers());
|
|
expect(onExit).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("does not call onExit when clicking the background if a checkbox has been checked", async () => {
|
|
const onExit = jest.fn();
|
|
const { container } = render(
|
|
<Modal title="Test" onExit={onExit}>
|
|
<div>
|
|
<input type="checkbox" />
|
|
</div>
|
|
</Modal>
|
|
);
|
|
|
|
const checkbox = screen.getByRole("checkbox");
|
|
fireEvent.click(checkbox);
|
|
|
|
const background = container.querySelector(".modal__background");
|
|
await clickBackground(background);
|
|
act(() => jest.runAllTimers());
|
|
expect(onExit).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("does not call onExit when clicking the background if a toggle has been clicked", async () => {
|
|
const onExit = jest.fn();
|
|
const { container } = render(
|
|
<Modal title="Test" onExit={onExit}>
|
|
<div>
|
|
<button type="button" role="switch" aria-checked={false}>
|
|
Toggle
|
|
</button>
|
|
</div>
|
|
</Modal>
|
|
);
|
|
|
|
const toggle = screen.getByRole("switch");
|
|
fireEvent.click(toggle);
|
|
|
|
const background = container.querySelector(".modal__background");
|
|
await clickBackground(background);
|
|
act(() => jest.runAllTimers());
|
|
expect(onExit).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("calls onExit when clicking the background if a form inside has not been interacted with", async () => {
|
|
const onExit = jest.fn();
|
|
const { container } = render(
|
|
<Modal title="Test" onExit={onExit}>
|
|
<form>
|
|
<input type="text" />
|
|
</form>
|
|
</Modal>
|
|
);
|
|
|
|
const background = container.querySelector(".modal__background");
|
|
await clickBackground(background);
|
|
act(() => jest.runAllTimers());
|
|
expect(onExit).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("does not call onExit when a backdrop mousedown is interrupted by the window losing focus", () => {
|
|
const onExit = jest.fn();
|
|
const { container } = render(
|
|
<Modal title="Test" onExit={onExit}>
|
|
<div>content</div>
|
|
</Modal>
|
|
);
|
|
|
|
const background = container.querySelector(".modal__background");
|
|
if (!background) throw new Error("Background element not found");
|
|
|
|
// User presses down on the backdrop then the window loses focus (tab switch /
|
|
// app switch) before releasing — mouseup never fires on the page.
|
|
fireEvent.mouseDown(background);
|
|
fireEvent.blur(window);
|
|
// On return, the stale mousedown state must not close the modal.
|
|
fireEvent.mouseUp(background);
|
|
act(() => jest.runAllTimers());
|
|
|
|
expect(onExit).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("does not call onExit when clicking the background if disableClosingModal is true", async () => {
|
|
const onExit = jest.fn();
|
|
const { container } = render(
|
|
<Modal title="Test" onExit={onExit} disableClosingModal>
|
|
<div>content</div>
|
|
</Modal>
|
|
);
|
|
|
|
const background = container.querySelector(".modal__background");
|
|
await clickBackground(background);
|
|
act(() => jest.runAllTimers());
|
|
expect(onExit).not.toHaveBeenCalled();
|
|
});
|
|
});
|