367 lines
11 KiB
TypeScript
Executable file
367 lines
11 KiB
TypeScript
Executable file
import { Root, createRoot } from "react-dom/client";
|
|
import { act } from "react-dom/test-utils";
|
|
import {
|
|
Panel,
|
|
PanelGroup,
|
|
PanelResizeHandle,
|
|
type PanelResizeHandleProps,
|
|
} from ".";
|
|
import { assert } from "./utils/assert";
|
|
import * as cursorUtils from "./utils/cursor";
|
|
import { getResizeHandleElement } from "./utils/dom/getResizeHandleElement";
|
|
import {
|
|
dispatchPointerEvent,
|
|
mockBoundingClientRect,
|
|
verifyAttribute,
|
|
} from "./utils/test-utils";
|
|
|
|
jest.mock("./utils/cursor", () => ({
|
|
getCursorStyle: jest.fn(),
|
|
resetGlobalCursorStyle: jest.fn(),
|
|
setGlobalCursorStyle: jest.fn(),
|
|
}));
|
|
|
|
describe("PanelResizeHandle", () => {
|
|
let expectedWarnings: string[] = [];
|
|
let root: Root;
|
|
let container: HTMLElement;
|
|
|
|
beforeEach(() => {
|
|
// @ts-expect-error
|
|
global.IS_REACT_ACT_ENVIRONMENT = true;
|
|
container = document.createElement("div");
|
|
document.body.appendChild(container);
|
|
|
|
expectedWarnings = [];
|
|
root = createRoot(container);
|
|
|
|
jest.spyOn(console, "warn").mockImplementation((actualMessage: string) => {
|
|
const match = expectedWarnings.findIndex((expectedMessage) => {
|
|
return actualMessage.includes(expectedMessage);
|
|
});
|
|
|
|
if (match >= 0) {
|
|
expectedWarnings.splice(match, 1);
|
|
return;
|
|
}
|
|
|
|
throw Error(`Unexpected warning: ${actualMessage}`);
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
jest.resetModules();
|
|
|
|
act(() => {
|
|
root.unmount();
|
|
});
|
|
|
|
expect(expectedWarnings).toHaveLength(0);
|
|
});
|
|
|
|
it("should support ...rest attributes", () => {
|
|
act(() => {
|
|
root.render(
|
|
<PanelGroup direction="horizontal">
|
|
<Panel />
|
|
<PanelResizeHandle
|
|
data-test-name="foo"
|
|
id="handle"
|
|
tabIndex={123}
|
|
title="bar"
|
|
/>
|
|
<Panel />
|
|
</PanelGroup>
|
|
);
|
|
});
|
|
|
|
const element = getResizeHandleElement("handle", container);
|
|
assert(element, "");
|
|
expect(element.tabIndex).toBe(123);
|
|
expect(element.getAttribute("data-test-name")).toBe("foo");
|
|
expect(element.title).toBe("bar");
|
|
});
|
|
|
|
function setupMockedGroup({
|
|
leftProps = {},
|
|
rightProps = {},
|
|
}: {
|
|
leftProps?: Partial<PanelResizeHandleProps>;
|
|
rightProps?: Partial<PanelResizeHandleProps>;
|
|
} = {}) {
|
|
act(() => {
|
|
root.render(
|
|
<PanelGroup direction="horizontal" id="test-group">
|
|
<Panel />
|
|
<PanelResizeHandle id="handle-left" tabIndex={1} {...leftProps} />
|
|
<Panel />
|
|
<PanelResizeHandle id="handle-right" tabIndex={2} {...rightProps} />
|
|
<Panel />
|
|
</PanelGroup>
|
|
);
|
|
});
|
|
|
|
const leftElement = getResizeHandleElement("handle-left", container);
|
|
const rightElement = getResizeHandleElement("handle-right", container);
|
|
|
|
assert(leftElement, "");
|
|
assert(rightElement, "");
|
|
|
|
// JSDom doesn't properly handle bounding rects
|
|
mockBoundingClientRect(leftElement, {
|
|
x: 50,
|
|
y: 0,
|
|
height: 50,
|
|
width: 2,
|
|
});
|
|
mockBoundingClientRect(rightElement, {
|
|
x: 100,
|
|
y: 0,
|
|
height: 50,
|
|
width: 2,
|
|
});
|
|
|
|
return {
|
|
leftElement,
|
|
rightElement,
|
|
};
|
|
}
|
|
|
|
describe("callbacks", () => {
|
|
describe("onDragging", () => {
|
|
it("should fire when dragging starts/stops", () => {
|
|
const onDragging = jest.fn();
|
|
|
|
const { leftElement } = setupMockedGroup({
|
|
leftProps: { onDragging },
|
|
});
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointermove", leftElement);
|
|
});
|
|
expect(onDragging).not.toHaveBeenCalled();
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointerdown", leftElement);
|
|
});
|
|
expect(onDragging).toHaveBeenCalledTimes(1);
|
|
expect(onDragging).toHaveBeenCalledWith(true);
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointerup", leftElement);
|
|
});
|
|
expect(onDragging).toHaveBeenCalledTimes(2);
|
|
expect(onDragging).toHaveBeenCalledWith(false);
|
|
});
|
|
|
|
it("should only fire for the handle that has been dragged", () => {
|
|
const onDraggingLeft = jest.fn();
|
|
const onDraggingRight = jest.fn();
|
|
|
|
const { leftElement } = setupMockedGroup({
|
|
leftProps: { onDragging: onDraggingLeft },
|
|
rightProps: { onDragging: onDraggingRight },
|
|
});
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointermove", leftElement);
|
|
});
|
|
expect(onDraggingLeft).not.toHaveBeenCalled();
|
|
expect(onDraggingRight).not.toHaveBeenCalled();
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointerdown", leftElement);
|
|
});
|
|
expect(onDraggingLeft).toHaveBeenCalledTimes(1);
|
|
expect(onDraggingLeft).toHaveBeenCalledWith(true);
|
|
expect(onDraggingRight).not.toHaveBeenCalled();
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointerup", leftElement);
|
|
});
|
|
expect(onDraggingLeft).toHaveBeenCalledTimes(2);
|
|
expect(onDraggingLeft).toHaveBeenCalledWith(false);
|
|
expect(onDraggingRight).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("data attributes", () => {
|
|
it("should initialize with the correct props based attributes", () => {
|
|
const { leftElement, rightElement } = setupMockedGroup();
|
|
|
|
verifyAttribute(leftElement, "data-panel-group-id", "test-group");
|
|
verifyAttribute(leftElement, "data-resize-handle", "");
|
|
verifyAttribute(leftElement, "data-panel-group-direction", "horizontal");
|
|
verifyAttribute(leftElement, "data-panel-resize-handle-enabled", "true");
|
|
verifyAttribute(
|
|
leftElement,
|
|
"data-panel-resize-handle-id",
|
|
"handle-left"
|
|
);
|
|
|
|
verifyAttribute(rightElement, "data-panel-group-id", "test-group");
|
|
verifyAttribute(rightElement, "data-resize-handle", "");
|
|
verifyAttribute(rightElement, "data-panel-group-direction", "horizontal");
|
|
verifyAttribute(rightElement, "data-panel-resize-handle-enabled", "true");
|
|
verifyAttribute(
|
|
rightElement,
|
|
"data-panel-resize-handle-id",
|
|
"handle-right"
|
|
);
|
|
});
|
|
|
|
it("should update data-resize-handle-active and data-resize-handle-state when dragging starts/stops", () => {
|
|
const { leftElement, rightElement } = setupMockedGroup();
|
|
verifyAttribute(leftElement, "data-resize-handle-active", null);
|
|
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
verifyAttribute(leftElement, "data-resize-handle-state", "inactive");
|
|
verifyAttribute(rightElement, "data-resize-handle-state", "inactive");
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointermove", leftElement);
|
|
});
|
|
verifyAttribute(leftElement, "data-resize-handle-active", null);
|
|
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
verifyAttribute(leftElement, "data-resize-handle-state", "hover");
|
|
verifyAttribute(rightElement, "data-resize-handle-state", "inactive");
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointerdown", leftElement);
|
|
});
|
|
verifyAttribute(leftElement, "data-resize-handle-active", "pointer");
|
|
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
verifyAttribute(leftElement, "data-resize-handle-state", "drag");
|
|
verifyAttribute(rightElement, "data-resize-handle-state", "inactive");
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointermove", leftElement);
|
|
});
|
|
verifyAttribute(leftElement, "data-resize-handle-active", "pointer");
|
|
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
verifyAttribute(leftElement, "data-resize-handle-state", "drag");
|
|
verifyAttribute(rightElement, "data-resize-handle-state", "inactive");
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointerup", leftElement);
|
|
});
|
|
verifyAttribute(leftElement, "data-resize-handle-active", null);
|
|
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
verifyAttribute(leftElement, "data-resize-handle-state", "hover");
|
|
verifyAttribute(rightElement, "data-resize-handle-state", "inactive");
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointermove", rightElement);
|
|
});
|
|
verifyAttribute(leftElement, "data-resize-handle-active", null);
|
|
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
verifyAttribute(leftElement, "data-resize-handle-state", "inactive");
|
|
verifyAttribute(rightElement, "data-resize-handle-state", "hover");
|
|
});
|
|
|
|
it("should update data-resize-handle-active when focused", () => {
|
|
const { leftElement, rightElement } = setupMockedGroup();
|
|
verifyAttribute(leftElement, "data-resize-handle-active", null);
|
|
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
|
|
act(() => {
|
|
leftElement.focus();
|
|
});
|
|
expect(document.activeElement).toBe(leftElement);
|
|
verifyAttribute(leftElement, "data-resize-handle-active", "keyboard");
|
|
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
|
|
act(() => {
|
|
leftElement.blur();
|
|
});
|
|
expect(document.activeElement).not.toBe(leftElement);
|
|
verifyAttribute(leftElement, "data-resize-handle-active", null);
|
|
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
});
|
|
});
|
|
|
|
describe("a11y", () => {
|
|
it("should pass explicit id prop to DOM", () => {
|
|
act(() => {
|
|
root.render(
|
|
<PanelGroup direction="horizontal">
|
|
<Panel />
|
|
<PanelResizeHandle id="explicit-id" />
|
|
<Panel />
|
|
</PanelGroup>
|
|
);
|
|
});
|
|
|
|
const element = container.querySelector("[data-resize-handle]");
|
|
|
|
expect(element).not.toBeNull();
|
|
expect(element?.getAttribute("id")).toBe("explicit-id");
|
|
});
|
|
|
|
it("should not pass auto-generated id prop to DOM", () => {
|
|
act(() => {
|
|
root.render(
|
|
<PanelGroup direction="horizontal">
|
|
<Panel />
|
|
<PanelResizeHandle />
|
|
<Panel />
|
|
</PanelGroup>
|
|
);
|
|
});
|
|
|
|
const element = container.querySelector("[data-resize-handle]");
|
|
|
|
expect(element).not.toBeNull();
|
|
expect(element?.getAttribute("id")).toBeNull();
|
|
});
|
|
});
|
|
|
|
it("resets the global cursor style on unmount", () => {
|
|
const onDraggingLeft = jest.fn();
|
|
|
|
const { leftElement } = setupMockedGroup({
|
|
leftProps: { onDragging: onDraggingLeft },
|
|
rightProps: {},
|
|
});
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointermove", leftElement);
|
|
});
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointerdown", leftElement);
|
|
});
|
|
expect(onDraggingLeft).toHaveBeenCalledTimes(1);
|
|
expect(onDraggingLeft).toHaveBeenCalledWith(true);
|
|
|
|
expect(cursorUtils.resetGlobalCursorStyle).not.toHaveBeenCalled();
|
|
expect(cursorUtils.setGlobalCursorStyle).toHaveBeenCalled();
|
|
|
|
onDraggingLeft.mockReset();
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointermove", leftElement);
|
|
});
|
|
expect(onDraggingLeft).not.toHaveBeenCalled();
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointerup", leftElement);
|
|
});
|
|
expect(onDraggingLeft).toHaveBeenCalledTimes(1);
|
|
expect(onDraggingLeft).toHaveBeenCalledWith(false);
|
|
|
|
onDraggingLeft.mockReset();
|
|
|
|
act(() => {
|
|
dispatchPointerEvent("pointermove", leftElement);
|
|
});
|
|
expect(onDraggingLeft).not.toHaveBeenCalled();
|
|
|
|
act(() => {
|
|
root.unmount();
|
|
});
|
|
|
|
expect(cursorUtils.resetGlobalCursorStyle).toHaveBeenCalled();
|
|
});
|
|
});
|