Files
fusion/packages/dashboard/app/components/__tests__/PrCreateModal.layout.test.tsx
gsxdsm 411806163f FN-7170: make Create PR dialog draggable and resizable
Move the Create PR dialog onto the shared floating window shell.

- Render PrCreateModal in FloatingWindow with persisted desktop geometry and embedded-header dragging.
- Add CSS that lets the modal fill its floating panel while preserving a full-screen mobile layout.
- Update docs, release notes, and modal tests for floating behavior, resize handles, and non-overlay dismissal.

Files changed:
 .changeset/fn-7170-pr-create-modal-floating.md     |  7 ++
 docs/dashboard-guide.md                            |  1 +
 .../dashboard/app/components/PrCreateModal.css     | 63 +++++++++++----
 .../dashboard/app/components/PrCreateModal.tsx     | 28 ++++---
 .../__tests__/PrCreateModal.layout.test.tsx        |  6 +-
 .../components/__tests__/PrCreateModal.test.tsx    | 94 +++++++++++++++++++---
 .../app/components/__tests__/TaskCard.test.tsx     |  8 +-
 7 files changed, 168 insertions(+), 39 deletions(-)

Fusion-Task-Id: FN-7170

Fusion-Task-Lineage: 03452d6a-0da7-4891-a58e-6d93a93383f7

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-06-27 23:42:11 -07:00

83 lines
2.6 KiB
TypeScript

import { render, screen, waitFor } from "@testing-library/react";
import { beforeAll, afterAll, beforeEach, describe, expect, it, vi } from "vitest";
import { PrCreateModal } from "../PrCreateModal";
import { loadAllAppCss } from "../../test/cssFixture";
const mocks = vi.hoisted(() => ({
generatePrMetadata: vi.fn(),
fetchPrPreflight: vi.fn(),
fetchPrOptions: vi.fn(),
createPr: vi.fn(),
pushPrBranch: vi.fn(),
resolvePrConflicts: vi.fn(),
}));
vi.mock("../../api", () => ({
generatePrMetadata: mocks.generatePrMetadata,
fetchPrPreflight: mocks.fetchPrPreflight,
fetchPrOptions: mocks.fetchPrOptions,
createPr: mocks.createPr,
pushPrBranch: mocks.pushPrBranch,
resolvePrConflicts: mocks.resolvePrConflicts,
}));
describe("PrCreateModal layout", () => {
let styleEl: HTMLStyleElement;
beforeAll(() => {
styleEl = document.createElement("style");
styleEl.textContent = loadAllAppCss();
document.head.appendChild(styleEl);
});
afterAll(() => {
styleEl.remove();
});
beforeEach(() => {
vi.clearAllMocks();
mocks.generatePrMetadata.mockResolvedValue({ title: "AI title", body: "AI body", templateUsed: false });
mocks.fetchPrPreflight.mockResolvedValue({
branchOnRemote: true,
commitsPresent: true,
conflictsWithBase: false,
ghAuthOk: true,
defaultBaseBranch: "main",
head: "fusion/FN-5049",
commits: [],
changedFiles: [],
});
mocks.fetchPrOptions.mockResolvedValue({
baseBranches: ["main"],
reviewers: [],
assignees: [],
labels: [],
});
});
it("renders a dedicated scroll body between modal header and actions", async () => {
render(<PrCreateModal open taskId="FN-5049" onClose={vi.fn()} onCreated={vi.fn()} addToast={vi.fn()} />);
await waitFor(() => expect(mocks.generatePrMetadata).toHaveBeenCalled());
const dialog = screen.getByRole("dialog", { name: "Create Pull Request" });
const modal = dialog.classList.contains("modal") ? dialog : dialog.closest(".modal");
expect(modal).toBeTruthy();
const header = modal?.querySelector(":scope > .modal-header");
const body = modal?.querySelector(":scope > .pr-create-modal__body");
const actions = modal?.querySelector(":scope > .modal-actions");
expect(header).toBeTruthy();
expect(body).toBeTruthy();
expect(actions).toBeTruthy();
expect(actions?.parentElement).toBe(modal);
const allAutoOverflow = Array.from(modal?.querySelectorAll<HTMLElement>("*") ?? []).filter(
(element) => getComputedStyle(element).overflowY === "auto",
);
expect(allAutoOverflow).toHaveLength(1);
expect(allAutoOverflow[0]).toBe(body);
});
});