Files
fusion/packages/dashboard/app/hooks/__tests__/useFavorites.test.ts
Fusion (runfusion.ai) cfc70b21bd feat(FN-5202): add SWR caching for chat agents, skills, and models
This merge introduces SWR-style caching across the dashboard to eliminate redundant fetches for models, agents, and skills, significantly refactoring ChatView.tsx (reduced by ~140 lines) by offloading cache coordination to three new dedicated hooks (`useAgentsMapCache`, `useDiscoveredSkillsCache`, `

Fusion-Task-Id: FN-5202
2026-05-20 03:19:44 -07:00

179 lines
5.7 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { act, renderHook, waitFor } from "@testing-library/react";
import { useFavorites } from "../useFavorites";
import { useModelsCache } from "../useModelsCache";
import * as api from "../../api";
vi.mock("../../api", () => ({
fetchModels: vi.fn(),
updateGlobalSettings: vi.fn(),
}));
const mockFetchModels = vi.mocked(api.fetchModels);
const mockUpdateGlobalSettings = vi.mocked(api.updateGlobalSettings);
describe("useFavorites", () => {
beforeEach(() => {
vi.clearAllMocks();
localStorage.clear();
mockFetchModels.mockResolvedValue({
models: [
{
provider: "anthropic",
id: "claude-sonnet-4-5",
name: "Claude Sonnet 4.5",
reasoning: true,
contextWindow: 200000,
},
],
favoriteProviders: ["openai"],
favoriteModels: ["gpt-4o"],
});
mockUpdateGlobalSettings.mockResolvedValue({} as never);
});
it("loads available models and favorites from API", async () => {
const { result } = renderHook(() => useFavorites());
await waitFor(() => {
expect(result.current.availableModels).toHaveLength(1);
expect(result.current.favoriteProviders).toEqual(["openai"]);
expect(result.current.favoriteModels).toEqual(["gpt-4o"]);
});
expect(mockFetchModels).toHaveBeenCalledTimes(1);
});
it("deduplicates fetchModels across useFavorites and useModelsCache mounts", async () => {
const favoritesHook = renderHook(() => useFavorites());
const modelsHook = renderHook(() => useModelsCache());
await waitFor(() => {
expect(favoritesHook.result.current.favoriteProviders).toEqual(["openai"]);
expect(modelsHook.result.current.models).toHaveLength(1);
});
expect(mockFetchModels).toHaveBeenCalledTimes(1);
});
it("optimistically toggles provider favorite and persists settings", async () => {
const { result } = renderHook(() => useFavorites());
await waitFor(() => {
expect(result.current.favoriteProviders).toEqual(["openai"]);
});
await act(async () => {
await result.current.toggleFavoriteProvider("anthropic");
});
expect(result.current.favoriteProviders).toEqual(["anthropic", "openai"]);
expect(mockUpdateGlobalSettings).toHaveBeenCalledWith({
favoriteProviders: ["anthropic", "openai"],
favoriteModels: ["gpt-4o"],
});
});
it("rolls back provider favorite on update failure", async () => {
mockUpdateGlobalSettings.mockRejectedValueOnce(new Error("network"));
const { result } = renderHook(() => useFavorites());
await waitFor(() => {
expect(result.current.favoriteProviders).toEqual(["openai"]);
});
await act(async () => {
await expect(result.current.toggleFavoriteProvider("anthropic")).rejects.toThrow("network");
});
expect(result.current.favoriteProviders).toEqual(["openai"]);
});
it("rolls back model favorite on update failure", async () => {
mockUpdateGlobalSettings.mockRejectedValueOnce(new Error("network"));
const { result } = renderHook(() => useFavorites());
await waitFor(() => {
expect(result.current.favoriteModels).toEqual(["gpt-4o"]);
});
await act(async () => {
await expect(result.current.toggleFavoriteModel("claude-sonnet-4-5")).rejects.toThrow("network");
});
expect(result.current.favoriteModels).toEqual(["gpt-4o"]);
});
it("toggle model favorite preserves a prior provider favorite change", async () => {
const { result } = renderHook(() => useFavorites());
await waitFor(() => {
expect(result.current.favoriteProviders).toEqual(["openai"]);
expect(result.current.favoriteModels).toEqual(["gpt-4o"]);
});
await act(async () => {
await result.current.toggleFavoriteProvider("anthropic");
});
await act(async () => {
await result.current.toggleFavoriteModel("claude-sonnet-4-5");
});
expect(mockUpdateGlobalSettings).toHaveBeenNthCalledWith(2, {
favoriteProviders: ["anthropic", "openai"],
favoriteModels: ["claude-sonnet-4-5", "gpt-4o"],
});
});
it("toggle provider favorite preserves a prior model favorite change", async () => {
const { result } = renderHook(() => useFavorites());
await waitFor(() => {
expect(result.current.favoriteProviders).toEqual(["openai"]);
expect(result.current.favoriteModels).toEqual(["gpt-4o"]);
});
await act(async () => {
await result.current.toggleFavoriteModel("claude-sonnet-4-5");
});
await act(async () => {
await result.current.toggleFavoriteProvider("anthropic");
});
expect(mockUpdateGlobalSettings).toHaveBeenNthCalledWith(2, {
favoriteProviders: ["anthropic", "openai"],
favoriteModels: ["claude-sonnet-4-5", "gpt-4o"],
});
});
it("rapid toggles of both model and provider favorites persist correctly", async () => {
const { result } = renderHook(() => useFavorites());
await waitFor(() => {
expect(result.current.favoriteProviders).toEqual(["openai"]);
expect(result.current.favoriteModels).toEqual(["gpt-4o"]);
});
await act(async () => {
const toggleModelPromise = result.current.toggleFavoriteModel("claude-sonnet-4-5");
const toggleProviderPromise = result.current.toggleFavoriteProvider("anthropic");
await Promise.all([toggleModelPromise, toggleProviderPromise]);
});
expect(mockUpdateGlobalSettings).toHaveBeenNthCalledWith(1, {
favoriteProviders: ["openai"],
favoriteModels: ["claude-sonnet-4-5", "gpt-4o"],
});
expect(mockUpdateGlobalSettings).toHaveBeenNthCalledWith(2, {
favoriteProviders: ["anthropic", "openai"],
favoriteModels: ["claude-sonnet-4-5", "gpt-4o"],
});
});
});