feat(FN-669): complete Step 8 — add and update tests for model favorites
This commit is contained in:
@@ -237,6 +237,7 @@ describe("fetchModels", () => {
|
||||
{ provider: "anthropic", id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5", reasoning: true, contextWindow: 200000 },
|
||||
],
|
||||
favoriteProviders: ["anthropic"],
|
||||
favoriteModels: ["anthropic/claude-sonnet-4-5"],
|
||||
};
|
||||
globalThis.fetch = vi.fn().mockReturnValue(mockFetchResponse(true, response));
|
||||
|
||||
|
||||
@@ -96,4 +96,196 @@ describe("CustomModelDropdown", () => {
|
||||
expect(screen.queryByTestId("model-combobox-portal")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("Model Favorites", () => {
|
||||
it("shows favorited models as pinned rows at the top before provider groups", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
|
||||
render(
|
||||
<CustomModelDropdown
|
||||
label="Executor Model"
|
||||
value=""
|
||||
onChange={onChange}
|
||||
models={MOCK_MODELS}
|
||||
favoriteModels={["anthropic/claude-sonnet-4-5"]}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Executor Model" }));
|
||||
const portal = await screen.findByTestId("model-combobox-portal");
|
||||
|
||||
// The favorited model should appear first (after "Use default" at index 0)
|
||||
const options = within(portal).getAllByRole("option");
|
||||
// Index 0 is "Use default", index 1 should be the favorited model
|
||||
expect(options[1]).toHaveTextContent("Claude Sonnet 4.5");
|
||||
|
||||
// GPT-4o should appear under its provider group, after the favorited model section
|
||||
expect(options[options.length - 1]).toHaveTextContent("GPT-4o");
|
||||
});
|
||||
|
||||
it("shows star buttons on model options when onToggleModelFavorite is provided", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
const onToggleModelFavorite = vi.fn();
|
||||
|
||||
render(
|
||||
<CustomModelDropdown
|
||||
label="Executor Model"
|
||||
value=""
|
||||
onChange={onChange}
|
||||
models={MOCK_MODELS}
|
||||
onToggleModelFavorite={onToggleModelFavorite}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Executor Model" }));
|
||||
const portal = await screen.findByTestId("model-combobox-portal");
|
||||
|
||||
// Find the star buttons (should have 2: one for each model)
|
||||
const starButtons = within(portal).getAllByRole("button", { name: /Add.*to favorites/ });
|
||||
expect(starButtons.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it("calls onToggleModelFavorite when star button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
const onToggleModelFavorite = vi.fn();
|
||||
|
||||
render(
|
||||
<CustomModelDropdown
|
||||
label="Executor Model"
|
||||
value=""
|
||||
onChange={onChange}
|
||||
models={MOCK_MODELS}
|
||||
onToggleModelFavorite={onToggleModelFavorite}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Executor Model" }));
|
||||
const portal = await screen.findByTestId("model-combobox-portal");
|
||||
|
||||
// Click the star button for Claude Sonnet
|
||||
const starButton = within(portal).getByRole("button", { name: "Add Claude Sonnet 4.5 to favorites" });
|
||||
await user.click(starButton);
|
||||
|
||||
expect(onToggleModelFavorite).toHaveBeenCalledWith("anthropic/claude-sonnet-4-5");
|
||||
});
|
||||
|
||||
it("shows filled star for favorited models and outline star for non-favorited", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
const onToggleModelFavorite = vi.fn();
|
||||
|
||||
render(
|
||||
<CustomModelDropdown
|
||||
label="Executor Model"
|
||||
value=""
|
||||
onChange={onChange}
|
||||
models={MOCK_MODELS}
|
||||
favoriteModels={["anthropic/claude-sonnet-4-5"]}
|
||||
onToggleModelFavorite={onToggleModelFavorite}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Executor Model" }));
|
||||
const portal = await screen.findByTestId("model-combobox-portal");
|
||||
|
||||
// Favorited model should have filled star (★)
|
||||
const favoritedStar = within(portal).getByRole("button", { name: /Remove.*to favorites/ });
|
||||
expect(favoritedStar).toHaveTextContent("★");
|
||||
|
||||
// Non-favorited model should have outline star (☆)
|
||||
const outlineStar = within(portal).getByRole("button", { name: "Add GPT-4o to favorites" });
|
||||
expect(outlineStar).toHaveTextContent("☆");
|
||||
});
|
||||
|
||||
it("shows favorited models in the correct order when multiple are favorited", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
|
||||
const modelsWithThree = [
|
||||
...MOCK_MODELS,
|
||||
{ provider: "google", id: "gemini-pro", name: "Gemini Pro", reasoning: false, contextWindow: 100000 },
|
||||
];
|
||||
|
||||
render(
|
||||
<CustomModelDropdown
|
||||
label="Executor Model"
|
||||
value=""
|
||||
onChange={onChange}
|
||||
models={modelsWithThree}
|
||||
favoriteModels={["google/gemini-pro", "anthropic/claude-sonnet-4-5"]}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Executor Model" }));
|
||||
const portal = await screen.findByTestId("model-combobox-portal");
|
||||
|
||||
// Get options after "Use default" (index 0)
|
||||
const options = within(portal).getAllByRole("option");
|
||||
|
||||
// First favorited model (gemini-pro) should be at index 1
|
||||
expect(options[1]).toHaveTextContent("Gemini Pro");
|
||||
|
||||
// Second favorited model (claude-sonnet) should be at index 2
|
||||
expect(options[2]).toHaveTextContent("Claude Sonnet 4.5");
|
||||
});
|
||||
|
||||
it("shows no pinned section when favoriteModels is empty", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
|
||||
render(
|
||||
<CustomModelDropdown
|
||||
label="Executor Model"
|
||||
value=""
|
||||
onChange={onChange}
|
||||
models={MOCK_MODELS}
|
||||
favoriteModels={[]}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Executor Model" }));
|
||||
const portal = await screen.findByTestId("model-combobox-portal");
|
||||
|
||||
// The favorited models pinned section should not exist
|
||||
const pinnedSection = within(portal).queryByTestId(/pinned|favorited/);
|
||||
expect(pinnedSection).not.toBeInTheDocument();
|
||||
|
||||
// First model should appear under its provider group
|
||||
const options = within(portal).getAllByRole("option");
|
||||
expect(options[1]).toHaveTextContent(/anthropic/i);
|
||||
});
|
||||
|
||||
it("filters favorited models correctly when search is active", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
|
||||
render(
|
||||
<CustomModelDropdown
|
||||
label="Executor Model"
|
||||
value=""
|
||||
onChange={onChange}
|
||||
models={MOCK_MODELS}
|
||||
favoriteModels={["anthropic/claude-sonnet-4-5"]}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Executor Model" }));
|
||||
const portal = await screen.findByTestId("model-combobox-portal");
|
||||
|
||||
// Type in search box to filter
|
||||
const searchInput = within(portal).getByPlaceholderText("Filter models…");
|
||||
await user.type(searchInput, "claude");
|
||||
|
||||
// The favorited model that matches should still appear
|
||||
expect(within(portal).getByText("Claude Sonnet 4.5")).toBeInTheDocument();
|
||||
|
||||
// GPT-4o should not appear since it doesn't match "claude"
|
||||
expect(within(portal).queryByText("GPT-4o")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -43,6 +43,7 @@ const MOCK_MODELS = [
|
||||
const MOCK_MODELS_RESPONSE = {
|
||||
models: MOCK_MODELS,
|
||||
favoriteProviders: [],
|
||||
favoriteModels: [],
|
||||
};
|
||||
|
||||
describe("ModelSelectorTab", () => {
|
||||
|
||||
Reference in New Issue
Block a user