FN-8110: add failed-check fix task action
Add an in-context recovery path for failed GitHub PR checks. - Add a per-failed-check Create fix task action with progress and result feedback. - Compose fix-task context from the selected PR, repository, branches, check status, and details URL. - Cover the GitHub import behavior and document the feature release. Files changed: .changeset/fn-8110-pr-failed-check-fix-task.md | 7 + docs/dashboard-guide.md | 2 +- .../dashboard/app/components/GitHubImportModal.css | 23 ++++ .../dashboard/app/components/GitHubImportModal.tsx | 91 ++++++++++++- .../__tests__/GitHubImportModal.test.tsx | 148 +++++++++++++++++++++ 5 files changed, 269 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-8110 Fusion-Task-Lineage: 0f69a4e2-d950-4a9f-925e-bd56fb6bf45d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8110-pr-failed-check-fix-task.md
Normal file
7
.changeset/fn-8110-pr-failed-check-fix-task.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Add a Create fix task button on failed PR checks in the GitHub import preview.
|
||||
category: feature
|
||||
dev: Reuses createTask with an auto-composed PR and check context prompt (FN-8110).
|
||||
@@ -312,7 +312,7 @@ Use Import Tasks on desktop/tablet:
|
||||
3. Stay on **Issues** or switch to **Pull Requests**, then optionally enter issue label filters before loading results.
|
||||
Expected outcome: the list pane shows matching open issues or pull requests and marks entries that already exist on the board. Use **Hide imported** beside the imported count to remove those unavailable rows from the current Issues, Pull Requests, or GitLab list; turning it off restores the greyed **Imported** rows. After a successful GitHub or GitLab import, the source row is marked **Imported** and made unavailable immediately, without waiting for the board list to refresh.
|
||||
4. Select an issue or pull request row.
|
||||
Expected outcome: the full-width candidate list stays visible while its title, source link, body, labels or PR metadata, and import controls open in a draggable and resizable detail window. On mobile, that detail is a full-screen sheet. When selected title/body content is in another language, the detail offers **Translate**, **Show original** / **Show translation**, and **Dismiss**; translation is display-only.
|
||||
Expected outcome: the full-width candidate list stays visible while its title, source link, body, labels or PR metadata, and import controls open in a draggable and resizable detail window. On mobile, that detail is a full-screen sheet. When selected title/body content is in another language, the detail offers **Translate**, **Show original** / **Show translation**, and **Dismiss**; translation is display-only. A pull request preview also shows its checks; each failed check has a **Create fix task** action that creates a new task prefilled with the repository, PR, branches, check status, and check-details link.
|
||||
5. Select the import action in the detail window.
|
||||
Expected outcome: Fusion creates a task (or review task for a pull request), preserves GitHub provenance/tracking metadata, closes the detail window, and returns to the list.
|
||||
|
||||
|
||||
@@ -1154,6 +1154,18 @@ Checks + Comments sections live below the PR body in the scrollable preview pane
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
word-break: break-word;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:GitHubImport 2026-07-16-17:00:
|
||||
FN-8110 keeps a failed check's repair action attached to its own row without introducing a new button visual system.
|
||||
The shared btn primitives own colors and control treatment; this rule only reserves row layout and mobile-safe wrapping.
|
||||
*/
|
||||
.github-import-pr-check-fix-task {
|
||||
flex: 0 0 auto;
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
|
||||
a.github-import-pr-check-name:hover {
|
||||
@@ -1628,6 +1640,17 @@ Import Tasks embedded header now adopts the canonical ViewHeader chrome — edge
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.github-import-pr-check-row {
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.github-import-pr-check-fix-task {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:GitHubImport 2026-07-15-18:20:
|
||||
This is the breakpoint where the detail FloatingWindow becomes a full-screen sheet. The panel's var(--space-lg) inset already reads as a conventional mobile gutter, so only the actions change: they grow to a 40px touch target because the desktop 30px pair is a mouse-sized hit area and Import is the sheet's primary action.
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
apiImportGitLabMergeRequest,
|
||||
fetchSettings,
|
||||
fetchGitRemotes,
|
||||
createTask,
|
||||
type GitHubIssue,
|
||||
type GitHubPull,
|
||||
type GitHubPullDetail,
|
||||
@@ -302,6 +303,34 @@ notice tells the operator to refine by label rather than pretending the list is
|
||||
const ISSUES_FETCH_CAP = 300;
|
||||
const ISSUES_PAGE_SIZE = 30;
|
||||
|
||||
/**
|
||||
* FNXC:GitHubImport 2026-07-16-17:00:
|
||||
* FN-8110 lets an operator create a repair task from a failed PR check without retyping its context.
|
||||
* Keep this prompt composition pure so every check row carries its repository, PR, branch, status, and details-link evidence.
|
||||
*/
|
||||
export function buildCheckFixTaskPrompt(
|
||||
pull: GitHubPull,
|
||||
check: GitHubPullDetail["checks"][number],
|
||||
repoSlug: string,
|
||||
): { title: string; description: string } {
|
||||
const indicator = check.conclusion ?? check.status ?? "unknown";
|
||||
const details = check.detailsUrl ? `\nCheck details: ${check.detailsUrl}` : "";
|
||||
|
||||
return {
|
||||
title: `Fix failing check "${check.name}" on PR #${pull.number}`,
|
||||
description: [
|
||||
"Fix the failing GitHub pull request check described below.",
|
||||
"",
|
||||
`Repository: ${repoSlug}`,
|
||||
`Pull request: #${pull.number} — ${pull.title}`,
|
||||
`PR URL: ${pull.html_url}`,
|
||||
`Branches: ${pull.headBranch} → ${pull.baseBranch}`,
|
||||
`Failing check: ${check.name}`,
|
||||
`Check status: ${indicator}${details}`,
|
||||
].join("\n"),
|
||||
};
|
||||
}
|
||||
|
||||
export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId, presentation = "modal" }: GitHubImportModalProps) {
|
||||
const { isEmbedded, scrollLockEnabled, resizePersistEnabled, escapeEnabled } = useEmbeddedPresentation(presentation);
|
||||
useMobileScrollLock(isOpen && scrollLockEnabled);
|
||||
@@ -438,6 +467,10 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
const [closingIssue, setClosingIssue] = useState(false);
|
||||
const [closeToast, setCloseToast] = useState<{ type: "success" | "error"; message: string } | null>(null);
|
||||
const closeToastTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
// FNXC:GitHubImport 2026-07-16-17:00: Track check-task submission by name + row index so duplicate GitHub check names never disable each other's controls.
|
||||
const [creatingCheckFixTaskRows, setCreatingCheckFixTaskRows] = useState<Set<string>>(new Set());
|
||||
const [checkFixTaskToast, setCheckFixTaskToast] = useState<{ type: "success" | "error"; message: string } | null>(null);
|
||||
const checkFixTaskToastTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isIssuesEmptyState, setIsIssuesEmptyState] = useState(false);
|
||||
@@ -1120,6 +1153,7 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
// FNXC:GitHubImport 2026-06-23-03:15: Clear the transient close toast timer on unmount.
|
||||
useEffect(() => () => {
|
||||
if (closeToastTimerRef.current) clearTimeout(closeToastTimerRef.current);
|
||||
if (checkFixTaskToastTimerRef.current) clearTimeout(checkFixTaskToastTimerRef.current);
|
||||
}, []);
|
||||
|
||||
/*
|
||||
@@ -1150,6 +1184,36 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
|
||||
const selectedIssue = issues.find((i) => i.number === selectedIssueNumber);
|
||||
const selectedPull = pulls.find((p) => p.number === selectedPullNumber);
|
||||
|
||||
const handleCreateCheckFixTask = useCallback(async (
|
||||
check: GitHubPullDetail["checks"][number],
|
||||
rowKey: string,
|
||||
) => {
|
||||
if (!selectedPull || !owner.trim() || !repo.trim()) return;
|
||||
|
||||
setCreatingCheckFixTaskRows((current) => new Set(current).add(rowKey));
|
||||
if (checkFixTaskToastTimerRef.current) clearTimeout(checkFixTaskToastTimerRef.current);
|
||||
setCheckFixTaskToast(null);
|
||||
|
||||
try {
|
||||
const task = await createTask(buildCheckFixTaskPrompt(selectedPull, check, `${owner.trim()}/${repo.trim()}`), projectId);
|
||||
onImport(task);
|
||||
setCheckFixTaskToast({ type: "success", message: t("git.checkFixTaskCreated", "Fix task created") });
|
||||
} catch (err: unknown) {
|
||||
setCheckFixTaskToast({
|
||||
type: "error",
|
||||
message: getErrorMessage(err) || t("git.failedToCreateCheckFixTask", "Failed to create fix task"),
|
||||
});
|
||||
} finally {
|
||||
setCreatingCheckFixTaskRows((current) => {
|
||||
const next = new Set(current);
|
||||
next.delete(rowKey);
|
||||
return next;
|
||||
});
|
||||
checkFixTaskToastTimerRef.current = setTimeout(() => setCheckFixTaskToast(null), 4000);
|
||||
}
|
||||
}, [onImport, owner, projectId, repo, selectedPull, t]);
|
||||
|
||||
/*
|
||||
FNXC:GitHubImport 2026-06-23-03:15:
|
||||
An issue counts as closed if the upstream state is closed OR we closed it locally this session. Only OPEN issues show the Close button.
|
||||
@@ -1882,14 +1946,30 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
: indicator === "neutral" || indicator === "skipped"
|
||||
? "neutral"
|
||||
: "pending";
|
||||
// FNXC:GitHubImport 2026-07-16-18:00: GitHub can return duplicate check names, and a user can switch PRs before a create finishes; include the PR number so only the originating row is disabled.
|
||||
const rowKey = `${selectedPull.number}:${check.name}-${idx}`;
|
||||
const creatingFixTask = creatingCheckFixTaskRows.has(rowKey);
|
||||
return (
|
||||
<li key={`${check.name}-${idx}`} className="github-import-pr-check-row">
|
||||
<li key={rowKey} className="github-import-pr-check-row">
|
||||
<span className={`github-import-pr-check-pill github-import-pr-check-pill--${variant}`}>{indicator || "pending"}</span>
|
||||
{check.detailsUrl ? (
|
||||
<a className="github-import-pr-check-name" href={check.detailsUrl} target="_blank" rel="noopener noreferrer">{check.name}</a>
|
||||
) : (
|
||||
<span className="github-import-pr-check-name">{check.name}</span>
|
||||
)}
|
||||
{variant === "failure" && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-sm github-import-pr-check-fix-task"
|
||||
data-testid="github-import-pr-check-fix-task"
|
||||
aria-label={t("git.createCheckFixTaskAria", "Create fix task for {{checkName}}", { checkName: check.name })}
|
||||
disabled={creatingFixTask}
|
||||
onClick={() => handleCreateCheckFixTask(check, rowKey)}
|
||||
>
|
||||
{creatingFixTask && <Loader2 size={14} className="spin" aria-hidden="true" />}
|
||||
{t("git.createCheckFixTask", "Create fix task")}
|
||||
</button>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
@@ -1897,6 +1977,15 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
) : (
|
||||
<div className="preview-detail-empty" data-testid="github-import-pr-checks-empty">{t("git.noChecks", "No checks")}</div>
|
||||
)}
|
||||
{checkFixTaskToast && (
|
||||
<div
|
||||
className={`github-import-close-toast github-import-close-toast--${checkFixTaskToast.type}`}
|
||||
role="status"
|
||||
data-testid="github-import-pr-check-fix-task-toast"
|
||||
>
|
||||
{checkFixTaskToast.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<CommentsThread
|
||||
comments={pullDetail?.comments ?? []}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
apiImportGitLabMergeRequest,
|
||||
fetchSettings,
|
||||
fetchGitRemotes,
|
||||
createTask,
|
||||
translateImportContent,
|
||||
} from "../../api";
|
||||
import type { Task } from "@fusion/core";
|
||||
@@ -44,6 +45,7 @@ vi.mock("../../api", async (importOriginal) => {
|
||||
apiImportGitLabMergeRequest: vi.fn(),
|
||||
fetchSettings: vi.fn(),
|
||||
fetchGitRemotes: vi.fn(),
|
||||
createTask: vi.fn(),
|
||||
translateImportContent: vi.fn(),
|
||||
};
|
||||
});
|
||||
@@ -175,6 +177,8 @@ describe("GitHubImportModal", () => {
|
||||
vi.mocked(apiImportGitLabGroupIssue).mockReset();
|
||||
vi.mocked(apiImportGitLabMergeRequest).mockReset();
|
||||
vi.mocked(fetchSettings).mockReset();
|
||||
vi.mocked(createTask).mockReset();
|
||||
vi.mocked(createTask).mockResolvedValue(mockTask);
|
||||
vi.mocked(fetchSettings).mockResolvedValue({ gitlabEnabled: true } as never);
|
||||
// Set default mock for apiFetchGitHubIssues to return empty array (prevents undefined issues state)
|
||||
vi.mocked(apiFetchGitHubIssues).mockResolvedValue([]);
|
||||
@@ -192,6 +196,150 @@ describe("GitHubImportModal", () => {
|
||||
onImport.mockReset();
|
||||
});
|
||||
|
||||
/*
|
||||
* FNXC:GitHubImport 2026-07-16-17:00:
|
||||
* FN-8110's per-check repair action is exercised through the same selected-PR detail path in both modal and embedded presentations.
|
||||
* The helper deliberately controls only the API seam so the tests cover the real row rendering and task callback behavior.
|
||||
*/
|
||||
const renderSelectedPullChecks = async (
|
||||
checks: Array<{ name: string; status: string; conclusion?: string; detailsUrl?: string }>,
|
||||
presentation: "modal" | "embedded" = "modal",
|
||||
detailRequest: Promise<{ comments: []; checks: Array<{ name: string; status: string; conclusion?: string; detailsUrl?: string }> }> = Promise.resolve({ comments: [], checks }),
|
||||
) => {
|
||||
const pull = {
|
||||
number: 81,
|
||||
title: "Fixable PR",
|
||||
body: "PR body",
|
||||
html_url: "https://github.com/owner/repo/pull/81",
|
||||
headBranch: "broken-build",
|
||||
baseBranch: "main",
|
||||
};
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce([{ name: "origin", owner: "owner", repo: "repo", url: "" }]);
|
||||
vi.mocked(apiFetchGitHubPulls).mockResolvedValueOnce([pull]);
|
||||
vi.mocked(apiFetchGitHubPullDetail).mockReturnValueOnce(detailRequest as never);
|
||||
|
||||
render(<GitHubImportModal isOpen onClose={onClose} onImport={onImport} tasks={[]} projectId="project-1" presentation={presentation} />);
|
||||
fireEvent.click(await screen.findByRole("tab", { name: /Pull Requests/i }));
|
||||
await screen.findByText("Fixable PR");
|
||||
fireEvent.click(screen.getByRole("button", { name: /Select pull request #81/i }));
|
||||
};
|
||||
|
||||
describe("failed PR check fix tasks", () => {
|
||||
const checkVariants = [
|
||||
{ name: "failure", status: "completed", conclusion: "failure", detailsUrl: "https://github.com/owner/repo/runs/1" },
|
||||
{ name: "success", status: "completed", conclusion: "success" },
|
||||
{ name: "pending", status: "in_progress" },
|
||||
{ name: "neutral", status: "completed", conclusion: "skipped" },
|
||||
];
|
||||
|
||||
it.each(["modal", "embedded"] as const)("creates a task from the failed row in %s presentation", async (presentation) => {
|
||||
const createdTask = { ...mockTask, id: `FN-${presentation}` };
|
||||
vi.mocked(createTask).mockResolvedValueOnce(createdTask);
|
||||
await renderSelectedPullChecks(checkVariants, presentation);
|
||||
|
||||
const buttons = await screen.findAllByTestId("github-import-pr-check-fix-task");
|
||||
expect(buttons).toHaveLength(1);
|
||||
expect(buttons[0]).toHaveAccessibleName("Create fix task for failure");
|
||||
fireEvent.click(buttons[0]);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createTask).toHaveBeenCalledWith(expect.objectContaining({
|
||||
title: 'Fix failing check "failure" on PR #81',
|
||||
description: expect.stringContaining("Repository: owner/repo"),
|
||||
}), "project-1");
|
||||
});
|
||||
const [{ description }] = vi.mocked(createTask).mock.calls[0];
|
||||
expect(description).toContain("https://github.com/owner/repo/pull/81");
|
||||
expect(description).toContain("broken-build → main");
|
||||
expect(description).toContain("Failing check: failure");
|
||||
expect(description).toContain("https://github.com/owner/repo/runs/1");
|
||||
expect(await screen.findByTestId("github-import-pr-check-fix-task-toast")).toHaveTextContent("Fix task created");
|
||||
expect(onImport).toHaveBeenCalledWith(createdTask);
|
||||
});
|
||||
|
||||
it("renders the affordance only for failed variants and keeps mobile wrapping source coverage", async () => {
|
||||
await renderSelectedPullChecks(checkVariants);
|
||||
expect(await screen.findAllByTestId("github-import-pr-check-fix-task")).toHaveLength(1);
|
||||
expect(screen.getByRole("button", { name: "Create fix task for failure" })).toBeTruthy();
|
||||
expect(screen.queryByRole("button", { name: "Create fix task for success" })).toBeNull();
|
||||
expect(screen.queryByRole("button", { name: "Create fix task for pending" })).toBeNull();
|
||||
expect(screen.queryByRole("button", { name: "Create fix task for neutral" })).toBeNull();
|
||||
|
||||
const source = readFileSync(resolve(__dirname, "../GitHubImportModal.css"), "utf8");
|
||||
expect(source).toMatch(/@media \(max-width: 768px\)[\s\S]*\.github-import-pr-check-row\s*\{[\s\S]*flex-wrap: wrap;/);
|
||||
expect(source).toMatch(/@media \(max-width: 768px\)[\s\S]*\.github-import-pr-check-fix-task\s*\{[\s\S]*width: 100%;/);
|
||||
});
|
||||
|
||||
it("keeps duplicate failed check rows independently actionable while creation is in flight", async () => {
|
||||
let resolveFirstCreate!: (task: Task) => void;
|
||||
vi.mocked(createTask)
|
||||
.mockImplementationOnce(() => new Promise<Task>((resolve) => { resolveFirstCreate = resolve; }))
|
||||
.mockResolvedValueOnce({ ...mockTask, id: "FN-second" });
|
||||
await renderSelectedPullChecks([
|
||||
{ name: "duplicate", status: "completed", conclusion: "failure" },
|
||||
{ name: "duplicate", status: "completed", conclusion: "failure" },
|
||||
]);
|
||||
|
||||
const [first, second] = await screen.findAllByTestId("github-import-pr-check-fix-task");
|
||||
fireEvent.click(first);
|
||||
await waitFor(() => expect(first).toBeDisabled());
|
||||
expect(first.querySelector(".spin")).toBeTruthy();
|
||||
expect(second).not.toBeDisabled();
|
||||
fireEvent.click(second);
|
||||
expect(createTask).toHaveBeenCalledTimes(2);
|
||||
|
||||
await act(async () => { resolveFirstCreate(mockTask); });
|
||||
});
|
||||
|
||||
it("surfaces create errors inline and permits retry", async () => {
|
||||
vi.mocked(createTask)
|
||||
.mockRejectedValueOnce(new Error("Task service unavailable"))
|
||||
.mockResolvedValueOnce({ ...mockTask, id: "FN-retry" });
|
||||
await renderSelectedPullChecks([{ name: "lint", status: "completed", conclusion: "failure" }]);
|
||||
|
||||
const button = await screen.findByTestId("github-import-pr-check-fix-task");
|
||||
fireEvent.click(button);
|
||||
expect(await screen.findByTestId("github-import-pr-check-fix-task-toast")).toHaveTextContent("Task service unavailable");
|
||||
expect(button).not.toBeDisabled();
|
||||
expect(onImport).not.toHaveBeenCalled();
|
||||
|
||||
fireEvent.click(button);
|
||||
await waitFor(() => expect(onImport).toHaveBeenCalledWith(expect.objectContaining({ id: "FN-retry" })));
|
||||
expect(createTask).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("emits no fix action while PR detail is loading, errored, empty, or has no failures", async () => {
|
||||
let resolveDetail!: (detail: { comments: []; checks: [] }) => void;
|
||||
const loadingDetail = new Promise<{ comments: []; checks: [] }>((resolve) => { resolveDetail = resolve; });
|
||||
await renderSelectedPullChecks([], "modal", loadingDetail);
|
||||
expect(await screen.findByTestId("github-import-pr-checks-loading")).toBeTruthy();
|
||||
expect(screen.queryByTestId("github-import-pr-check-fix-task")).toBeNull();
|
||||
await act(async () => { resolveDetail({ comments: [], checks: [] }); });
|
||||
expect(await screen.findByTestId("github-import-pr-checks-empty")).toBeTruthy();
|
||||
expect(screen.queryByTestId("github-import-pr-check-fix-task")).toBeNull();
|
||||
});
|
||||
|
||||
it("emits no fix action for PR detail errors or populated non-failing checks", async () => {
|
||||
vi.mocked(apiFetchGitHubPullDetail).mockRejectedValueOnce(new Error("Checks unavailable"));
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce([{ name: "origin", owner: "owner", repo: "repo", url: "" }]);
|
||||
vi.mocked(apiFetchGitHubPulls).mockResolvedValueOnce([{
|
||||
number: 82, title: "No failures", body: "", html_url: "https://github.com/owner/repo/pull/82", headBranch: "feature", baseBranch: "main",
|
||||
}]);
|
||||
render(<GitHubImportModal isOpen onClose={onClose} onImport={onImport} tasks={[]} />);
|
||||
fireEvent.click(await screen.findByRole("tab", { name: /Pull Requests/i }));
|
||||
await screen.findByText("No failures");
|
||||
fireEvent.click(screen.getByRole("button", { name: /Select pull request #82/i }));
|
||||
expect(await screen.findByTestId("github-import-pr-checks-error")).toHaveTextContent("Checks unavailable");
|
||||
expect(screen.queryByTestId("github-import-pr-check-fix-task")).toBeNull();
|
||||
});
|
||||
|
||||
it("emits no fix action for populated successful, pending, and neutral checks", async () => {
|
||||
await renderSelectedPullChecks(checkVariants.slice(1));
|
||||
expect(await screen.findByTestId("github-import-pr-checks")).toBeTruthy();
|
||||
expect(screen.queryByTestId("github-import-pr-check-fix-task")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it("renders when isOpen is true", async () => {
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce([]);
|
||||
render(<GitHubImportModal isOpen={true} onClose={onClose} onImport={onImport} tasks={[]} />);
|
||||
|
||||
Reference in New Issue
Block a user