test(FN-4306): complete Step 4 — add capacity banner gating coverage

Fusion-Task-Id: FN-4306
Fusion-Task-Lineage: b0ab9b60-de02-40a7-ad8b-0ca18c98e16c
This commit is contained in:
Fusion
2026-05-13 06:36:11 -07:00
committed by gsxdsm
parent 29f01ec44d
commit cd9f5bbf04
3 changed files with 76 additions and 1 deletions

View File

@@ -23,10 +23,19 @@ const defaultSettings: Settings = {
worktreeInitCommand: "",
testCommand: "",
buildCommand: "",
capacityRiskBannerEnabled: false,
capacityRiskTodoThreshold: 20,
experimentalFeatures: { insights: true, skillsView: true, agentsView: true, memoryView: true, evalsView: true },
};
const mockAgentStats = {
activeCount: 0,
busyCount: 0,
pausedCount: 0,
todoTaskCount: 0,
idleNonEphemeralCount: 1,
};
const mockSubscribeSse = vi.fn((..._args: any[]) => vi.fn());
vi.mock("../../sse-bus", () => ({
@@ -55,6 +64,7 @@ vi.mock("../../api", async (importOriginal) => {
fetchModels: vi.fn(() => Promise.resolve({ models: [], favoriteProviders: [], favoriteModels: [] })),
fetchGitRemotes: vi.fn(() => Promise.resolve([])),
fetchAgents: vi.fn(() => Promise.resolve([])),
fetchAgentStats: vi.fn(() => Promise.resolve({ ...mockAgentStats })),
fetchTaskDetail: vi.fn((id: string) => Promise.resolve({ id, title: `Task ${id}` })),
fetchUnreadCount: vi.fn(() => Promise.resolve({ unreadCount: 0 })),
fetchDashboardHealth: vi.fn(() => Promise.resolve({
@@ -660,6 +670,8 @@ beforeEach(() => {
});
mockUseViewportMode.mockReset();
mockUseViewportMode.mockReturnValue("desktop");
mockAgentStats.todoTaskCount = 0;
mockAgentStats.idleNonEphemeralCount = 1;
});
describe("FN-4250 FileBrowserProvider coverage", () => {
@@ -685,6 +697,45 @@ describe("FN-4250 FileBrowserProvider coverage", () => {
});
});
describe("Capacity risk banner gating", () => {
it("does not render banner when capacityRiskBannerEnabled is false", async () => {
mockAgentStats.todoTaskCount = 21;
mockAgentStats.idleNonEphemeralCount = 0;
vi.mocked(fetchSettings).mockResolvedValueOnce({
...defaultSettings,
capacityRiskBannerEnabled: false,
capacityRiskTodoThreshold: 20,
});
render(<App />);
await waitForAppShell();
expect(screen.queryByText(/Capacity risk:/i)).not.toBeInTheDocument();
});
it("renders and dismisses banner when enabled", async () => {
mockAgentStats.todoTaskCount = 21;
mockAgentStats.idleNonEphemeralCount = 0;
vi.mocked(fetchSettings).mockResolvedValueOnce({
...defaultSettings,
capacityRiskBannerEnabled: true,
capacityRiskTodoThreshold: 20,
});
render(<App />);
await waitFor(() => {
expect(screen.getByText(/Capacity risk:/i)).toBeInTheDocument();
});
fireEvent.click(screen.getByRole("button", { name: /dismiss capacity warning/i }));
expect(localStorage.getItem(scopedKey("kb-capacity-risk-banner-dismissed", DEFAULT_PROJECT_ID))).toBe("true");
await waitFor(() => {
expect(screen.queryByText(/Capacity risk:/i)).not.toBeInTheDocument();
});
});
});
describe("App backend-unreachable first-run flow", () => {
it("renders backend connection error page instead of setup wizard when projects fetch fails during first-run", async () => {
mockProjectsState.projects = [];

View File

@@ -30,6 +30,7 @@ describe("useAppSettings", () => {
taskStuckTimeoutMs: 600000,
staleHighFanoutBlockerAgeThresholdMs: 7200000,
showQuickChatFAB: false,
capacityRiskBannerEnabled: false,
} as never);
mockUpdateSettings.mockResolvedValue({} as never);
@@ -49,6 +50,8 @@ describe("useAppSettings", () => {
expect(result.current.taskStuckTimeoutMs).toBe(600000);
expect(result.current.staleHighFanoutBlockerAgeThresholdMs).toBe(7200000);
expect(result.current.showQuickChatFAB).toBe(false);
expect(result.current.capacityRiskBannerEnabled).toBe(false);
expect(result.current.capacityRiskTodoThreshold).toBe(20);
});
expect(mockFetchConfig).toHaveBeenCalledWith("proj_123");
@@ -117,6 +120,26 @@ describe("useAppSettings", () => {
);
});
it("propagates capacity risk settings from fetchSettings", async () => {
mockFetchSettings.mockResolvedValueOnce({
autoMerge: false,
globalPause: true,
enginePaused: false,
prAuthAvailable: true,
taskStuckTimeoutMs: 600000,
showQuickChatFAB: false,
capacityRiskBannerEnabled: true,
capacityRiskTodoThreshold: 30,
} as never);
const { result } = renderHook(() => useAppSettings("proj_123"));
await waitFor(() => {
expect(result.current.capacityRiskBannerEnabled).toBe(true);
expect(result.current.capacityRiskTodoThreshold).toBe(30);
});
});
it("refresh() re-fetches and updates state", async () => {
const { result } = renderHook(() => useAppSettings("proj_123"));

View File

@@ -31,6 +31,7 @@ export const PROJECT_STORAGE_KEYS: string[] = [
"kb-chat-active-session",
"kb-dashboard-working-branch-filter",
"kb-dashboard-base-branch-filter",
"kb-capacity-risk-banner-dismissed",
"kb-files-line-numbers",
"fusion-plugin-dependency-graph:positions",
];