diff --git a/apps/app/src/components/secondary-panel/SecondaryPanelLayout.split-drag.test.tsx b/apps/app/src/components/secondary-panel/SecondaryPanelLayout.split-drag.test.tsx new file mode 100644 index 0000000000..8f47504b6a --- /dev/null +++ b/apps/app/src/components/secondary-panel/SecondaryPanelLayout.split-drag.test.tsx @@ -0,0 +1,110 @@ +// @vitest-environment jsdom + +import { cleanup, fireEvent, render, screen } from "@testing-library/react"; +import { afterEach, expect, it, vi } from "vitest"; +import { CompactViewportOverrideProvider } from "@bb/shared-ui/hooks/use-compact-viewport"; +import { + PaneContext, + type PaneContextValue, +} from "@/views/thread-detail/PaneContext"; +import { beginSplitDrag, decideThreadDrop } from "@/lib/split-drag"; +import { SecondaryPanelLayout } from "./SecondaryPanelLayout"; + +const noop = () => {}; +const context: PaneContextValue = { + paneId: "pane-test", + isFocused: true, + isSplitPane: false, + secondaryPanelHost: null, + reservesWindowPanelToggle: false, + onRequestClose: null, + isMaximized: false, + onToggleMaximize: null, + isBoundedPane: false, + isTopRow: true, + ownsWindowTopLeft: true, + navigateInPane: noop, +}; + +afterEach(() => { + fireEvent(window, new Event("pointercancel")); + cleanup(); + vi.restoreAllMocks(); + vi.unstubAllGlobals(); +}); + +it.each([false, true])( + "resolves a single chat's center with panel open=%s", + (open) => { + render( +
+ + + Chat} + renderPanel={() =>
Details
} + composerHost={null} + compactPresentation="shelf" + /> +
+
+
, + ); + const workspace = screen.getByTestId("workspace"); + const chat = screen.getByTestId("chat"); + const width = open ? 500 : 1000; + vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation( + function (this: HTMLElement) { + return new DOMRect(200, 0, this === workspace ? 1000 : width, 600); + }, + ); + const originalHitTest = Object.getOwnPropertyDescriptor( + document, + "elementsFromPoint", + ); + Object.defineProperty(document, "elementsFromPoint", { + configurable: true, + value: () => [chat], + }); + const onDrop = vi.fn(); + beginSplitDrag({ + ghostLabel: "Another thread", + fallback: { paneId: context.paneId, container: workspace }, + shouldEngage: () => true, + decide: (_paneId, zone) => + decideThreadDrop({ zone, threadAlreadyOpen: false, atMaxPanes: false }), + onDrop, + }); + try { + fireEvent( + window, + new MouseEvent("pointermove", { + clientX: 200 + width / 2, + clientY: 300, + }), + ); + expect( + document.querySelector("[data-split-drag-label]")?.textContent, + ).toBe("Replace this chat"); + fireEvent(window, new Event("pointerup")); + expect(onDrop).toHaveBeenCalledWith({ + paneId: context.paneId, + zone: "center", + }); + const target = chat.closest("[data-split-pane-id]"); + expect(target?.contains(screen.getByTestId("details"))).toBe(false); + } finally { + if (originalHitTest) + Object.defineProperty(document, "elementsFromPoint", originalHitTest); + else Reflect.deleteProperty(document, "elementsFromPoint"); + } + }, +); diff --git a/apps/app/src/components/secondary-panel/SecondaryPanelLayout.tsx b/apps/app/src/components/secondary-panel/SecondaryPanelLayout.tsx index bafad99c04..aa603eab46 100644 --- a/apps/app/src/components/secondary-panel/SecondaryPanelLayout.tsx +++ b/apps/app/src/components/secondary-panel/SecondaryPanelLayout.tsx @@ -297,6 +297,9 @@ export function SecondaryPanelLayout({ const mainContent = (