Files
silo-server/web/src/pages/AdminRecommendations.test.tsx
T

202 lines
6.3 KiB
TypeScript

// @vitest-environment jsdom
import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
useAdminServerSettings: vi.fn(),
useAdminSensitiveStatus: vi.fn(),
useRecommendationsStatus: vi.fn(),
checkConnectionMutateAsync: vi.fn(),
updateMutate: vi.fn(),
updateMutateAsync: vi.fn(),
triggerEmbeddingsMutate: vi.fn(),
triggerTasteProfilesMutate: vi.fn(),
triggerCowatchMutate: vi.fn(),
triggerRecommendationsMutate: vi.fn(),
}));
vi.mock("@/hooks/queries/admin/settings", () => ({
useAdminServerSettings: (...args: unknown[]) => mocks.useAdminServerSettings(...args),
useCheckAdminSettingsConnection: () => ({
isPending: false,
mutateAsync: (...args: unknown[]) => mocks.checkConnectionMutateAsync(...args),
}),
useUpdateServerSetting: () => ({
isPending: false,
mutate: (...args: unknown[]) => mocks.updateMutate(...args),
mutateAsync: (...args: unknown[]) => mocks.updateMutateAsync(...args),
}),
useAdminSensitiveStatus: (...args: unknown[]) => mocks.useAdminSensitiveStatus(...args),
}));
vi.mock("@/hooks/queries/admin/recommendations", () => ({
useRecommendationsStatus: (...args: unknown[]) => mocks.useRecommendationsStatus(...args),
useTriggerEmbeddings: () => ({
isPending: false,
mutate: (...args: unknown[]) => mocks.triggerEmbeddingsMutate(...args),
}),
useTriggerTasteProfiles: () => ({
isPending: false,
mutate: (...args: unknown[]) => mocks.triggerTasteProfilesMutate(...args),
}),
useTriggerCowatch: () => ({
isPending: false,
mutate: (...args: unknown[]) => mocks.triggerCowatchMutate(...args),
}),
useTriggerRecommendations: () => ({
isPending: false,
mutate: (...args: unknown[]) => mocks.triggerRecommendationsMutate(...args),
}),
}));
import AdminRecommendations from "./AdminRecommendations";
function findButton(container: HTMLElement, label: string) {
return Array.from(container.querySelectorAll("button")).find((button) =>
button.textContent?.includes(label),
);
}
async function click(element: Element | undefined) {
if (!element) {
throw new Error("element not found");
}
await act(async () => {
element.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
}
async function changeInput(input: HTMLInputElement | null, value: string) {
if (!input) {
throw new Error("input not found");
}
const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");
if (!descriptor?.set) {
throw new Error("input value setter not found");
}
await act(async () => {
descriptor.set?.call(input, value);
input.dispatchEvent(new Event("input", { bubbles: true }));
input.dispatchEvent(new Event("change", { bubbles: true }));
});
}
describe("AdminRecommendations", () => {
let container: HTMLDivElement;
let root: Root;
beforeEach(() => {
(
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true;
container = document.createElement("div");
document.body.appendChild(container);
root = createRoot(container);
mocks.useAdminServerSettings.mockReset();
mocks.useAdminSensitiveStatus.mockReset();
mocks.useRecommendationsStatus.mockReset();
mocks.checkConnectionMutateAsync.mockReset();
mocks.updateMutate.mockReset();
mocks.updateMutateAsync.mockReset();
mocks.triggerEmbeddingsMutate.mockReset();
mocks.triggerTasteProfilesMutate.mockReset();
mocks.triggerCowatchMutate.mockReset();
mocks.triggerRecommendationsMutate.mockReset();
mocks.useAdminServerSettings.mockReturnValue({
data: {
"recommendations.enabled": "true",
"recommendations.embedding_base_url": "http://localhost:9999",
"recommendations.embedding_model": "custom-model",
},
isLoading: false,
});
mocks.useAdminSensitiveStatus.mockReturnValue({
data: { configured: ["recommendations.embedding_auth_token"] },
});
mocks.useRecommendationsStatus.mockReturnValue({ data: undefined });
mocks.checkConnectionMutateAsync.mockResolvedValue({
success: true,
message: "Embedding connection successful.",
});
mocks.updateMutateAsync.mockResolvedValue(undefined);
});
afterEach(async () => {
await act(async () => {
root.unmount();
});
container.remove();
});
async function render() {
await act(async () => {
root.render(<AdminRecommendations />);
});
}
it("applies a provider preset to the embedding settings", async () => {
await render();
await click(findButton(container, "Gemini"));
expect(mocks.updateMutateAsync.mock.calls).toEqual([
[
{
key: "recommendations.embedding_base_url",
value: "https://generativelanguage.googleapis.com",
},
],
[
{
key: "recommendations.embedding_model",
value: "gemini-embedding-001",
},
],
]);
const baseUrlInput = container.querySelector<HTMLInputElement>(
'input[id="recommendations.embedding_base_url"]',
);
const modelInput = container.querySelector<HTMLInputElement>(
'input[id="recommendations.embedding_model"]',
);
expect(baseUrlInput?.value).toBe("https://generativelanguage.googleapis.com");
expect(modelInput?.value).toBe("gemini-embedding-001");
expect(findButton(container, "Gemini")?.getAttribute("aria-pressed")).toBe("true");
});
it("checks the current unsaved embedding draft", async () => {
await render();
const tokenInput = container.querySelector<HTMLInputElement>(
'input[id="recommendations.embedding_auth_token"]',
);
expect(tokenInput).toBeTruthy();
await changeInput(tokenInput, "draft-token");
await click(findButton(container, "Check Connection"));
expect(mocks.checkConnectionMutateAsync).toHaveBeenCalledWith({
kind: "recommendations_embedding",
body: {
values: {
"recommendations.enabled": "true",
"recommendations.embedding_base_url": "http://localhost:9999",
"recommendations.embedding_model": "custom-model",
"recommendations.embedding_auth_token": "draft-token",
},
dirty_keys: ["recommendations.embedding_auth_token"],
},
});
});
});