248 lines
7.0 KiB
TypeScript
248 lines
7.0 KiB
TypeScript
// @vitest-environment jsdom
|
|
import React from "react";
|
|
import { act } from "react";
|
|
import { createRoot } from "react-dom/client";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("next-intl", () => ({
|
|
useTranslations: () => (key: string, values?: Record<string, unknown>) => {
|
|
if (values) return `${key}:${JSON.stringify(values)}`;
|
|
return key;
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/shared/components", () => ({
|
|
Card: ({ children }: { children: React.ReactNode }) =>
|
|
React.createElement("div", { className: "card" }, children),
|
|
Button: ({
|
|
children,
|
|
onClick,
|
|
disabled,
|
|
loading,
|
|
"data-testid": testId,
|
|
variant,
|
|
size,
|
|
}: {
|
|
children?: React.ReactNode;
|
|
onClick?: () => void;
|
|
disabled?: boolean;
|
|
loading?: boolean;
|
|
"data-testid"?: string;
|
|
variant?: string;
|
|
size?: string;
|
|
}) =>
|
|
React.createElement(
|
|
"button",
|
|
{ onClick, disabled: disabled || loading, "data-testid": testId, "data-variant": variant },
|
|
children,
|
|
),
|
|
}));
|
|
|
|
// Mock the hooks directly to avoid swr dependency resolution issues
|
|
vi.mock(
|
|
"../../../src/app/(dashboard)/dashboard/memory/hooks/useEngineStatus",
|
|
() => ({
|
|
useEngineStatus: () => ({
|
|
status: {
|
|
keyword: { available: true, backend: "FTS5" },
|
|
embedding: {
|
|
source: "remote",
|
|
model: "openai/text-embedding-3-small",
|
|
dimensions: 1536,
|
|
available: true,
|
|
reason: "provider openai with key configured",
|
|
cacheStats: { hits: 0, misses: 0, size: 0 },
|
|
},
|
|
vectorStore: {
|
|
backend: "sqlite-vec",
|
|
available: true,
|
|
rowCount: 10,
|
|
needsReindex: 0,
|
|
reason: "sqlite-vec loaded",
|
|
},
|
|
qdrant: { enabled: false, healthy: null, latencyMs: null, error: null },
|
|
rerank: {
|
|
enabled: false,
|
|
provider: null,
|
|
model: null,
|
|
available: false,
|
|
reason: "rerank disabled",
|
|
},
|
|
},
|
|
isLoading: false,
|
|
isError: false,
|
|
mutate: vi.fn(),
|
|
}),
|
|
}),
|
|
);
|
|
|
|
const mockSave = vi.fn().mockResolvedValue(true);
|
|
|
|
vi.mock(
|
|
"../../../src/app/(dashboard)/dashboard/memory/hooks/useMemorySettings",
|
|
() => ({
|
|
useMemorySettings: () => ({
|
|
settings: {
|
|
enabled: true,
|
|
maxTokens: 2000,
|
|
retentionDays: 30,
|
|
strategy: "hybrid",
|
|
skillsEnabled: false,
|
|
embeddingSource: "auto",
|
|
embeddingProviderModel: null,
|
|
transformersEnabled: false,
|
|
staticEnabled: false,
|
|
rerankEnabled: false,
|
|
rerankProviderModel: null,
|
|
vectorStore: "auto",
|
|
},
|
|
isLoading: false,
|
|
isError: false,
|
|
mutate: vi.fn(),
|
|
save: mockSave,
|
|
}),
|
|
}),
|
|
);
|
|
|
|
vi.mock(
|
|
"../../../src/app/(dashboard)/dashboard/memory/components/MemoryEngineStatus",
|
|
() => ({
|
|
default: ({ status }: { status: { embedding: { available: boolean } } }) =>
|
|
React.createElement(
|
|
"div",
|
|
{ "data-testid": "engine-status-panel" },
|
|
status.embedding.available ? "embedding:available" : "embedding:unavailable",
|
|
),
|
|
}),
|
|
);
|
|
|
|
vi.mock(
|
|
"../../../src/app/(dashboard)/dashboard/memory/components/EmbeddingSourceSelector",
|
|
() => ({
|
|
default: ({
|
|
onSave,
|
|
}: {
|
|
settings: unknown;
|
|
providers: unknown[];
|
|
onSave: (u: unknown) => Promise<boolean>;
|
|
saving?: boolean;
|
|
}) =>
|
|
React.createElement(
|
|
"div",
|
|
{ "data-testid": "embedding-selector" },
|
|
React.createElement(
|
|
"button",
|
|
{
|
|
"data-testid": "toggle-transformers-btn",
|
|
onClick: () => onSave({ transformersEnabled: true }),
|
|
},
|
|
"toggle-transformers",
|
|
),
|
|
),
|
|
}),
|
|
);
|
|
|
|
vi.mock(
|
|
"../../../src/app/(dashboard)/dashboard/memory/components/QdrantConfigCard",
|
|
() => ({
|
|
default: () => React.createElement("div", { "data-testid": "qdrant-config-card" }, "QdrantCard"),
|
|
}),
|
|
);
|
|
|
|
vi.mock(
|
|
"../../../src/app/(dashboard)/dashboard/memory/components/RerankConfigCard",
|
|
() => ({
|
|
default: () => React.createElement("div", { "data-testid": "rerank-config-card" }, "RerankCard"),
|
|
}),
|
|
);
|
|
|
|
const cleanupCallbacks: Array<() => void> = [];
|
|
|
|
function makeContainer(): HTMLElement {
|
|
const container = document.createElement("div");
|
|
document.body.appendChild(container);
|
|
cleanupCallbacks.push(() => container.remove());
|
|
return container;
|
|
}
|
|
|
|
describe("EngineTab", () => {
|
|
beforeEach(() => {
|
|
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT =
|
|
true;
|
|
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ providers: [] }),
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
while (cleanupCallbacks.length > 0) cleanupCallbacks.pop()?.();
|
|
document.body.innerHTML = "";
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("renders the engine status panel", async () => {
|
|
const { default: EngineTab } = await import(
|
|
"../../../src/app/(dashboard)/dashboard/memory/components/tabs/EngineTab"
|
|
);
|
|
const container = makeContainer();
|
|
const root = createRoot(container);
|
|
await act(async () => {
|
|
root.render(<EngineTab />);
|
|
});
|
|
expect(container.querySelector("[data-testid='engine-status-panel']")).toBeTruthy();
|
|
expect(container.textContent).toContain("embedding:available");
|
|
});
|
|
|
|
it("renders QdrantConfigCard and RerankConfigCard", async () => {
|
|
const { default: EngineTab } = await import(
|
|
"../../../src/app/(dashboard)/dashboard/memory/components/tabs/EngineTab"
|
|
);
|
|
const container = makeContainer();
|
|
const root = createRoot(container);
|
|
await act(async () => {
|
|
root.render(<EngineTab />);
|
|
});
|
|
expect(container.querySelector("[data-testid='qdrant-config-card']")).toBeTruthy();
|
|
expect(container.querySelector("[data-testid='rerank-config-card']")).toBeTruthy();
|
|
});
|
|
|
|
it("renders Reindex Now button", async () => {
|
|
const { default: EngineTab } = await import(
|
|
"../../../src/app/(dashboard)/dashboard/memory/components/tabs/EngineTab"
|
|
);
|
|
const container = makeContainer();
|
|
const root = createRoot(container);
|
|
await act(async () => {
|
|
root.render(<EngineTab />);
|
|
});
|
|
expect(container.querySelector("[data-testid='reindex-now-button']")).toBeTruthy();
|
|
});
|
|
|
|
it("calls save() when EmbeddingSourceSelector calls onSave", async () => {
|
|
mockSave.mockClear();
|
|
|
|
const { default: EngineTab } = await import(
|
|
"../../../src/app/(dashboard)/dashboard/memory/components/tabs/EngineTab"
|
|
);
|
|
const container = makeContainer();
|
|
const root = createRoot(container);
|
|
await act(async () => {
|
|
root.render(<EngineTab />);
|
|
});
|
|
|
|
const toggleBtn = container.querySelector(
|
|
"[data-testid='toggle-transformers-btn']",
|
|
) as HTMLButtonElement | null;
|
|
expect(toggleBtn).toBeTruthy();
|
|
await act(async () => {
|
|
toggleBtn?.click();
|
|
});
|
|
await act(async () => {
|
|
await new Promise((r) => setTimeout(r, 50));
|
|
});
|
|
|
|
expect(mockSave).toHaveBeenCalledWith({ transformersEnabled: true });
|
|
});
|
|
});
|