feat(FN-752): wire up model favorites across dashboard components
- Add favorite model selection to QuickEntryBox and InlineCreateCard components - Wire up model favorites in TaskForm and TaskDetailModal - Add model favorites management UI to SettingsModal - Add tests for favorites in InlineCreateCard, QuickEntryBox, TaskForm, ListView, and SettingsModal - Remove unused CommitDiffTab component and related tests
This commit is contained in:
@@ -89,6 +89,7 @@ export function InlineCreateCard({
|
||||
const [modelsError, setModelsError] = useState<string | null>(null);
|
||||
const [loadedModels, setLoadedModels] = useState<ModelInfo[]>(availableModels ?? []);
|
||||
const [favoriteProviders, setFavoriteProviders] = useState<string[]>([]);
|
||||
const [favoriteModels, setFavoriteModels] = useState<string[]>([]);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [pendingImages, setPendingImages] = useState<PendingImage[]>([]);
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
@@ -118,6 +119,7 @@ export function InlineCreateCard({
|
||||
const response = await fetchModels();
|
||||
setLoadedModels(response.models);
|
||||
setFavoriteProviders(response.favoriteProviders);
|
||||
setFavoriteModels(response.favoriteModels);
|
||||
} catch (err: any) {
|
||||
setModelsError(err?.message || "Failed to load models");
|
||||
} finally {
|
||||
@@ -150,6 +152,7 @@ export function InlineCreateCard({
|
||||
if (!cancelled) {
|
||||
setLoadedModels(response.models);
|
||||
setFavoriteProviders(response.favoriteProviders);
|
||||
setFavoriteModels(response.favoriteModels);
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
@@ -422,12 +425,29 @@ export function InlineCreateCard({
|
||||
setFavoriteProviders(newFavorites);
|
||||
|
||||
try {
|
||||
await updateGlobalSettings({ favoriteProviders: newFavorites });
|
||||
await updateGlobalSettings({ favoriteProviders: newFavorites, favoriteModels });
|
||||
} catch {
|
||||
// Revert on error
|
||||
setFavoriteProviders(currentFavorites);
|
||||
}
|
||||
}, [favoriteProviders]);
|
||||
}, [favoriteProviders, favoriteModels]);
|
||||
|
||||
const handleToggleModelFavorite = useCallback(async (modelId: string) => {
|
||||
const currentFavorites = favoriteModels;
|
||||
const isFavorite = currentFavorites.includes(modelId);
|
||||
const newFavorites = isFavorite
|
||||
? currentFavorites.filter((m) => m !== modelId)
|
||||
: [modelId, ...currentFavorites];
|
||||
|
||||
setFavoriteModels(newFavorites);
|
||||
|
||||
try {
|
||||
await updateGlobalSettings({ favoriteProviders, favoriteModels: newFavorites });
|
||||
} catch {
|
||||
// Revert on error
|
||||
setFavoriteModels(currentFavorites);
|
||||
}
|
||||
}, [favoriteModels, favoriteProviders]);
|
||||
|
||||
const handleModelDropdownMouseDown = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const target = e.target;
|
||||
@@ -750,6 +770,8 @@ export function InlineCreateCard({
|
||||
onRetry={loadModels}
|
||||
favoriteProviders={favoriteProviders}
|
||||
onToggleFavorite={handleToggleFavorite}
|
||||
favoriteModels={favoriteModels}
|
||||
onToggleModelFavorite={handleToggleModelFavorite}
|
||||
/>,
|
||||
document.body,
|
||||
)
|
||||
|
||||
@@ -35,6 +35,10 @@ interface ListViewProps {
|
||||
onNewTask?: () => void;
|
||||
onQuickCreate?: (input: TaskCreateInput) => Promise<void>;
|
||||
availableModels?: ModelInfo[];
|
||||
favoriteProviders?: string[];
|
||||
favoriteModels?: string[];
|
||||
onToggleFavorite?: (provider: string) => void;
|
||||
onToggleModelFavorite?: (modelId: string) => void;
|
||||
/**
|
||||
* Called when the user clicks the "Plan" button in the quick entry box.
|
||||
*/
|
||||
@@ -75,6 +79,10 @@ export function ListView({
|
||||
onNewTask,
|
||||
onQuickCreate,
|
||||
availableModels,
|
||||
favoriteProviders = [],
|
||||
favoriteModels = [],
|
||||
onToggleFavorite,
|
||||
onToggleModelFavorite,
|
||||
onPlanningMode,
|
||||
onSubtaskBreakdown,
|
||||
onTasksUpdated,
|
||||
@@ -668,6 +676,10 @@ export function ListView({
|
||||
onChange={(value) => setExecutorModel(value === "" ? "__no_change__" : value)}
|
||||
label="Executor Model"
|
||||
placeholder="No change"
|
||||
favoriteProviders={favoriteProviders}
|
||||
onToggleFavorite={onToggleFavorite}
|
||||
favoriteModels={favoriteModels}
|
||||
onToggleModelFavorite={onToggleModelFavorite}
|
||||
/>
|
||||
</div>
|
||||
<div className="bulk-edit-dropdown">
|
||||
@@ -677,6 +689,10 @@ export function ListView({
|
||||
onChange={(value) => setValidatorModel(value === "" ? "__no_change__" : value)}
|
||||
label="Validator Model"
|
||||
placeholder="No change"
|
||||
favoriteProviders={favoriteProviders}
|
||||
onToggleFavorite={onToggleFavorite}
|
||||
favoriteModels={favoriteModels}
|
||||
onToggleModelFavorite={onToggleModelFavorite}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
|
||||
@@ -87,6 +87,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
||||
const [modelsError, setModelsError] = useState<string | null>(null);
|
||||
const [loadedModels, setLoadedModels] = useState<ModelInfo[]>(availableModels ?? []);
|
||||
const [favoriteProviders, setFavoriteProviders] = useState<string[]>([]);
|
||||
const [favoriteModels, setFavoriteModels] = useState<string[]>([]);
|
||||
|
||||
// AI Refinement state
|
||||
const [isRefineMenuOpen, setIsRefineMenuOpen] = useState(false);
|
||||
@@ -113,6 +114,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
||||
if (!cancelled) {
|
||||
setLoadedModels(response.models);
|
||||
setFavoriteProviders(response.favoriteProviders);
|
||||
setFavoriteModels(response.favoriteModels);
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
@@ -388,12 +390,29 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
||||
setFavoriteProviders(newFavorites);
|
||||
|
||||
try {
|
||||
await updateGlobalSettings({ favoriteProviders: newFavorites });
|
||||
await updateGlobalSettings({ favoriteProviders: newFavorites, favoriteModels });
|
||||
} catch {
|
||||
// Revert on error
|
||||
setFavoriteProviders(currentFavorites);
|
||||
}
|
||||
}, [favoriteProviders]);
|
||||
}, [favoriteProviders, favoriteModels]);
|
||||
|
||||
const handleToggleModelFavorite = useCallback(async (modelId: string) => {
|
||||
const currentFavorites = favoriteModels;
|
||||
const isFavorite = currentFavorites.includes(modelId);
|
||||
const newFavorites = isFavorite
|
||||
? currentFavorites.filter((m) => m !== modelId)
|
||||
: [modelId, ...currentFavorites];
|
||||
|
||||
setFavoriteModels(newFavorites);
|
||||
|
||||
try {
|
||||
await updateGlobalSettings({ favoriteProviders, favoriteModels: newFavorites });
|
||||
} catch {
|
||||
// Revert on error
|
||||
setFavoriteModels(currentFavorites);
|
||||
}
|
||||
}, [favoriteModels, favoriteProviders]);
|
||||
|
||||
const handlePlanClick = useCallback(() => {
|
||||
const trimmed = description.trim();
|
||||
@@ -462,6 +481,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
||||
const response = await fetchModels();
|
||||
setLoadedModels(response.models);
|
||||
setFavoriteProviders(response.favoriteProviders);
|
||||
setFavoriteModels(response.favoriteModels);
|
||||
} catch (err: any) {
|
||||
setModelsError(err?.message || "Failed to load models");
|
||||
} finally {
|
||||
@@ -702,6 +722,8 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
||||
onRetry={loadModels}
|
||||
favoriteProviders={favoriteProviders}
|
||||
onToggleFavorite={handleToggleFavorite}
|
||||
favoriteModels={favoriteModels}
|
||||
onToggleModelFavorite={handleToggleModelFavorite}
|
||||
/>,
|
||||
document.body,
|
||||
)
|
||||
|
||||
@@ -91,6 +91,8 @@ export function SettingsModal({
|
||||
// Model state
|
||||
const [availableModels, setAvailableModels] = useState<ModelInfo[]>([]);
|
||||
const [modelsLoading, setModelsLoading] = useState(false);
|
||||
const [favoriteProviders, setFavoriteProviders] = useState<string[]>([]);
|
||||
const [favoriteModels, setFavoriteModels] = useState<string[]>([]);
|
||||
|
||||
// Test notification state
|
||||
const [testNotificationLoading, setTestNotificationLoading] = useState(false);
|
||||
@@ -137,7 +139,11 @@ export function SettingsModal({
|
||||
if (activeSection === "models") {
|
||||
setModelsLoading(true);
|
||||
fetchModels()
|
||||
.then((response) => setAvailableModels(response.models))
|
||||
.then((response) => {
|
||||
setAvailableModels(response.models);
|
||||
setFavoriteProviders(response.favoriteProviders);
|
||||
setFavoriteModels(response.favoriteModels);
|
||||
})
|
||||
.catch(() => setAvailableModels([]))
|
||||
.finally(() => setModelsLoading(false));
|
||||
}
|
||||
@@ -327,6 +333,38 @@ export function SettingsModal({
|
||||
}
|
||||
}, [addToast, importPreview, importScope, importMerge, projectId]);
|
||||
|
||||
const handleToggleFavorite = useCallback(async (provider: string) => {
|
||||
const currentFavorites = favoriteProviders;
|
||||
const isFavorite = currentFavorites.includes(provider);
|
||||
const newFavorites = isFavorite
|
||||
? currentFavorites.filter((p) => p !== provider)
|
||||
: [provider, ...currentFavorites];
|
||||
|
||||
setFavoriteProviders(newFavorites);
|
||||
|
||||
try {
|
||||
await updateGlobalSettings({ favoriteProviders: newFavorites, favoriteModels });
|
||||
} catch {
|
||||
setFavoriteProviders(currentFavorites);
|
||||
}
|
||||
}, [favoriteProviders, favoriteModels]);
|
||||
|
||||
const handleToggleModelFavorite = useCallback(async (modelId: string) => {
|
||||
const currentFavorites = favoriteModels;
|
||||
const isFavorite = currentFavorites.includes(modelId);
|
||||
const newFavorites = isFavorite
|
||||
? currentFavorites.filter((m) => m !== modelId)
|
||||
: [modelId, ...currentFavorites];
|
||||
|
||||
setFavoriteModels(newFavorites);
|
||||
|
||||
try {
|
||||
await updateGlobalSettings({ favoriteProviders, favoriteModels: newFavorites });
|
||||
} catch {
|
||||
setFavoriteModels(currentFavorites);
|
||||
}
|
||||
}, [favoriteModels, favoriteProviders]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKey = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
@@ -536,6 +574,10 @@ export function SettingsModal({
|
||||
}
|
||||
}}
|
||||
placeholder="Use default"
|
||||
favoriteProviders={favoriteProviders}
|
||||
onToggleFavorite={handleToggleFavorite}
|
||||
favoriteModels={favoriteModels}
|
||||
onToggleModelFavorite={handleToggleModelFavorite}
|
||||
/>
|
||||
<small>Default AI model used for task execution when no per-task override is set. "Use default" lets the engine choose automatically.</small>
|
||||
</div>
|
||||
@@ -598,6 +640,10 @@ export function SettingsModal({
|
||||
}
|
||||
}}
|
||||
placeholder="Use default"
|
||||
favoriteProviders={favoriteProviders}
|
||||
onToggleFavorite={handleToggleFavorite}
|
||||
favoriteModels={favoriteModels}
|
||||
onToggleModelFavorite={handleToggleModelFavorite}
|
||||
/>
|
||||
<small>AI model used for task planning and specification (triage). Falls back to Default Model when not set.</small>
|
||||
</div>
|
||||
@@ -621,6 +667,10 @@ export function SettingsModal({
|
||||
}
|
||||
}}
|
||||
placeholder="Use default"
|
||||
favoriteProviders={favoriteProviders}
|
||||
onToggleFavorite={handleToggleFavorite}
|
||||
favoriteModels={favoriteModels}
|
||||
onToggleModelFavorite={handleToggleModelFavorite}
|
||||
/>
|
||||
<small>AI model used for code and specification review. Falls back to Default Model when not set.</small>
|
||||
</div>
|
||||
@@ -760,6 +810,10 @@ export function SettingsModal({
|
||||
} : current);
|
||||
}}
|
||||
placeholder="Use default"
|
||||
favoriteProviders={favoriteProviders}
|
||||
onToggleFavorite={handleToggleFavorite}
|
||||
favoriteModels={favoriteModels}
|
||||
onToggleModelFavorite={handleToggleModelFavorite}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
@@ -782,6 +836,10 @@ export function SettingsModal({
|
||||
} : current);
|
||||
}}
|
||||
placeholder="Use default"
|
||||
favoriteProviders={favoriteProviders}
|
||||
onToggleFavorite={handleToggleFavorite}
|
||||
favoriteModels={favoriteModels}
|
||||
onToggleModelFavorite={handleToggleModelFavorite}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
@@ -889,6 +947,10 @@ export function SettingsModal({
|
||||
}));
|
||||
}}
|
||||
placeholder="Use fallback model"
|
||||
favoriteProviders={favoriteProviders}
|
||||
onToggleFavorite={handleToggleFavorite}
|
||||
favoriteModels={favoriteModels}
|
||||
onToggleModelFavorite={handleToggleModelFavorite}
|
||||
/>
|
||||
)}
|
||||
<small>
|
||||
|
||||
@@ -90,6 +90,7 @@ export function TaskForm({
|
||||
const [depSearch, setDepSearch] = useState("");
|
||||
const [availableModels, setAvailableModels] = useState<ModelInfo[]>([]);
|
||||
const [favoriteProviders, setFavoriteProviders] = useState<string[]>([]);
|
||||
const [favoriteModels, setFavoriteModels] = useState<string[]>([]);
|
||||
const [modelsLoading, setModelsLoading] = useState(false);
|
||||
const [settings, setSettings] = useState<Settings | null>(null);
|
||||
const [workflowSteps, setWorkflowSteps] = useState<WorkflowStep[]>([]);
|
||||
@@ -112,6 +113,7 @@ export function TaskForm({
|
||||
.then((response) => {
|
||||
setAvailableModels(response.models);
|
||||
setFavoriteProviders(response.favoriteProviders);
|
||||
setFavoriteModels(response.favoriteModels);
|
||||
})
|
||||
.catch(() => {/* silently fail */})
|
||||
.finally(() => setModelsLoading(false));
|
||||
@@ -269,11 +271,27 @@ export function TaskForm({
|
||||
setFavoriteProviders(newFavorites);
|
||||
|
||||
try {
|
||||
await updateGlobalSettings({ favoriteProviders: newFavorites });
|
||||
await updateGlobalSettings({ favoriteProviders: newFavorites, favoriteModels });
|
||||
} catch {
|
||||
setFavoriteProviders(currentFavorites);
|
||||
}
|
||||
}, [favoriteProviders]);
|
||||
}, [favoriteProviders, favoriteModels]);
|
||||
|
||||
const handleToggleModelFavorite = useCallback(async (modelId: string) => {
|
||||
const currentFavorites = favoriteModels;
|
||||
const isFavorite = currentFavorites.includes(modelId);
|
||||
const newFavorites = isFavorite
|
||||
? currentFavorites.filter((m) => m !== modelId)
|
||||
: [modelId, ...currentFavorites];
|
||||
|
||||
setFavoriteModels(newFavorites);
|
||||
|
||||
try {
|
||||
await updateGlobalSettings({ favoriteProviders, favoriteModels: newFavorites });
|
||||
} catch {
|
||||
setFavoriteModels(currentFavorites);
|
||||
}
|
||||
}, [favoriteModels, favoriteProviders]);
|
||||
|
||||
const availableDeps = tasks
|
||||
.filter((t) => !dependencies.includes(t.id))
|
||||
@@ -501,6 +519,8 @@ export function TaskForm({
|
||||
disabled={disabled || presetMode === "preset"}
|
||||
favoriteProviders={favoriteProviders}
|
||||
onToggleFavorite={handleToggleFavorite}
|
||||
favoriteModels={favoriteModels}
|
||||
onToggleModelFavorite={handleToggleModelFavorite}
|
||||
/>
|
||||
</div>
|
||||
<div className="model-select-row">
|
||||
@@ -518,6 +538,8 @@ export function TaskForm({
|
||||
disabled={disabled || presetMode === "preset"}
|
||||
favoriteProviders={favoriteProviders}
|
||||
onToggleFavorite={handleToggleFavorite}
|
||||
favoriteModels={favoriteModels}
|
||||
onToggleModelFavorite={handleToggleModelFavorite}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -35,6 +35,8 @@ vi.mock("../ModelSelectionModal", () => ({
|
||||
modelsLoading,
|
||||
modelsError,
|
||||
onRetry,
|
||||
favoriteModels,
|
||||
onToggleModelFavorite,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
@@ -46,6 +48,8 @@ vi.mock("../ModelSelectionModal", () => ({
|
||||
modelsLoading: boolean;
|
||||
modelsError: string | null;
|
||||
onRetry: () => void;
|
||||
favoriteModels?: string[];
|
||||
onToggleModelFavorite?: (modelId: string) => void;
|
||||
}) => {
|
||||
if (!isOpen) return null;
|
||||
return (
|
||||
@@ -54,6 +58,8 @@ vi.mock("../ModelSelectionModal", () => ({
|
||||
<div data-testid="modal-props-validator-value">{validatorValue}</div>
|
||||
<div data-testid="modal-props-loading">{modelsLoading ? "loading" : "not-loading"}</div>
|
||||
<div data-testid="modal-props-error">{modelsError || "no-error"}</div>
|
||||
<div data-testid="modal-props-favorite-models">{JSON.stringify(favoriteModels ?? [])}</div>
|
||||
<div data-testid="modal-props-has-toggle-model-favorite">{onToggleModelFavorite ? "yes" : "no"}</div>
|
||||
<button data-testid="modal-close" onClick={onClose}>Close</button>
|
||||
<button data-testid="modal-select-executor" onClick={() => onExecutorChange("anthropic/claude-sonnet-4-5")}>Select Executor</button>
|
||||
<button data-testid="modal-select-validator" onClick={() => onValidatorChange("openai/gpt-4o")}>Select Validator</button>
|
||||
@@ -245,6 +251,14 @@ describe("InlineCreateCard model selector", () => {
|
||||
expect(screen.getByTestId("model-selection-modal")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("passes favoriteModels and onToggleModelFavorite to ModelSelectionModal", () => {
|
||||
renderCard();
|
||||
openModelModal();
|
||||
|
||||
expect(screen.getByTestId("modal-props-favorite-models").textContent).toBe("[]");
|
||||
expect(screen.getByTestId("modal-props-has-toggle-model-favorite").textContent).toBe("yes");
|
||||
});
|
||||
|
||||
it("closes the model modal via the close button", () => {
|
||||
renderCard();
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { Task, TaskDetail } from "@fusion/core";
|
||||
|
||||
// Mock the API
|
||||
vi.mock("../../api", () => ({
|
||||
fetchModels: vi.fn().mockResolvedValue({ models: [], favoriteProviders: [] }),
|
||||
fetchModels: vi.fn().mockResolvedValue({ models: [], favoriteProviders: [], favoriteModels: [] }),
|
||||
fetchTaskDetail: vi.fn(),
|
||||
batchUpdateTaskModels: vi.fn(),
|
||||
}));
|
||||
@@ -1838,6 +1838,35 @@ describe("ListView - Bulk Selection", () => {
|
||||
expect(screen.getByText("2 selected")).toBeDefined();
|
||||
});
|
||||
|
||||
it("accepts favoriteProviders and favoriteModels props", () => {
|
||||
const availableModels = [
|
||||
{ provider: "openai", id: "gpt-4o", name: "GPT-4o", reasoning: false, contextWindow: 128000 },
|
||||
];
|
||||
const tasks = [createMockTask({ id: "FN-001" })];
|
||||
const onToggleFavorite = vi.fn();
|
||||
const onToggleModelFavorite = vi.fn();
|
||||
|
||||
render(
|
||||
<ListView
|
||||
tasks={tasks}
|
||||
onMoveTask={vi.fn()}
|
||||
onOpenDetail={vi.fn()}
|
||||
addToast={mockAddToast}
|
||||
availableModels={availableModels}
|
||||
favoriteProviders={["openai"]}
|
||||
favoriteModels={["openai/gpt-4o"]}
|
||||
onToggleFavorite={onToggleFavorite}
|
||||
onToggleModelFavorite={onToggleModelFavorite}
|
||||
/>
|
||||
);
|
||||
|
||||
// Select a task to show bulk edit toolbar with dropdowns
|
||||
const checkbox = screen.getByLabelText("Select FN-001");
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
expect(screen.getByText("Bulk Edit Models:")).toBeDefined();
|
||||
});
|
||||
|
||||
it("shows bulk edit toolbar when tasks are selected", () => {
|
||||
const availableModels = [
|
||||
{ provider: "openai", id: "gpt-4o", name: "GPT-4o", reasoning: false, contextWindow: 128000 },
|
||||
|
||||
@@ -64,7 +64,7 @@ vi.mock("../../api", () => ({
|
||||
reasoning: true,
|
||||
contextWindow: 128_000,
|
||||
},
|
||||
], favoriteProviders: [] }),
|
||||
], favoriteProviders: [], favoriteModels: [] }),
|
||||
refineText: vi.fn(),
|
||||
getRefineErrorMessage: vi.fn((err) => err?.message || "Failed to refine text. Please try again."),
|
||||
}));
|
||||
@@ -95,6 +95,8 @@ vi.mock("../ModelSelectionModal", () => ({
|
||||
modelsLoading,
|
||||
modelsError,
|
||||
onRetry,
|
||||
favoriteModels,
|
||||
onToggleModelFavorite,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
@@ -106,6 +108,8 @@ vi.mock("../ModelSelectionModal", () => ({
|
||||
modelsLoading: boolean;
|
||||
modelsError: string | null;
|
||||
onRetry: () => void;
|
||||
favoriteModels?: string[];
|
||||
onToggleModelFavorite?: (modelId: string) => void;
|
||||
}) => {
|
||||
if (!isOpen) return null;
|
||||
return (
|
||||
@@ -115,6 +119,8 @@ vi.mock("../ModelSelectionModal", () => ({
|
||||
<div data-testid="modal-props-validator-value">{validatorValue}</div>
|
||||
<div data-testid="modal-props-loading">{modelsLoading ? "loading" : "not-loading"}</div>
|
||||
<div data-testid="modal-props-error">{modelsError || "no-error"}</div>
|
||||
<div data-testid="modal-props-favorite-models">{JSON.stringify(favoriteModels ?? [])}</div>
|
||||
<div data-testid="modal-props-has-toggle-model-favorite">{onToggleModelFavorite ? "yes" : "no"}</div>
|
||||
<button data-testid="modal-close" onClick={onClose}>
|
||||
Close
|
||||
</button>
|
||||
@@ -594,6 +600,18 @@ describe("QuickEntryBox", () => {
|
||||
expect(screen.getByTestId("modal-props-error").textContent).toBe("no-error");
|
||||
});
|
||||
|
||||
it("passes favoriteModels and onToggleModelFavorite to ModelSelectionModal", () => {
|
||||
renderQuickEntryBox({});
|
||||
expandQuickEntry();
|
||||
const textarea = screen.getByTestId("quick-entry-input");
|
||||
|
||||
fireEvent.change(textarea, { target: { value: "Task with models" } });
|
||||
fireEvent.click(screen.getByTestId("quick-entry-models-button"));
|
||||
|
||||
expect(screen.getByTestId("modal-props-favorite-models").textContent).toBe("[]");
|
||||
expect(screen.getByTestId("modal-props-has-toggle-model-favorite").textContent).toBe("yes");
|
||||
});
|
||||
|
||||
it("selects dependencies and includes them in submit payload", async () => {
|
||||
const { props } = renderQuickEntryBox({});
|
||||
expandQuickEntry();
|
||||
|
||||
@@ -413,6 +413,25 @@ describe("SettingsModal", () => {
|
||||
expect(globalPayload.defaultModelId).toBe("claude-sonnet-4-5");
|
||||
});
|
||||
|
||||
it("stores favoriteModels from fetchModels response", async () => {
|
||||
(fetchModels as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
models: [
|
||||
{ provider: "anthropic", id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5", reasoning: false, contextWindow: 200000 },
|
||||
],
|
||||
favoriteProviders: ["anthropic"],
|
||||
favoriteModels: ["anthropic/claude-sonnet-4-5"],
|
||||
});
|
||||
|
||||
render(<SettingsModal onClose={onClose} addToast={addToast} />);
|
||||
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
|
||||
|
||||
fireEvent.click(screen.getByText("Models"));
|
||||
await waitFor(() => expect(fetchModels).toHaveBeenCalled());
|
||||
|
||||
// No error thrown means favoriteModels was accepted
|
||||
expect(screen.getByLabelText("Default Model")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("saving in Models section updates project settings with planning and validator models", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<SettingsModal onClose={onClose} addToast={addToast} />);
|
||||
|
||||
@@ -14,7 +14,7 @@ vi.mock("../../api", () => ({
|
||||
fetchModels: vi.fn().mockResolvedValue({ models: [
|
||||
{ provider: "anthropic", id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5", reasoning: true, contextWindow: 200000 },
|
||||
{ provider: "openai", id: "gpt-4o", name: "GPT-4o", reasoning: false, contextWindow: 128000 },
|
||||
], favoriteProviders: [] }),
|
||||
], favoriteProviders: [], favoriteModels: [] }),
|
||||
fetchSettings: vi.fn().mockResolvedValue({
|
||||
modelPresets: [],
|
||||
autoSelectModelPreset: false,
|
||||
@@ -116,6 +116,21 @@ describe("TaskForm", () => {
|
||||
expect(screen.getByText(/Model Configuration/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("fetches and stores favoriteModels from fetchModels response", async () => {
|
||||
const { fetchModels } = await import("../../api");
|
||||
vi.mocked(fetchModels).mockResolvedValueOnce({
|
||||
models: [],
|
||||
favoriteProviders: ["anthropic"],
|
||||
favoriteModels: ["anthropic/claude-sonnet-4-5"],
|
||||
});
|
||||
renderTaskForm();
|
||||
// The component fetches models on mount when isActive=true
|
||||
// If no error is thrown, the favoriteModels state is accepted
|
||||
await vi.waitFor(() => {
|
||||
expect(fetchModels).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("renders workflow step checkboxes with browser verification", () => {
|
||||
renderTaskForm();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user