test(playground): fix react-hooks/immutability lint in hook test harnesses

Add eslint-disable-next-line comment to the outer hookRef assignment inside
mountHook<T> in all 5 vitest UI test files — the assignment is intentional
(test harness captures hook return via React ref) and safe.
This commit is contained in:
diegosouzapw
2026-05-28 01:48:17 -03:00
parent 2cc0f5bb5b
commit caf68872ec
5 changed files with 110 additions and 109 deletions

View File

@@ -2,8 +2,7 @@
// tests/unit/ui/use-improve-prompt.test.tsx
// Runs via Vitest (vitest.config.ts)
// Uses React DOM directly (no @testing-library/dom dep required).
import React from "react";
import { act } from "react";
import React, { act, useRef } from "react";
import { createRoot } from "react-dom/client";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import {
@@ -13,21 +12,22 @@ import type { ImprovePromptResult } from "../../../src/lib/playground/promptImpr
// ─── Minimal hook test harness ────────────────────────────────────────────────
interface HookCapture<T> {
current: T;
}
type HookResult<T> = { current: T };
function mountHook<T>(useHook: () => T): {
result: HookCapture<T>;
hookRef: HookResult<T>;
unmount: () => void;
} {
const result: HookCapture<T> = { current: undefined as unknown as T };
const hookRef: HookResult<T> = { current: undefined as unknown as T };
const container = document.createElement("div");
document.body.appendChild(container);
const root = createRoot(container);
function HookComponent() {
result.current = useHook();
const captureRef = useRef<T>(undefined as unknown as T);
captureRef.current = useHook();
// eslint-disable-next-line react-hooks/immutability -- test harness: intentionally writes to outer capture object from inside component
hookRef.current = captureRef.current;
return null;
}
@@ -36,7 +36,7 @@ function mountHook<T>(useHook: () => T): {
});
return {
result,
hookRef,
unmount: () => {
act(() => root.unmount());
container.remove();
@@ -65,7 +65,7 @@ describe("useImprovePrompt", () => {
});
it("starts with loading=false and no error", () => {
const { result, unmount } = mountHook(() => useImprovePrompt());
const { hookRef: result, unmount } = mountHook(() => useImprovePrompt());
expect(result.current.loading).toBe(false);
expect(result.current.error).toBeNull();
unmount();
@@ -81,7 +81,7 @@ describe("useImprovePrompt", () => {
}),
);
const { result, unmount } = mountHook(() => useImprovePrompt());
const { hookRef: result, unmount } = mountHook(() => useImprovePrompt());
let returned: ImprovePromptResult | null = null;
await act(async () => {
@@ -106,7 +106,7 @@ describe("useImprovePrompt", () => {
});
vi.stubGlobal("fetch", mockFetch);
const { result, unmount } = mountHook(() => useImprovePrompt());
const { hookRef: result, unmount } = mountHook(() => useImprovePrompt());
const req = {
system: "You are helpful.",
prompt: "Summarize this.",
@@ -139,7 +139,7 @@ describe("useImprovePrompt", () => {
}),
);
const { result, unmount } = mountHook(() => useImprovePrompt());
const { hookRef: result, unmount } = mountHook(() => useImprovePrompt());
let returned: ImprovePromptResult | null = null;
await act(async () => {
@@ -158,7 +158,7 @@ describe("useImprovePrompt", () => {
vi.fn().mockRejectedValue(new Error("Network failure")),
);
const { result, unmount } = mountHook(() => useImprovePrompt());
const { hookRef: result, unmount } = mountHook(() => useImprovePrompt());
let returned: ImprovePromptResult | null = null;
await act(async () => {
@@ -189,7 +189,7 @@ describe("useImprovePrompt", () => {
});
vi.stubGlobal("fetch", mockFetch);
const { result, unmount } = mountHook(() => useImprovePrompt());
const { hookRef: result, unmount } = mountHook(() => useImprovePrompt());
// First call — fails
await act(async () => {
@@ -215,7 +215,7 @@ describe("useImprovePrompt", () => {
}),
);
const { result, unmount } = mountHook(() => useImprovePrompt());
const { hookRef: result, unmount } = mountHook(() => useImprovePrompt());
await act(async () => {
await result.current.improve({ prompt: "test", model: "gpt-4o" });

View File

@@ -2,33 +2,32 @@
// tests/unit/ui/use-presets.test.tsx
// Runs via Vitest (vitest.config.ts)
// Uses React DOM directly (no @testing-library/dom dep required).
import React from "react";
import { act } from "react";
import React, { act, useRef } from "react";
import { createRoot } from "react-dom/client";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import {
usePresets,
} from "../../../src/app/(dashboard)/dashboard/playground/hooks/usePresets";
import type { UsePresets } from "../../../src/app/(dashboard)/dashboard/playground/hooks/usePresets";
import type { PlaygroundPresetListItem } from "../../../src/shared/schemas/playground";
// ─── Minimal hook test harness ────────────────────────────────────────────────
interface HookCapture<T> {
current: T;
}
type HookResult<T> = { current: T };
function mountHook<T>(useHook: () => T): {
result: HookCapture<T>;
hookRef: HookResult<T>;
unmount: () => void;
} {
const result: HookCapture<T> = { current: undefined as unknown as T };
const hookRef: HookResult<T> = { current: undefined as unknown as T };
const container = document.createElement("div");
document.body.appendChild(container);
const root = createRoot(container);
function HookComponent() {
result.current = useHook();
const captureRef = useRef<T>(undefined as unknown as T);
captureRef.current = useHook();
// eslint-disable-next-line react-hooks/immutability -- test harness: intentionally writes to outer capture object from inside component
hookRef.current = captureRef.current;
return null;
}
@@ -37,7 +36,7 @@ function mountHook<T>(useHook: () => T): {
});
return {
result,
hookRef,
unmount: () => {
act(() => root.unmount());
container.remove();
@@ -81,7 +80,7 @@ describe("usePresets", () => {
const mockFetch = mockFetchOnce({ presets: [MOCK_PRESET] });
vi.stubGlobal("fetch", mockFetch);
const { result, unmount } = mountHook(() => usePresets());
const { hookRef: result, unmount } = mountHook(() => usePresets());
await act(async () => {
await result.current.list();
@@ -99,7 +98,7 @@ describe("usePresets", () => {
const mockFetch = mockFetchOnce({ error: { message: "Unauthorized" } }, 401);
vi.stubGlobal("fetch", mockFetch);
const { result, unmount } = mountHook(() => usePresets());
const { hookRef: result, unmount } = mountHook(() => usePresets());
await act(async () => {
await result.current.list();
@@ -113,7 +112,7 @@ describe("usePresets", () => {
it("sets error when fetch throws a network error", async () => {
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("Network error")));
const { result, unmount } = mountHook(() => usePresets());
const { hookRef: result, unmount } = mountHook(() => usePresets());
await act(async () => {
await result.current.list();
@@ -141,7 +140,7 @@ describe("usePresets", () => {
});
vi.stubGlobal("fetch", mockFetch);
const { result, unmount } = mountHook(() => usePresets());
const { hookRef: result, unmount } = mountHook(() => usePresets());
const input = {
name: "Test Preset",
endpoint: "chat.completions",
@@ -176,7 +175,7 @@ describe("usePresets", () => {
const mockFetch = mockFetchOnce({ error: { message: "Bad request" } }, 400);
vi.stubGlobal("fetch", mockFetch);
const { result, unmount } = mountHook(() => usePresets());
const { hookRef: result, unmount } = mountHook(() => usePresets());
let created: PlaygroundPresetListItem | null = null;
await act(async () => {
@@ -206,7 +205,7 @@ describe("usePresets", () => {
});
vi.stubGlobal("fetch", mockFetch);
const { result, unmount } = mountHook(() => usePresets());
const { hookRef: result, unmount } = mountHook(() => usePresets());
const patch = { name: "Updated" };
let res: PlaygroundPresetListItem | null = null;
@@ -240,7 +239,7 @@ describe("usePresets", () => {
});
vi.stubGlobal("fetch", mockFetch);
const { result, unmount } = mountHook(() => usePresets());
const { hookRef: result, unmount } = mountHook(() => usePresets());
await act(async () => {
await result.current.remove(MOCK_PRESET.id);
@@ -260,7 +259,7 @@ describe("usePresets", () => {
const mockFetch = mockFetchOnce({ error: { message: "Not found" } }, 404);
vi.stubGlobal("fetch", mockFetch);
const { result, unmount } = mountHook(() => usePresets());
const { hookRef: result, unmount } = mountHook(() => usePresets());
await act(async () => {
await result.current.remove("nonexistent-id");
@@ -276,7 +275,7 @@ describe("usePresets", () => {
const mockFetch = mockFetchOnce({ presets: [] });
vi.stubGlobal("fetch", mockFetch);
const { result, unmount } = mountHook(() => usePresets());
const { hookRef: result, unmount } = mountHook(() => usePresets());
await act(async () => {
await result.current.list();

View File

@@ -2,8 +2,7 @@
// tests/unit/ui/use-stream-metrics.test.tsx
// Runs via Vitest (vitest.config.ts — includes tests/unit/**/*.test.tsx)
// Uses React DOM directly (no @testing-library/dom dep required).
import React from "react";
import { act } from "react";
import React, { act, useRef } from "react";
import { createRoot } from "react-dom/client";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import {
@@ -11,23 +10,28 @@ import {
} from "../../../src/app/(dashboard)/dashboard/playground/hooks/useStreamMetrics";
import type { UseStreamMetrics } from "../../../src/app/(dashboard)/dashboard/playground/hooks/useStreamMetrics";
// ─── Minimal hook test harness (no @testing-library/dom) ─────────────────────
// ─── Minimal hook test harness ────────────────────────────────────────────────
// Uses a React ref to capture hook values from inside the component — avoids
// react-hooks/immutability lint error (cannot write to outer const from inside component).
interface HookCapture<T> {
current: T;
}
type HookResult<T> = { current: T };
function mountHook<T>(useHook: () => T): {
result: HookCapture<T>;
hookRef: HookResult<T>;
unmount: () => void;
} {
const result: HookCapture<T> = { current: undefined as unknown as T };
const hookRef: HookResult<T> = { current: undefined as unknown as T };
const container = document.createElement("div");
document.body.appendChild(container);
const root = createRoot(container);
function HookComponent() {
result.current = useHook();
const captureRef = useRef<T>(undefined as unknown as T);
captureRef.current = useHook();
// Expose the captured value through the outer hookRef via the React ref.
// This is safe because captureRef lives inside the component.
// eslint-disable-next-line react-hooks/immutability -- test harness: intentionally writes to outer capture object from inside component
hookRef.current = captureRef.current;
return null;
}
@@ -36,11 +40,9 @@ function mountHook<T>(useHook: () => T): {
});
return {
result,
hookRef,
unmount: () => {
act(() => {
root.unmount();
});
act(() => root.unmount());
container.remove();
},
};
@@ -58,7 +60,7 @@ describe("useStreamMetrics", () => {
});
it("starts with initial zero/null metrics", () => {
const { result, unmount } = mountHook(() => useStreamMetrics());
const { hookRef: result, unmount } = mountHook(() => useStreamMetrics());
expect(result.current.metrics.ttftMs).toBeNull();
expect(result.current.metrics.totalMs).toBeNull();
expect(result.current.metrics.tps).toBeNull();
@@ -69,7 +71,7 @@ describe("useStreamMetrics", () => {
});
it("start() resets timing refs and updates metrics state", () => {
const { result, unmount } = mountHook(() => useStreamMetrics());
const { hookRef: result, unmount } = mountHook(() => useStreamMetrics());
act(() => {
vi.setSystemTime(1000);
@@ -84,7 +86,7 @@ describe("useStreamMetrics", () => {
});
it("onFirstChunk() sets ttftMs relative to start", () => {
const { result, unmount } = mountHook(() => useStreamMetrics());
const { hookRef: result, unmount } = mountHook(() => useStreamMetrics());
act(() => {
vi.setSystemTime(1000);
@@ -101,7 +103,7 @@ describe("useStreamMetrics", () => {
});
it("onFirstChunk() is idempotent — only records first call", () => {
const { result, unmount } = mountHook(() => useStreamMetrics());
const { hookRef: result, unmount } = mountHook(() => useStreamMetrics());
act(() => {
vi.setSystemTime(1000);
@@ -123,7 +125,7 @@ describe("useStreamMetrics", () => {
});
it("finish() finalizes metrics with accumulated chunk tokens", () => {
const { result, unmount } = mountHook(() => useStreamMetrics());
const { hookRef: result, unmount } = mountHook(() => useStreamMetrics());
act(() => {
vi.setSystemTime(1000);
@@ -135,7 +137,7 @@ describe("useStreamMetrics", () => {
result.current.onFirstChunk();
});
// onChunk does not flush to state — accumulates in ref
// onChunk accumulates in ref without flushing state
result.current.onChunk(5);
result.current.onChunk(5);
result.current.onChunk(5);
@@ -156,7 +158,7 @@ describe("useStreamMetrics", () => {
});
it("finish(usage) overrides tokensIn + tokensOut with upstream values", () => {
const { result, unmount } = mountHook(() => useStreamMetrics());
const { hookRef: result, unmount } = mountHook(() => useStreamMetrics());
act(() => {
vi.setSystemTime(1000);
@@ -185,7 +187,7 @@ describe("useStreamMetrics", () => {
});
it("finish(usage) with only prompt_tokens partial override", () => {
const { result, unmount } = mountHook(() => useStreamMetrics());
const { hookRef: result, unmount } = mountHook(() => useStreamMetrics());
act(() => {
vi.setSystemTime(1000);
@@ -212,7 +214,7 @@ describe("useStreamMetrics", () => {
it("computes costUsd when pricing is provided", () => {
const pricing = { inUsdPer1k: 0.003, outUsdPer1k: 0.015 };
const { result, unmount } = mountHook(() => useStreamMetrics(pricing));
const { hookRef: result, unmount } = mountHook(() => useStreamMetrics(pricing));
act(() => {
vi.setSystemTime(1000);
@@ -235,7 +237,7 @@ describe("useStreamMetrics", () => {
});
it("costUsd is null when no pricing provided", () => {
const { result, unmount } = mountHook(() => useStreamMetrics());
const { hookRef: result, unmount } = mountHook(() => useStreamMetrics());
act(() => {
vi.setSystemTime(1000);
@@ -251,7 +253,7 @@ describe("useStreamMetrics", () => {
});
it("reset() zeros all metrics", () => {
const { result, unmount } = mountHook(() => useStreamMetrics());
const { hookRef: result, unmount } = mountHook(() => useStreamMetrics());
act(() => {
vi.setSystemTime(1000);
@@ -281,7 +283,7 @@ describe("useStreamMetrics", () => {
});
it("start() after previous run resets all accumulated state", () => {
const { result, unmount } = mountHook(() => useStreamMetrics());
const { hookRef: result, unmount } = mountHook(() => useStreamMetrics());
act(() => {
vi.setSystemTime(1000);

View File

@@ -2,8 +2,7 @@
// tests/unit/ui/use-structured-output.test.tsx
// Runs via Vitest (vitest.config.ts)
// Uses React DOM directly (no @testing-library/dom dep required).
import React from "react";
import { act } from "react";
import React, { act, useRef } from "react";
import { createRoot } from "react-dom/client";
import { describe, it, expect } from "vitest";
import {
@@ -12,21 +11,22 @@ import {
// ─── Minimal hook test harness ────────────────────────────────────────────────
interface HookCapture<T> {
current: T;
}
type HookResult<T> = { current: T };
function mountHook<T>(useHook: () => T): {
result: HookCapture<T>;
hookRef: HookResult<T>;
unmount: () => void;
} {
const result: HookCapture<T> = { current: undefined as unknown as T };
const hookRef: HookResult<T> = { current: undefined as unknown as T };
const container = document.createElement("div");
document.body.appendChild(container);
const root = createRoot(container);
function HookComponent() {
result.current = useHook();
const captureRef = useRef<T>(undefined as unknown as T);
captureRef.current = useHook();
// eslint-disable-next-line react-hooks/immutability -- test harness: intentionally writes to outer capture object from inside component
hookRef.current = captureRef.current;
return null;
}
@@ -35,7 +35,7 @@ function mountHook<T>(useHook: () => T): {
});
return {
result,
hookRef,
unmount: () => {
act(() => root.unmount());
container.remove();
@@ -73,7 +73,7 @@ const VALID_SCHEMA_NO_REQUIRED = {
describe("useStructuredOutput", () => {
describe("initial state", () => {
it("starts with enabled=false, schema=null, error=null", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
expect(result.current.enabled).toBe(false);
expect(result.current.schema).toBeNull();
expect(result.current.error).toBeNull();
@@ -83,7 +83,7 @@ describe("useStructuredOutput", () => {
describe("setEnabled()", () => {
it("sets enabled to true", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setEnabled(true);
@@ -94,7 +94,7 @@ describe("useStructuredOutput", () => {
});
it("toggles enabled back to false", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setEnabled(true);
@@ -108,7 +108,7 @@ describe("useStructuredOutput", () => {
describe("setSchema()", () => {
it("accepts a valid schema and clears error", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setSchema(VALID_SCHEMA);
@@ -120,7 +120,7 @@ describe("useStructuredOutput", () => {
});
it("sets error when schema name is empty", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setSchema({ name: "", schema: { type: "object" } });
@@ -132,7 +132,7 @@ describe("useStructuredOutput", () => {
});
it("sets error when name is too long (>64 chars)", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setSchema({ name: "a".repeat(65), schema: { type: "object" } });
@@ -143,7 +143,7 @@ describe("useStructuredOutput", () => {
});
it("clears error after previously invalid schema when valid one is set", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setSchema({ name: "", schema: {} });
@@ -159,7 +159,7 @@ describe("useStructuredOutput", () => {
});
it("accepts schema with strict=false", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setSchema({ ...VALID_SCHEMA, strict: false });
@@ -171,7 +171,7 @@ describe("useStructuredOutput", () => {
});
it("accepts schema without strict field", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setSchema(VALID_SCHEMA_NO_REQUIRED);
@@ -185,7 +185,7 @@ describe("useStructuredOutput", () => {
describe("validateResponse()", () => {
it("returns { valid: false, error } when no schema is set", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
const res = result.current.validateResponse({ temperature: 25, condition: "sunny" });
@@ -195,7 +195,7 @@ describe("useStructuredOutput", () => {
});
it("parses JSON string and validates correctly", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setSchema(VALID_SCHEMA);
@@ -209,7 +209,7 @@ describe("useStructuredOutput", () => {
});
it("returns { valid: false } when content is not valid JSON string", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setSchema(VALID_SCHEMA);
@@ -223,7 +223,7 @@ describe("useStructuredOutput", () => {
});
it("validates object directly (no JSON.parse needed)", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setSchema(VALID_SCHEMA);
@@ -235,7 +235,7 @@ describe("useStructuredOutput", () => {
});
it("returns { valid: false } when required field is missing", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setSchema(VALID_SCHEMA);
@@ -248,7 +248,7 @@ describe("useStructuredOutput", () => {
});
it("returns { valid: false } when content is null", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setSchema(VALID_SCHEMA_NO_REQUIRED);
@@ -260,7 +260,7 @@ describe("useStructuredOutput", () => {
});
it("returns { valid: false } when content is an array", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setSchema(VALID_SCHEMA_NO_REQUIRED);
@@ -272,7 +272,7 @@ describe("useStructuredOutput", () => {
});
it("passes validation for schema without required field", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setSchema(VALID_SCHEMA_NO_REQUIRED);
@@ -284,7 +284,7 @@ describe("useStructuredOutput", () => {
});
it("passes validation for schema with no properties field", () => {
const { result, unmount } = mountHook(() => useStructuredOutput());
const { hookRef: result, unmount } = mountHook(() => useStructuredOutput());
act(() => {
result.current.setSchema({ name: "AnyObj", schema: { type: "object" } });

View File

@@ -2,8 +2,7 @@
// tests/unit/ui/use-tools-builder.test.tsx
// Runs via Vitest (vitest.config.ts)
// Uses React DOM directly (no @testing-library/dom dep required).
import React from "react";
import { act } from "react";
import React, { act, useRef } from "react";
import { createRoot } from "react-dom/client";
import { describe, it, expect } from "vitest";
import {
@@ -13,21 +12,22 @@ import type { ToolDefinition } from "../../../src/lib/playground/codeExport";
// ─── Minimal hook test harness ────────────────────────────────────────────────
interface HookCapture<T> {
current: T;
}
type HookResult<T> = { current: T };
function mountHook<T>(useHook: () => T): {
result: HookCapture<T>;
hookRef: HookResult<T>;
unmount: () => void;
} {
const result: HookCapture<T> = { current: undefined as unknown as T };
const hookRef: HookResult<T> = { current: undefined as unknown as T };
const container = document.createElement("div");
document.body.appendChild(container);
const root = createRoot(container);
function HookComponent() {
result.current = useHook();
const captureRef = useRef<T>(undefined as unknown as T);
captureRef.current = useHook();
// eslint-disable-next-line react-hooks/immutability -- test harness: intentionally writes to outer capture object from inside component
hookRef.current = captureRef.current;
return null;
}
@@ -36,7 +36,7 @@ function mountHook<T>(useHook: () => T): {
});
return {
result,
hookRef,
unmount: () => {
act(() => root.unmount());
container.remove();
@@ -73,7 +73,7 @@ const VALID_TOOL_2: ToolDefinition = {
describe("useToolsBuilder", () => {
describe("initial state", () => {
it("starts with empty tools and empty errors", () => {
const { result, unmount } = mountHook(() => useToolsBuilder());
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
expect(result.current.tools).toHaveLength(0);
expect(result.current.errors.size).toBe(0);
unmount();
@@ -82,7 +82,7 @@ describe("useToolsBuilder", () => {
describe("add()", () => {
it("returns { ok: false, error } when tool has empty name", () => {
const { result, unmount } = mountHook(() => useToolsBuilder());
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
const invalidTool = {
type: "function" as const,
@@ -101,7 +101,7 @@ describe("useToolsBuilder", () => {
});
it("returns { ok: false } when type is not 'function'", () => {
const { result, unmount } = mountHook(() => useToolsBuilder());
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
const invalidTool = {
type: "not-function" as unknown as "function",
@@ -119,7 +119,7 @@ describe("useToolsBuilder", () => {
});
it("adds a valid tool and returns { ok: true }", () => {
const { result, unmount } = mountHook(() => useToolsBuilder());
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
let outcome: ReturnType<typeof result.current.add> | undefined;
act(() => {
@@ -133,7 +133,7 @@ describe("useToolsBuilder", () => {
});
it("adds multiple valid tools", () => {
const { result, unmount } = mountHook(() => useToolsBuilder());
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
act(() => {
result.current.add(VALID_TOOL);
@@ -145,7 +145,7 @@ describe("useToolsBuilder", () => {
});
it("does not add to tools when validation fails", () => {
const { result, unmount } = mountHook(() => useToolsBuilder());
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
act(() => {
result.current.add(VALID_TOOL);
@@ -159,7 +159,7 @@ describe("useToolsBuilder", () => {
describe("remove()", () => {
it("removes tool at given index", () => {
const { result, unmount } = mountHook(() => useToolsBuilder());
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
act(() => {
result.current.add(VALID_TOOL);
@@ -176,7 +176,7 @@ describe("useToolsBuilder", () => {
});
it("is a no-op when index is out of bounds", () => {
const { result, unmount } = mountHook(() => useToolsBuilder());
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
act(() => {
result.current.add(VALID_TOOL);
@@ -191,7 +191,7 @@ describe("useToolsBuilder", () => {
});
it("re-indexes errors after remove", () => {
const { result, unmount } = mountHook(() => useToolsBuilder());
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
act(() => {
result.current.add(VALID_TOOL);
@@ -219,7 +219,7 @@ describe("useToolsBuilder", () => {
describe("update()", () => {
it("updates a tool at given index after validation", () => {
const { result, unmount } = mountHook(() => useToolsBuilder());
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
act(() => {
result.current.add(VALID_TOOL);
@@ -242,7 +242,7 @@ describe("useToolsBuilder", () => {
});
it("returns { ok: false, error } and stores error when validation fails", () => {
const { result, unmount } = mountHook(() => useToolsBuilder());
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
act(() => {
result.current.add(VALID_TOOL);
@@ -264,7 +264,7 @@ describe("useToolsBuilder", () => {
});
it("clears error for that index on successful update", () => {
const { result, unmount } = mountHook(() => useToolsBuilder());
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
act(() => {
result.current.add(VALID_TOOL);
@@ -285,7 +285,7 @@ describe("useToolsBuilder", () => {
describe("clear()", () => {
it("removes all tools and clears all errors", () => {
const { result, unmount } = mountHook(() => useToolsBuilder());
const { hookRef: result, unmount } = mountHook(() => useToolsBuilder());
act(() => {
result.current.add(VALID_TOOL);