feat(FN-2109): merge fusion/fn-2109
This commit is contained in:
@@ -447,53 +447,38 @@ describe("MissionInterviewModal", () => {
|
|||||||
mockFetchModels.mockResolvedValue(mockModelsWithFavorites);
|
mockFetchModels.mockResolvedValue(mockModelsWithFavorites);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("calls updateGlobalSettings when toggling provider favorite", async () => {
|
it("persists provider favorite toggle via updateGlobalSettings", async () => {
|
||||||
const user = userEvent.setup();
|
mockFetchModels.mockResolvedValue({
|
||||||
renderModal();
|
models: mockModelsWithFavorites.models,
|
||||||
|
favoriteProviders: [],
|
||||||
// Wait for models to load
|
favoriteModels: [],
|
||||||
await waitFor(() => {
|
|
||||||
expect(mockFetchModels).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
mockUpdateGlobalSettings.mockResolvedValue({});
|
||||||
|
|
||||||
// The component has a provider favorite toggle - we need to find and click it
|
|
||||||
// For this test, we verify the mock was set up correctly
|
|
||||||
expect(mockUpdateGlobalSettings).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("persists provider favorite toggle to global settings", async () => {
|
|
||||||
const user = userEvent.setup();
|
|
||||||
renderModal();
|
renderModal();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockFetchModels).toHaveBeenCalled();
|
expect(mockFetchModels).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Simulate the toggle by directly calling updateGlobalSettings
|
fireEvent.click(screen.getByRole("button", { name: "Planning Model" }));
|
||||||
await mockUpdateGlobalSettings({ favoriteProviders: ["openai"], favoriteModels: ["anthropic/claude-sonnet-4-5"] });
|
|
||||||
|
|
||||||
expect(mockUpdateGlobalSettings).toHaveBeenCalledWith({
|
|
||||||
favoriteProviders: ["openai"],
|
|
||||||
favoriteModels: ["anthropic/claude-sonnet-4-5"],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("persists model favorite toggle to global settings", async () => {
|
|
||||||
renderModal();
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockFetchModels).toHaveBeenCalled();
|
expect(document.body.querySelector('[data-testid="model-combobox-portal"]')).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
await mockUpdateGlobalSettings({ favoriteProviders: ["anthropic"], favoriteModels: ["openai/gpt-4o"] });
|
const portal = document.body.querySelector('[data-testid="model-combobox-portal"]')!;
|
||||||
|
const addButton = within(portal).getByRole("button", { name: "Add anthropic to favorites" });
|
||||||
|
fireEvent.click(addButton);
|
||||||
|
|
||||||
expect(mockUpdateGlobalSettings).toHaveBeenCalledWith({
|
expect(mockUpdateGlobalSettings).toHaveBeenCalledWith({
|
||||||
favoriteProviders: ["anthropic"],
|
favoriteProviders: ["anthropic"],
|
||||||
favoriteModels: ["openai/gpt-4o"],
|
favoriteModels: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rolls back local favorite state when updateGlobalSettings fails", async () => {
|
it("rolls back local favorite state when updateGlobalSettings fails", async () => {
|
||||||
|
mockFetchModels.mockResolvedValue(mockModelsWithFavorites);
|
||||||
vi.mocked(api.updateGlobalSettings).mockRejectedValueOnce(new Error("Network error"));
|
vi.mocked(api.updateGlobalSettings).mockRejectedValueOnce(new Error("Network error"));
|
||||||
|
|
||||||
renderModal();
|
renderModal();
|
||||||
@@ -509,10 +494,11 @@ describe("MissionInterviewModal", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const portal = document.body.querySelector('[data-testid="model-combobox-portal"]')!;
|
const portal = document.body.querySelector('[data-testid="model-combobox-portal"]')!;
|
||||||
fireEvent.click(within(portal).getByRole("button", { name: "Remove anthropic from favorites" }));
|
const removeButton = within(portal).getByRole("button", { name: "Remove anthropic from favorites" });
|
||||||
|
fireEvent.click(removeButton);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(api.updateGlobalSettings).toHaveBeenCalled();
|
expect(mockUpdateGlobalSettings).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
const portalAfterRollback = document.body.querySelector('[data-testid="model-combobox-portal"]')!;
|
const portalAfterRollback = document.body.querySelector('[data-testid="model-combobox-portal"]')!;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
import { render, screen, fireEvent, waitFor, within } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { NewAgentDialog } from "../NewAgentDialog";
|
import { NewAgentDialog } from "../NewAgentDialog";
|
||||||
import * as apiModule from "../../api";
|
import * as apiModule from "../../api";
|
||||||
@@ -24,69 +24,6 @@ vi.mock("../SkillMultiselect", () => ({
|
|||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Mock CustomModelDropdown to simplify interaction testing
|
|
||||||
vi.mock("../CustomModelDropdown", () => ({
|
|
||||||
CustomModelDropdown: ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
label,
|
|
||||||
favoriteProviders = [],
|
|
||||||
favoriteModels = [],
|
|
||||||
onToggleFavorite,
|
|
||||||
onToggleModelFavorite,
|
|
||||||
}: {
|
|
||||||
value: string;
|
|
||||||
onChange: (v: string) => void;
|
|
||||||
label: string;
|
|
||||||
favoriteProviders?: string[];
|
|
||||||
favoriteModels?: string[];
|
|
||||||
onToggleFavorite?: (provider: string) => void;
|
|
||||||
onToggleModelFavorite?: (modelId: string) => void;
|
|
||||||
}) => (
|
|
||||||
<div data-testid="custom-model-dropdown">
|
|
||||||
<span data-testid="dropdown-label">{label}</span>
|
|
||||||
<span data-testid="dropdown-value">{value}</span>
|
|
||||||
<button
|
|
||||||
data-testid="dropdown-select-anthropic"
|
|
||||||
onClick={() => onChange("anthropic/claude-sonnet-4-5")}
|
|
||||||
>
|
|
||||||
Select Claude
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
data-testid="dropdown-select-default"
|
|
||||||
onClick={() => onChange("")}
|
|
||||||
>
|
|
||||||
Use default
|
|
||||||
</button>
|
|
||||||
{favoriteProviders.map((provider) => (
|
|
||||||
<button
|
|
||||||
key={`fav-provider-${provider}`}
|
|
||||||
aria-label={`Remove ${provider} from favorites`}
|
|
||||||
onClick={() => onToggleFavorite?.(provider)}
|
|
||||||
>
|
|
||||||
★ {provider}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
{favoriteModels.map((model) => (
|
|
||||||
<button
|
|
||||||
key={`fav-model-${model}`}
|
|
||||||
aria-label={`Remove ${model} from favorites`}
|
|
||||||
onClick={() => onToggleModelFavorite?.(model)}
|
|
||||||
>
|
|
||||||
★ {model}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Mock ProviderIcon
|
|
||||||
vi.mock("../ProviderIcon", () => ({
|
|
||||||
ProviderIcon: ({ provider }: { provider: string }) => (
|
|
||||||
<span data-testid={`provider-icon-${provider}`} />
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Mock AgentGenerationModal
|
// Mock AgentGenerationModal
|
||||||
vi.mock("../AgentGenerationModal", () => ({
|
vi.mock("../AgentGenerationModal", () => ({
|
||||||
AgentGenerationModal: ({ isOpen, onClose, onGenerated }: { isOpen: boolean; onClose: () => void; onGenerated: (spec: any) => void }) => {
|
AgentGenerationModal: ({ isOpen, onClose, onGenerated }: { isOpen: boolean; onClose: () => void; onGenerated: (spec: any) => void }) => {
|
||||||
@@ -156,6 +93,24 @@ const MOCK_SKILLS_RESPONSE = [
|
|||||||
{ id: "skill-2", name: "Skill Two", path: "/path/skill-2", relativePath: "skills/skill-2", enabled: true, metadata: { source: "*", scope: "user" as const, origin: "top-level" as const } },
|
{ id: "skill-2", name: "Skill Two", path: "/path/skill-2", relativePath: "skills/skill-2", enabled: true, metadata: { source: "*", scope: "user" as const, origin: "top-level" as const } },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
async function openModelDropdown(label = "Model") {
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: label }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(document.body.querySelector('[data-testid="model-combobox-portal"]')).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
return document.body.querySelector('[data-testid="model-combobox-portal"]') as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickModelOption(portal: HTMLElement, optionText: RegExp) {
|
||||||
|
const option = within(portal)
|
||||||
|
.getAllByRole("option")
|
||||||
|
.find((candidate) => optionText.test(candidate.textContent ?? ""));
|
||||||
|
expect(option).toBeTruthy();
|
||||||
|
fireEvent.click(option!);
|
||||||
|
}
|
||||||
|
|
||||||
describe("NewAgentDialog", () => {
|
describe("NewAgentDialog", () => {
|
||||||
const mockOnClose = vi.fn();
|
const mockOnClose = vi.fn();
|
||||||
const mockOnCreated = vi.fn();
|
const mockOnCreated = vi.fn();
|
||||||
@@ -212,7 +167,7 @@ describe("NewAgentDialog", () => {
|
|||||||
// Resolve models
|
// Resolve models
|
||||||
resolveModels!(MOCK_MODELS_RESPONSE);
|
resolveModels!(MOCK_MODELS_RESPONSE);
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId("custom-model-dropdown")).toBeTruthy();
|
expect(screen.getByRole("button", { name: "Model" })).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -232,9 +187,9 @@ describe("NewAgentDialog", () => {
|
|||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
expect(screen.getByTestId("custom-model-dropdown")).toBeTruthy();
|
const modelTrigger = screen.getByRole("button", { name: "Model" });
|
||||||
expect(screen.getByTestId("dropdown-label").textContent).toBe("Model");
|
expect(modelTrigger).toBeTruthy();
|
||||||
expect(screen.getByTestId("dropdown-value").textContent).toBe("");
|
expect(modelTrigger.textContent).toContain("Use default");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("selecting a model from dropdown updates state", async () => {
|
it("selecting a model from dropdown updates state", async () => {
|
||||||
@@ -253,10 +208,10 @@ describe("NewAgentDialog", () => {
|
|||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
// Select a model from the mocked dropdown
|
const portal = await openModelDropdown();
|
||||||
await user.click(screen.getByTestId("dropdown-select-anthropic"));
|
clickModelOption(portal, /Claude Sonnet 4.5/i);
|
||||||
|
|
||||||
expect(screen.getByTestId("dropdown-value").textContent).toBe("anthropic/claude-sonnet-4-5");
|
expect(screen.getByRole("button", { name: "Model" }).textContent).toContain("Claude Sonnet 4.5");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("deselecting model sets value back to default", async () => {
|
it("deselecting model sets value back to default", async () => {
|
||||||
@@ -273,12 +228,14 @@ describe("NewAgentDialog", () => {
|
|||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
// Select a model
|
// Select a model
|
||||||
await user.click(screen.getByTestId("dropdown-select-anthropic"));
|
const selectPortal = await openModelDropdown();
|
||||||
expect(screen.getByTestId("dropdown-value").textContent).toBe("anthropic/claude-sonnet-4-5");
|
clickModelOption(selectPortal, /Claude Sonnet 4.5/i);
|
||||||
|
expect(screen.getByRole("button", { name: "Model" }).textContent).toContain("Claude Sonnet 4.5");
|
||||||
|
|
||||||
// Deselect (use default)
|
// Deselect (use default)
|
||||||
await user.click(screen.getByTestId("dropdown-select-default"));
|
const defaultPortal = await openModelDropdown();
|
||||||
expect(screen.getByTestId("dropdown-value").textContent).toBe("");
|
fireEvent.click(within(defaultPortal).getByRole("option", { name: "Use default" }));
|
||||||
|
expect(screen.getByRole("button", { name: "Model" }).textContent).toContain("Use default");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -317,13 +274,14 @@ describe("NewAgentDialog", () => {
|
|||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
// Select a model
|
// Select a model
|
||||||
await user.click(screen.getByTestId("dropdown-select-anthropic"));
|
const portal = await openModelDropdown();
|
||||||
|
clickModelOption(portal, /Claude Sonnet 4.5/i);
|
||||||
|
|
||||||
// Navigate to step 2
|
// Navigate to step 2
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
// Summary should show model name
|
// Summary should show model name
|
||||||
expect(screen.getByTestId("provider-icon-anthropic")).toBeTruthy();
|
expect(screen.getByTestId("anthropic-icon")).toBeTruthy();
|
||||||
expect(screen.getByText("Claude Sonnet 4.5")).toBeTruthy();
|
expect(screen.getByText("Claude Sonnet 4.5")).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -343,7 +301,8 @@ describe("NewAgentDialog", () => {
|
|||||||
|
|
||||||
// Step 1: Navigate and select model
|
// Step 1: Navigate and select model
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
await user.click(screen.getByTestId("dropdown-select-anthropic"));
|
const portal = await openModelDropdown();
|
||||||
|
clickModelOption(portal, /Claude Sonnet 4.5/i);
|
||||||
|
|
||||||
// Step 2: Navigate to summary and create
|
// Step 2: Navigate to summary and create
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
@@ -403,7 +362,8 @@ describe("NewAgentDialog", () => {
|
|||||||
|
|
||||||
// Step 1: Select model and thinking level
|
// Step 1: Select model and thinking level
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
await user.click(screen.getByTestId("dropdown-select-anthropic"));
|
const portal = await openModelDropdown();
|
||||||
|
clickModelOption(portal, /Claude Sonnet 4.5/i);
|
||||||
|
|
||||||
const thinkingSelect = screen.getByLabelText(/Thinking Level/);
|
const thinkingSelect = screen.getByLabelText(/Thinking Level/);
|
||||||
await user.selectOptions(thinkingSelect, "high");
|
await user.selectOptions(thinkingSelect, "high");
|
||||||
@@ -441,7 +401,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
// Dropdown should still render (just with empty models)
|
// Dropdown should still render (just with empty models)
|
||||||
expect(screen.getByTestId("custom-model-dropdown")).toBeTruthy();
|
expect(screen.getByRole("button", { name: "Model" })).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -460,7 +420,8 @@ describe("NewAgentDialog", () => {
|
|||||||
|
|
||||||
// Navigate to step 1 and select model
|
// Navigate to step 1 and select model
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
await user.click(screen.getByTestId("dropdown-select-anthropic"));
|
const portal = await openModelDropdown();
|
||||||
|
clickModelOption(portal, /Claude Sonnet 4.5/i);
|
||||||
|
|
||||||
// Close the dialog
|
// Close the dialog
|
||||||
await user.click(screen.getByLabelText("Close"));
|
await user.click(screen.getByLabelText("Close"));
|
||||||
@@ -516,7 +477,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await user.click(screen.getByTestId("generation-modal-apply"));
|
await user.click(screen.getByTestId("generation-modal-apply"));
|
||||||
|
|
||||||
// Should advance to step 1 (model config)
|
// Should advance to step 1 (model config)
|
||||||
expect(screen.getByTestId("custom-model-dropdown")).toBeTruthy();
|
expect(screen.getByRole("button", { name: "Model" })).toBeTruthy();
|
||||||
|
|
||||||
// Navigate to step 2 to verify the summary
|
// Navigate to step 2 to verify the summary
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
@@ -700,7 +661,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await user.click(screen.getByTestId("preset-cto"));
|
await user.click(screen.getByTestId("preset-cto"));
|
||||||
|
|
||||||
// Should be on step 1 — model dropdown visible
|
// Should be on step 1 — model dropdown visible
|
||||||
expect(screen.getByTestId("custom-model-dropdown")).toBeTruthy();
|
expect(screen.getByRole("button", { name: "Model" })).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("selected preset card has .selected CSS class", async () => {
|
it("selected preset card has .selected CSS class", async () => {
|
||||||
@@ -1117,82 +1078,112 @@ describe("NewAgentDialog", () => {
|
|||||||
|
|
||||||
describe("model favorites persistence", () => {
|
describe("model favorites persistence", () => {
|
||||||
it("calls updateGlobalSettings when toggling provider favorite", async () => {
|
it("calls updateGlobalSettings when toggling provider favorite", async () => {
|
||||||
|
mockFetchModels.mockResolvedValue({
|
||||||
|
models: MOCK_MODELS_RESPONSE.models,
|
||||||
|
favoriteProviders: [],
|
||||||
|
favoriteModels: [],
|
||||||
|
});
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Navigate to step 1 where the model selector is visible
|
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = screen.getByLabelText(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
// The updateGlobalSettings should be called when favorite toggle happens
|
const portal = await openModelDropdown();
|
||||||
// Since we can't easily interact with the favorite toggle in tests,
|
fireEvent.click(within(portal).getByRole("button", { name: "Add anthropic to favorites" }));
|
||||||
// we verify the mock is set up correctly
|
|
||||||
expect(mockUpdateGlobalSettings).toBeDefined();
|
await waitFor(() => {
|
||||||
|
expect(mockUpdateGlobalSettings).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("persists provider favorite toggle to global settings", async () => {
|
it("persists provider favorite toggle to global settings", async () => {
|
||||||
|
mockFetchModels.mockResolvedValue({
|
||||||
|
models: MOCK_MODELS_RESPONSE.models,
|
||||||
|
favoriteProviders: [],
|
||||||
|
favoriteModels: [],
|
||||||
|
});
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Simulate the toggle by directly calling updateGlobalSettings
|
const user = userEvent.setup();
|
||||||
await mockUpdateGlobalSettings({
|
const nameInput = screen.getByLabelText(/Name/);
|
||||||
favoriteProviders: ["openai"],
|
await user.type(nameInput, "Test Agent");
|
||||||
favoriteModels: ["anthropic/claude-sonnet-4-5"],
|
await user.click(screen.getByText("Next"));
|
||||||
});
|
|
||||||
|
const portal = await openModelDropdown();
|
||||||
|
fireEvent.click(within(portal).getByRole("button", { name: "Add anthropic to favorites" }));
|
||||||
|
|
||||||
expect(mockUpdateGlobalSettings).toHaveBeenCalledWith({
|
expect(mockUpdateGlobalSettings).toHaveBeenCalledWith({
|
||||||
favoriteProviders: ["openai"],
|
favoriteProviders: ["anthropic"],
|
||||||
favoriteModels: ["anthropic/claude-sonnet-4-5"],
|
favoriteModels: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("persists model favorite toggle to global settings", async () => {
|
it("persists model favorite toggle to global settings", async () => {
|
||||||
|
mockFetchModels.mockResolvedValue({
|
||||||
|
models: MOCK_MODELS_RESPONSE.models,
|
||||||
|
favoriteProviders: [],
|
||||||
|
favoriteModels: [],
|
||||||
|
});
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
await mockUpdateGlobalSettings({
|
const user = userEvent.setup();
|
||||||
favoriteProviders: ["anthropic"],
|
const nameInput = screen.getByLabelText(/Name/);
|
||||||
favoriteModels: ["openai/gpt-4o"],
|
await user.type(nameInput, "Test Agent");
|
||||||
});
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
|
const portal = await openModelDropdown();
|
||||||
|
fireEvent.click(within(portal).getByRole("button", { name: "Add Claude Sonnet 4.5 to favorites" }));
|
||||||
|
|
||||||
expect(mockUpdateGlobalSettings).toHaveBeenCalledWith({
|
expect(mockUpdateGlobalSettings).toHaveBeenCalledWith({
|
||||||
favoriteProviders: ["anthropic"],
|
favoriteProviders: [],
|
||||||
favoriteModels: ["openai/gpt-4o"],
|
favoriteModels: ["anthropic/claude-sonnet-4-5"],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rolls back local favorite state when updateGlobalSettings fails", async () => {
|
it("rolls back local favorite state when updateGlobalSettings fails", async () => {
|
||||||
|
mockFetchModels.mockResolvedValue(MOCK_MODELS_RESPONSE);
|
||||||
vi.mocked(apiModule.updateGlobalSettings).mockRejectedValueOnce(new Error("Network error"));
|
vi.mocked(apiModule.updateGlobalSettings).mockRejectedValueOnce(new Error("Network error"));
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => {
|
||||||
|
expect(mockFetchModels).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = screen.getByLabelText(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Remove anthropic from favorites" }));
|
const portal = await openModelDropdown();
|
||||||
|
const removeButton = within(portal).getByRole("button", { name: "Remove anthropic from favorites" });
|
||||||
|
fireEvent.click(removeButton);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(apiModule.updateGlobalSettings).toHaveBeenCalled();
|
expect(apiModule.updateGlobalSettings).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(screen.getByRole("button", { name: "Remove anthropic from favorites" })).toBeTruthy();
|
const portalAfterRollback = document.body.querySelector('[data-testid="model-combobox-portal"]') as HTMLElement;
|
||||||
|
expect(within(portalAfterRollback).getByRole("button", { name: "Remove anthropic from favorites" })).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user