import React, { useState, useEffect } from "react";
import { screen, fireEvent, waitFor } from "@testing-library/react";
import { z } from "zod";
import { useFrontendTool } from "../use-frontend-tool";
import { useCopilotKit } from "../../providers/CopilotKitProvider";
import { ReactFrontendTool } from "../../types";
import { CopilotKitCoreReact } from "../../lib/react-core";
import { renderWithCopilotKit } from "../../__tests__/utils/test-helpers";
/**
* Component that captures the copilotkit core ref for test assertions.
*/
const CoreCapture: React.FC<{
onCore: (core: CopilotKitCoreReact) => void;
}> = ({ onCore }) => {
const { copilotkit } = useCopilotKit();
useEffect(() => {
onCore(copilotkit);
}, [copilotkit, onCore]);
return null;
};
describe("useFrontendTool available flag", () => {
it("registers tool with available: false on the core", async () => {
let coreRef: CopilotKitCoreReact | null = null;
const ToolComponent: React.FC = () => {
const tool: ReactFrontendTool<{ msg: string }> = {
name: "disabledTool",
description: "A disabled tool",
available: false,
parameters: z.object({ msg: z.string() }),
handler: async () => ({ result: "ok" }),
};
useFrontendTool(tool);
return null;
};
const ui = renderWithCopilotKit({
children: (
<>
{
coreRef = c;
}}
/>
>
),
});
await waitFor(() => {
expect(coreRef).not.toBeNull();
const tool = coreRef!.tools.find((t) => t.name === "disabledTool");
expect(tool).toBeDefined();
expect(tool!.available).toBe(false);
});
ui.unmount();
});
it("registers tool with available: true on the core", async () => {
let coreRef: CopilotKitCoreReact | null = null;
const ToolComponent: React.FC = () => {
const tool: ReactFrontendTool<{ msg: string }> = {
name: "enabledTool",
description: "An enabled tool",
available: true,
parameters: z.object({ msg: z.string() }),
handler: async () => ({ result: "ok" }),
};
useFrontendTool(tool);
return null;
};
const ui = renderWithCopilotKit({
children: (
<>
{
coreRef = c;
}}
/>
>
),
});
await waitFor(() => {
expect(coreRef).not.toBeNull();
const tool = coreRef!.tools.find((t) => t.name === "enabledTool");
expect(tool).toBeDefined();
expect(tool!.available).toBe(true);
});
ui.unmount();
});
it("re-registers tool when available toggles between true and false", async () => {
let coreRef: CopilotKitCoreReact | null = null;
const ToolWithToggle: React.FC = () => {
const [isEnabled, setIsEnabled] = useState(true);
const tool: ReactFrontendTool<{ data: string }> = {
name: "toggleTool",
description: "A toggleable tool",
available: isEnabled,
parameters: z.object({ data: z.string() }),
handler: async () => ({ ok: true }),
};
useFrontendTool(tool, [isEnabled]);
return (
);
};
const ui = renderWithCopilotKit({
children: (
<>
{
coreRef = c;
}}
/>
>
),
});
// Tool should be registered as enabled initially
await waitFor(() => {
expect(coreRef).not.toBeNull();
const tool = coreRef!.tools.find((t) => t.name === "toggleTool");
expect(tool).toBeDefined();
expect(tool!.available).toBe(true);
});
// Toggle to disabled
fireEvent.click(screen.getByTestId("toggle-btn"));
// Tool should be re-registered as disabled
await waitFor(() => {
const tool = coreRef!.tools.find((t) => t.name === "toggleTool");
expect(tool).toBeDefined();
expect(tool!.available).toBe(false);
});
// Toggle back to enabled
fireEvent.click(screen.getByTestId("toggle-btn"));
await waitFor(() => {
const tool = coreRef!.tools.find((t) => t.name === "toggleTool");
expect(tool).toBeDefined();
expect(tool!.available).toBe(true);
});
ui.unmount();
});
});