feat(FN-1941): surface setup readiness warnings in task entry flows
- Add useSetupReadiness hook to evaluate setup state and expose actionable warning metadata - Add reusable SetupWarningBanner component with compact and full warning presentation modes - Render setup warnings in NewTaskModal and QuickEntryBox so task creation surfaces missing configuration early - Add targeted tests for the hook and banner plus integration coverage updates for modal and quick-entry behavior
This commit is contained in:
@@ -29,6 +29,7 @@ vi.mock("../../api", () => ({
|
||||
}),
|
||||
fetchWorkflowSteps: vi.fn().mockResolvedValue([]),
|
||||
fetchAgents: vi.fn().mockResolvedValue([]),
|
||||
fetchAuthStatus: vi.fn().mockResolvedValue({ providers: [] }),
|
||||
refineText: vi.fn(),
|
||||
getRefineErrorMessage: vi.fn((err) => err?.message || "Failed to refine text. Please try again."),
|
||||
updateGlobalSettings: vi.fn().mockResolvedValue({}),
|
||||
@@ -139,6 +140,32 @@ describe("NewTaskModal", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("still submits when setup warnings are shown", async () => {
|
||||
const { fetchAuthStatus } = await import("../../api");
|
||||
vi.mocked(fetchAuthStatus).mockResolvedValueOnce({
|
||||
providers: [{ id: "github", name: "GitHub", authenticated: false, type: "oauth" }],
|
||||
});
|
||||
|
||||
const { props } = renderNewTaskModal();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("No AI provider connected")).toBeTruthy();
|
||||
expect(screen.getByText("GitHub not connected")).toBeTruthy();
|
||||
});
|
||||
|
||||
const descTextarea = screen.getByRole("textbox");
|
||||
fireEvent.change(descTextarea, { target: { value: "Submit despite warning" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(props.onCreateTask).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
description: "Submit despite warning",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("closes modal after successful creation", async () => {
|
||||
const { props } = renderNewTaskModal();
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ vi.mock("../../api", () => ({
|
||||
groupOverlappingFiles: true,
|
||||
autoMerge: true,
|
||||
}),
|
||||
fetchAuthStatus: vi.fn().mockResolvedValue({ providers: [] }),
|
||||
refineText: vi.fn(),
|
||||
getRefineErrorMessage: vi.fn((err) => err?.message || "Failed to refine text. Please try again."),
|
||||
fetchAgents: vi.fn().mockResolvedValue([]),
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { SetupWarningBanner } from "../SetupWarningBanner";
|
||||
|
||||
describe("SetupWarningBanner", () => {
|
||||
it("returns null when both hasAiProvider and hasGithub are true", () => {
|
||||
const { container } = render(
|
||||
<SetupWarningBanner hasAiProvider hasGithub />,
|
||||
);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it("shows AI provider warning when hasAiProvider is false and hasGithub is true", () => {
|
||||
render(<SetupWarningBanner hasAiProvider={false} hasGithub />);
|
||||
|
||||
expect(screen.getByText("No AI provider connected")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
"AI agents won't be able to work on tasks until you connect a provider. Set one up in Settings → AI Setup.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText("GitHub not connected")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows GitHub warning when hasGithub is false and hasAiProvider is true", () => {
|
||||
render(<SetupWarningBanner hasAiProvider hasGithub={false} />);
|
||||
|
||||
expect(screen.getByText("GitHub not connected")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
"You won't be able to import issues from GitHub, but you can still create tasks manually.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText("No AI provider connected")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows both warnings when both providers are missing", () => {
|
||||
render(<SetupWarningBanner hasAiProvider={false} hasGithub={false} />);
|
||||
|
||||
expect(screen.getByText("No AI provider connected")).toBeInTheDocument();
|
||||
expect(screen.getByText("GitHub not connected")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("compact mode renders a single-line summary", () => {
|
||||
render(
|
||||
<SetupWarningBanner hasAiProvider={false} hasGithub compact />,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText("⚠ Setup incomplete — AI and/or GitHub features will be limited."),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText("No AI provider connected")).toBeNull();
|
||||
});
|
||||
|
||||
it("full mode renders setup-warning-banner class with expected structure", () => {
|
||||
const { container } = render(
|
||||
<SetupWarningBanner hasAiProvider={false} hasGithub={false} />,
|
||||
);
|
||||
|
||||
const banner = container.querySelector(".setup-warning-banner");
|
||||
const items = container.querySelectorAll(".setup-warning-banner__item");
|
||||
|
||||
expect(banner).toBeTruthy();
|
||||
expect(items).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("has role=status and aria-live=polite for accessibility", () => {
|
||||
render(<SetupWarningBanner hasAiProvider={false} hasGithub />);
|
||||
|
||||
const banner = screen.getByRole("status");
|
||||
expect(banner).toHaveAttribute("aria-live", "polite");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user