feat: improve session polling logic in BoardOnboardingChat component

This commit is contained in:
Abhimanyu Saharan
2026-02-16 01:46:12 +05:30
parent 6c3c9913db
commit 522761bc26
2 changed files with 130 additions and 3 deletions

View File

@@ -0,0 +1,122 @@
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
import type { ReactNode } from "react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { BoardOnboardingRead } from "@/api/generated/model";
import { BoardOnboardingChat } from "./BoardOnboardingChat";
const startOnboardingMock = vi.fn();
const getOnboardingMock = vi.fn();
const answerOnboardingMock = vi.fn();
const confirmOnboardingMock = vi.fn();
vi.mock("@/hooks/usePageActive", () => ({
usePageActive: () => true,
}));
vi.mock("@/components/ui/dialog", () => ({
DialogHeader: ({ children }: { children?: ReactNode }) => (
<div>{children}</div>
),
DialogFooter: ({ children }: { children?: ReactNode }) => (
<div>{children}</div>
),
DialogTitle: ({ children }: { children?: ReactNode }) => (
<h2>{children}</h2>
),
}));
vi.mock("@/api/generated/board-onboarding/board-onboarding", () => ({
startOnboardingApiV1BoardsBoardIdOnboardingStartPost: (...args: unknown[]) =>
startOnboardingMock(...args),
getOnboardingApiV1BoardsBoardIdOnboardingGet: (...args: unknown[]) =>
getOnboardingMock(...args),
answerOnboardingApiV1BoardsBoardIdOnboardingAnswerPost: (...args: unknown[]) =>
answerOnboardingMock(...args),
confirmOnboardingApiV1BoardsBoardIdOnboardingConfirmPost: (...args: unknown[]) =>
confirmOnboardingMock(...args),
}));
const buildQuestionSession = (question: string): BoardOnboardingRead => ({
id: "session-1",
board_id: "board-1",
session_key: "session:key",
status: "active",
messages: [
{
role: "assistant",
content: JSON.stringify({
question,
options: ["Option A", "Option B"],
}),
timestamp: "2026-02-15T00:00:00Z",
},
],
draft_goal: null,
created_at: "2026-02-15T00:00:00Z",
updated_at: "2026-02-15T00:00:00Z",
});
describe("BoardOnboardingChat polling", () => {
beforeEach(() => {
vi.useFakeTimers({ toFake: ["setInterval", "clearInterval"] });
startOnboardingMock.mockReset();
getOnboardingMock.mockReset();
answerOnboardingMock.mockReset();
confirmOnboardingMock.mockReset();
});
afterEach(() => {
vi.useRealTimers();
});
it("does not keep polling while waiting for user answer on a shown question", async () => {
const session = buildQuestionSession("What should we prioritize?");
startOnboardingMock.mockResolvedValue({ status: 200, data: session });
getOnboardingMock.mockResolvedValue({ status: 200, data: session });
render(
<BoardOnboardingChat boardId="board-1" onConfirmed={() => undefined} />,
);
await screen.findByText("What should we prioritize?");
const callsBeforeWait = getOnboardingMock.mock.calls.length;
await act(async () => {
vi.advanceTimersByTime(6500);
await Promise.resolve();
});
expect(getOnboardingMock.mock.calls.length).toBe(callsBeforeWait);
});
it("continues polling after an answer is submitted and waiting for assistant", async () => {
const session = buildQuestionSession("Pick a style");
startOnboardingMock.mockResolvedValue({ status: 200, data: session });
getOnboardingMock.mockResolvedValue({ status: 200, data: session });
answerOnboardingMock.mockResolvedValue({ status: 200, data: session });
render(
<BoardOnboardingChat boardId="board-1" onConfirmed={() => undefined} />,
);
await screen.findByText("Pick a style");
fireEvent.click(screen.getByRole("button", { name: "Option A" }));
fireEvent.click(screen.getByRole("button", { name: "Next" }));
await waitFor(() => {
expect(answerOnboardingMock).toHaveBeenCalledTimes(1);
});
const callsBeforePoll = getOnboardingMock.mock.calls.length;
await act(async () => {
vi.advanceTimersByTime(2500);
await Promise.resolve();
});
await waitFor(() => {
expect(getOnboardingMock.mock.calls.length).toBeGreaterThan(callsBeforePoll);
});
});
});

View File

@@ -247,12 +247,17 @@ export function BoardOnboardingChat({
void startSession();
}, [startSession]);
const shouldPollSession =
isPageActive && (loading || isAwaitingAgent || (!question && !draft));
useEffect(() => {
if (!isPageActive) return;
if (!shouldPollSession) return;
void refreshSession();
const interval = setInterval(refreshSession, 2000);
const interval = setInterval(() => {
void refreshSession();
}, 2000);
return () => clearInterval(interval);
}, [isPageActive, refreshSession]);
}, [refreshSession, shouldPollSession]);
const handleAnswer = useCallback(
async (value: string, freeText?: string) => {