feat(FN-2896): keep GitHub import modal open after successful import

- Keep the GitHub import modal open after successful issue or PR import instead of auto-closing
- Reset selected issue/PR after import so the Import action disables until a new selection is made
- On mobile, return from preview to list view after a successful import for faster repeated imports
- Expand GitHubImportModal tests to cover stay-open behavior, selection reset, mobile view reset, and stabilized auto-load button assertions
This commit is contained in:
Fusion
2026-04-28 16:20:24 -07:00
committed by gsxdsm
parent dd9c126ce9
commit afa804ae17
2 changed files with 135 additions and 7 deletions

View File

@@ -286,7 +286,10 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
try {
const task = await apiImportGitHubIssue(owner.trim(), repo.trim(), selectedIssueNumber, projectId);
onImport(task);
onClose();
setSelectedIssueNumber(null);
if (isMobile && mobileView === "preview") {
setMobileView("list");
}
} catch (err) {
const msg = getErrorMessage(err);
if (msg?.includes("already imported")) {
@@ -306,7 +309,10 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
try {
const task = await apiImportGitHubPull(owner.trim(), repo.trim(), selectedPullNumber, projectId);
onImport(task);
onClose();
setSelectedPullNumber(null);
if (isMobile && mobileView === "preview") {
setMobileView("list");
}
} catch (err) {
const msg = getErrorMessage(err);
if (msg?.includes("already imported")) {
@@ -318,7 +324,7 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
setImporting(false);
}
}
}, [activeTab, selectedIssueNumber, selectedPullNumber, owner, repo, onImport, onClose]);
}, [activeTab, selectedIssueNumber, selectedPullNumber, owner, repo, onImport, isMobile, mobileView]);
const selectedIssue = issues.find((i) => i.number === selectedIssueNumber);
const selectedPull = pulls.find((p) => p.number === selectedPullNumber);

View File

@@ -234,8 +234,10 @@ describe("GitHubImportModal", () => {
expect(resultsSection).toBeTruthy();
});
const loadButton = screen.getByRole("button", { name: /Load issues/i }) as HTMLButtonElement;
expect(loadButton.disabled).toBe(false);
await waitFor(() => {
const loadButton = screen.getByRole("button", { name: /Load issues/i }) as HTMLButtonElement;
expect(loadButton.disabled).toBe(false);
});
});
it("auto-loads issues when single remote is detected", async () => {
@@ -395,7 +397,49 @@ describe("GitHubImportModal", () => {
await waitFor(() => {
expect(apiImportGitHubIssue).toHaveBeenCalledWith("dustinbyrne", "kb", 1, "project-1");
expect(onImport).toHaveBeenCalledWith(mockTask);
expect(onClose).toHaveBeenCalled();
expect(onClose).not.toHaveBeenCalled();
expect(screen.getByText("Import from GitHub")).toBeTruthy();
});
});
it("stays open and resets selection after successful issue import", async () => {
const issues = [
{ number: 1, title: "First Issue", body: "Body 1", html_url: "https://github.com/owner/repo/issues/1", labels: [] },
];
vi.mocked(fetchGitRemotes).mockResolvedValueOnce([{ name: "origin", owner: "owner", repo: "repo", url: "" }]);
vi.mocked(apiFetchGitHubIssues).mockResolvedValueOnce(issues);
vi.mocked(apiImportGitHubIssue).mockResolvedValueOnce(mockTask);
const { rerender } = render(
<GitHubImportModal isOpen={true} onClose={onClose} onImport={onImport} tasks={[]} projectId="project-1" />,
);
await waitFor(() => {
expect(screen.getByText("First Issue")).toBeTruthy();
});
const importButton = screen.getByRole("button", { name: /Import$/i }) as HTMLButtonElement;
fireEvent.click(screen.getByRole("radio", { name: /Select issue #1/i }));
expect(importButton.disabled).toBe(false);
fireEvent.click(importButton);
await waitFor(() => {
expect(apiImportGitHubIssue).toHaveBeenCalledWith("owner", "repo", 1, "project-1");
expect(onClose).not.toHaveBeenCalled();
});
await waitFor(() => {
expect((screen.getByRole("button", { name: /Import$/i }) as HTMLButtonElement).disabled).toBe(true);
});
rerender(
<GitHubImportModal isOpen={true} onClose={onClose} onImport={onImport} tasks={[mockTask]} projectId="project-1" />,
);
await waitFor(() => {
expect(screen.getByText("First Issue")).toBeTruthy();
expect(screen.getByText("Imported")).toBeTruthy();
});
});
@@ -585,6 +629,43 @@ describe("GitHubImportModal", () => {
const previewPane = screen.getByTestId("github-import-preview-pane");
expect(previewPane.classList.contains("active")).toBe(false);
});
it("returns to list view on mobile after successful import", async () => {
Object.defineProperty(window, "innerWidth", {
writable: true,
configurable: true,
value: 480,
});
const issues = [
{ number: 1, title: "First Issue", body: "Body 1", html_url: "https://github.com/owner/repo/issues/1", labels: [] },
];
vi.mocked(fetchGitRemotes).mockResolvedValueOnce([{ name: "origin", owner: "owner", repo: "repo", url: "" }]);
vi.mocked(apiFetchGitHubIssues).mockResolvedValueOnce(issues);
vi.mocked(apiImportGitHubIssue).mockResolvedValueOnce(mockTask);
render(<GitHubImportModal isOpen={true} onClose={onClose} onImport={onImport} tasks={[]} projectId="project-1" />);
await waitFor(() => {
expect(screen.getByText("First Issue")).toBeTruthy();
});
fireEvent.click(screen.getByRole("radio", { name: /Select issue #1/i }));
const previewPane = screen.getByTestId("github-import-preview-pane");
await waitFor(() => {
expect(previewPane.classList.contains("active")).toBe(true);
});
fireEvent.click(screen.getByRole("button", { name: /Import$/i }));
await waitFor(() => {
expect(apiImportGitHubIssue).toHaveBeenCalledWith("owner", "repo", 1, "project-1");
});
expect(previewPane.classList.contains("active")).toBe(false);
expect(onClose).not.toHaveBeenCalled();
});
});
describe("modal actions", () => {
@@ -763,7 +844,48 @@ describe("GitHubImportModal", () => {
await waitFor(() => {
expect(apiImportGitHubPull).toHaveBeenCalledWith("dustinbyrne", "kb", 1, "project-1");
expect(onImport).toHaveBeenCalledWith(mockPRTask);
expect(onClose).toHaveBeenCalled();
expect(onClose).not.toHaveBeenCalled();
expect(screen.getByText("Import from GitHub")).toBeTruthy();
});
});
it("stays open and resets selection after successful PR import", async () => {
vi.mocked(fetchGitRemotes).mockResolvedValueOnce([{ name: "origin", owner: "owner", repo: "repo", url: "" }]);
vi.mocked(apiFetchGitHubPulls).mockResolvedValueOnce(mockPulls);
vi.mocked(apiImportGitHubPull).mockResolvedValueOnce(mockPRTask);
const { rerender } = render(
<GitHubImportModal isOpen={true} onClose={onClose} onImport={onImport} tasks={[]} projectId="project-1" />,
);
fireEvent.click(screen.getByRole("tab", { name: /Pull Requests/i }));
await waitFor(() => {
expect(screen.getByText("Test PR")).toBeTruthy();
});
const importButton = screen.getByRole("button", { name: /Import$/i }) as HTMLButtonElement;
fireEvent.click(screen.getByRole("radio", { name: /Select pull request #1/i }));
expect(importButton.disabled).toBe(false);
fireEvent.click(importButton);
await waitFor(() => {
expect(apiImportGitHubPull).toHaveBeenCalledWith("owner", "repo", 1, "project-1");
expect(onClose).not.toHaveBeenCalled();
});
await waitFor(() => {
expect((screen.getByRole("button", { name: /Import$/i }) as HTMLButtonElement).disabled).toBe(true);
});
rerender(
<GitHubImportModal isOpen={true} onClose={onClose} onImport={onImport} tasks={[mockPRTask]} projectId="project-1" />,
);
await waitFor(() => {
expect(screen.getByText("Test PR")).toBeTruthy();
expect(screen.getByText("Imported")).toBeTruthy();
});
});