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>
83 lines
2.6 KiB
TypeScript
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);
|
|
});
|
|
});
|