mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-20 19:46:16 +00:00
* feat(knowledge): integrate RAGFlow retrieval and management * test(knowledge): cover merged listing tool * feat(knowledge): add per-message retrieval scope * chore(docs): remove unrelated document * docs(knowledge): add interaction screenshots * feat(knowledge): simplify scope selector trigger * docs(knowledge): refresh selector screenshot * feat(knowledge): defer standalone management * docs(knowledge): show chat-only scope UI * fix(knowledge): honor scope on clarification replies * fix(knowledge): harden scoped replay validation * docs(knowledge): clarify replay scope precedence * fix(knowledge): keep provider settings on tools * fix(config): preserve tools-only knowledge settings * fix(knowledge): submit custom assistant identity * refactor(knowledge): trim PR scope changes * fix(knowledge): sanitize document scope display * feat(knowledge): enable scope selection in main chat * fix(knowledge): emphasize active scope icon without button frame * fix(knowledge): close context scrubbing and refresh e2e checks * fix(knowledge): preserve idempotent canonical retries * fix(knowledge): accept promptless conversation runs * style(knowledge): format backend regression tests * chore(knowledge): trim PR scope and fix frontend format * fix(knowledge): remove shared-scope notice * fix(knowledge): remove scope persistence notice * docs(knowledge): include main chat in catalog scope * fix(knowledge): preserve scope recovery and upgrades * fix(config): preserve LightRAG knowledge upgrades --------- Co-authored-by: foreleven <for-eleven@hotmail.com>
122 lines
3.7 KiB
TypeScript
122 lines
3.7 KiB
TypeScript
import { afterEach, describe, expect, it, rs } from "@rstest/core";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import {
|
|
act,
|
|
cleanup,
|
|
fireEvent,
|
|
render,
|
|
screen,
|
|
waitFor,
|
|
} from "@testing-library/react";
|
|
import type { PropsWithChildren } from "react";
|
|
|
|
import { KnowledgeScopeSelector } from "@/components/workspace/knowledge-scope-selector";
|
|
import { I18nProvider } from "@/core/i18n/context";
|
|
import type { KnowledgeScopeSelection } from "@/core/knowledge";
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
rs.restoreAllMocks();
|
|
});
|
|
|
|
function renderSelector(selection: KnowledgeScopeSelection) {
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: { queries: { retry: false } },
|
|
});
|
|
|
|
function Wrapper({ children }: PropsWithChildren) {
|
|
return (
|
|
<QueryClientProvider client={queryClient}>
|
|
<I18nProvider initialLocale="en-US">{children}</I18nProvider>
|
|
</QueryClientProvider>
|
|
);
|
|
}
|
|
|
|
return render(
|
|
<KnowledgeScopeSelector
|
|
agentName="researcher"
|
|
selection={selection}
|
|
onChange={() => undefined}
|
|
/>,
|
|
{ wrapper: Wrapper },
|
|
);
|
|
}
|
|
|
|
function requestUrl(input: RequestInfo | URL): string {
|
|
if (typeof input === "string") return input;
|
|
if (input instanceof URL) return input.href;
|
|
return input.url;
|
|
}
|
|
|
|
describe("KnowledgeScopeSelector trigger", () => {
|
|
it("renders only the icon and stays highlighted while retrieval is active", () => {
|
|
renderSelector({ mode: "all" });
|
|
|
|
const trigger = screen.getByRole("button", { name: "Knowledge · All" });
|
|
expect(trigger.textContent).toBe("");
|
|
expect(trigger.getAttribute("aria-pressed")).toBe("true");
|
|
expect(trigger.className).toContain("text-foreground");
|
|
expect(trigger.className).not.toContain("bg-primary/10");
|
|
expect(trigger.className).not.toContain("border-primary/20");
|
|
expect(trigger.querySelector("svg")).not.toBeNull();
|
|
});
|
|
|
|
it("returns to the neutral icon state when retrieval is off", () => {
|
|
renderSelector({ mode: "disabled" });
|
|
|
|
const trigger = screen.getByRole("button", { name: "Knowledge · Off" });
|
|
expect(trigger.textContent).toBe("");
|
|
expect(trigger.getAttribute("aria-pressed")).toBe("false");
|
|
expect(trigger.className).not.toContain("bg-primary/10");
|
|
expect(trigger.querySelector("svg")).not.toBeNull();
|
|
});
|
|
|
|
it("does not load documents while an expanded dataset still uses all files", async () => {
|
|
const fetch = rs.spyOn(globalThis, "fetch").mockImplementation((input) => {
|
|
const url = requestUrl(input);
|
|
return Promise.resolve(
|
|
Response.json({
|
|
items: url.includes("/documents?")
|
|
? [{ id: "document-1", name: "Guide", selectable: true }]
|
|
: [{ id: "dataset-1", name: "Policies", selectable: true }],
|
|
page: 1,
|
|
page_size: 100,
|
|
total: 1,
|
|
}),
|
|
);
|
|
});
|
|
renderSelector({
|
|
mode: "selected",
|
|
datasets: [
|
|
{
|
|
id: "dataset-1",
|
|
name: "Policies",
|
|
documents: { mode: "all" },
|
|
},
|
|
],
|
|
});
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "Knowledge · 1 base" }));
|
|
await screen.findByText("Policies");
|
|
fireEvent.click(screen.getByRole("button", { name: "Files" }));
|
|
await act(async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
});
|
|
|
|
expect(
|
|
fetch.mock.calls.some(([input]) =>
|
|
requestUrl(input).includes("/documents?"),
|
|
),
|
|
).toBe(false);
|
|
|
|
fireEvent.click(screen.getByRole("radio", { name: "Selected files" }));
|
|
await waitFor(() => {
|
|
expect(
|
|
fetch.mock.calls.some(([input]) =>
|
|
requestUrl(input).includes("/documents?"),
|
|
),
|
|
).toBe(true);
|
|
});
|
|
});
|
|
});
|