import { afterEach, describe, expect, it, rs } from "@rstest/core";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { cleanup, render, screen, waitFor } from "@testing-library/react";
import { PromptInputProvider } from "@/components/ai-elements/prompt-input";
import { InputBox } from "@/components/workspace/input-box";
import { ThreadContext } from "@/components/workspace/messages/context";
import { AuthProvider } from "@/core/auth/AuthProvider";
import { DEFAULT_LOCALE } from "@/core/i18n";
import { I18nProvider } from "@/core/i18n/context";
import { buildComposerDraftKey } from "@/core/threads/composer-draft";
const skillState = rs.hoisted(() => ({
skills: [
{
name: "research",
description: "Research a topic",
category: "general",
license: "MIT",
enabled: true,
editable: false,
},
],
}));
rs.mock("next/navigation", () => ({
useRouter: () => ({ push: rs.fn(), replace: rs.fn(), refresh: rs.fn() }),
usePathname: () => "/workspace",
useSearchParams: () => new URLSearchParams(),
}));
rs.mock("@/core/models/hooks", () => ({
useModels: () => ({
models: [],
tokenUsageEnabled: false,
isLoading: false,
isFetching: false,
error: null,
refetch: rs.fn(),
}),
}));
rs.mock("@/core/skills/hooks", () => ({
useSkills: () => ({
skills: skillState.skills,
isLoading: false,
error: null,
}),
}));
const draftKey = buildComposerDraftKey({
userId: "user-1",
agentName: "researcher",
threadId: "thread-1",
});
function renderComposer({
agentSkillsLoading,
agentSkillNames,
}: {
agentSkillsLoading: boolean;
agentSkillNames?: string[] | null;
}) {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
return render(
,
);
}
afterEach(() => {
rs.restoreAllMocks();
window.sessionStorage.clear();
cleanup();
});
describe("InputBox agent skill draft hydration", () => {
it("waits for the agent scope before restoring a saved skill chip", async () => {
window.sessionStorage.setItem(
draftKey,
JSON.stringify({ version: 1, text: "topic", skillName: "research" }),
);
const view = renderComposer({
agentSkillsLoading: true,
agentSkillNames: undefined,
});
expect(
screen.queryByRole("button", { name: "Remove /research" }),
).toBeNull();
expect(window.sessionStorage.getItem(draftKey)).toContain(
'"skillName":"research"',
);
view.rerender(
,
);
await waitFor(() => {
expect(
screen.getByRole("button", { name: "Remove /research" }),
).toBeTruthy();
});
expect(screen.getByRole("textbox").textContent).toBe("topic");
});
it("preserves inherit semantics when an agent fetch fails", async () => {
window.sessionStorage.setItem(
draftKey,
JSON.stringify({ version: 1, text: "topic", skillName: "research" }),
);
renderComposer({
agentSkillsLoading: false,
agentSkillNames: undefined,
});
await waitFor(() => {
expect(
screen.getByRole("button", { name: "Remove /research" }),
).toBeTruthy();
});
});
});