diff --git a/CHANGELOG.md b/CHANGELOG.md index c2256785..7725bf7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ ## [Unreleased] +### 修复 + +- 知识库文本文档编辑抽屉在「编辑」模式下点「保存」无响应(`name`/`format` 字段未挂载时 `validateFields` 缺值导致抛错被吞掉;#592) +- 知识库文本文档编辑内容未改时点「保存」只关闭抽屉,不触发更新与重建索引 + ## [0.9.32] - 2026-09-06 ### 新增 diff --git a/dashboard/src/pages/KnowledgeBases/TextDocumentEditorModal.test.tsx b/dashboard/src/pages/KnowledgeBases/TextDocumentEditorModal.test.tsx new file mode 100644 index 00000000..c67e674a --- /dev/null +++ b/dashboard/src/pages/KnowledgeBases/TextDocumentEditorModal.test.tsx @@ -0,0 +1,101 @@ +/** + * Edit-mode Save used to call `values.name.trim()` even though the name field + * is not mounted — validateFields() omits it, so the click threw and was + * swallowed (GitHub #592). + */ + +import { describe, it, expect, vi } from "vitest"; +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +import TextDocumentEditorModal from "./TextDocumentEditorModal"; + +describe("", () => { + it("submits edit-mode content without a mounted name field", async () => { + const user = userEvent.setup(); + const onSubmit = vi.fn().mockResolvedValue(undefined); + + render( + undefined} + onSubmit={onSubmit} + />, + ); + + const editor = screen.getByPlaceholderText( + "knowledgeBases.fileContentPlaceholder", + ); + await user.clear(editor); + await user.type(editor, "updated body"); + + await user.click(screen.getByRole("button", { name: "common.save" })); + + await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1)); + expect(onSubmit).toHaveBeenCalledWith({ + name: "高德地图.md", + format: "md", + content: "updated body", + }); + }); + + it("closes without submitting when edit-mode content is unchanged", async () => { + const user = userEvent.setup(); + const onCancel = vi.fn(); + const onSubmit = vi.fn().mockResolvedValue(undefined); + + render( + , + ); + + await user.click(screen.getByRole("button", { name: "common.save" })); + + await waitFor(() => expect(onCancel).toHaveBeenCalledTimes(1)); + expect(onSubmit).not.toHaveBeenCalled(); + }); + + it("submits when edit-mode content changes by a single character", async () => { + const user = userEvent.setup(); + const onCancel = vi.fn(); + const onSubmit = vi.fn().mockResolvedValue(undefined); + + render( + , + ); + + const editor = screen.getByPlaceholderText( + "knowledgeBases.fileContentPlaceholder", + ); + await user.type(editor, "!"); + + await user.click(screen.getByRole("button", { name: "common.save" })); + + await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1)); + expect(onSubmit).toHaveBeenCalledWith({ + name: "高德地图.md", + format: "md", + content: "same body!", + }); + expect(onCancel).not.toHaveBeenCalled(); + }); +}); diff --git a/dashboard/src/pages/KnowledgeBases/TextDocumentEditorModal.tsx b/dashboard/src/pages/KnowledgeBases/TextDocumentEditorModal.tsx index 4ca4ef31..af745204 100644 --- a/dashboard/src/pages/KnowledgeBases/TextDocumentEditorModal.tsx +++ b/dashboard/src/pages/KnowledgeBases/TextDocumentEditorModal.tsx @@ -79,14 +79,36 @@ export default function TextDocumentEditorModal({ const handleOk = async () => { try { const values = await form.validateFields(); + const name = String(values.name ?? initialName ?? "").trim(); + const format = values.format ?? initialFormat; + const nextContent = values.content ?? ""; + + // Edit mode: unchanged content → just close; skip API / reindex. + if (mode === "edit" && nextContent === initialContent) { + onCancel(); + return; + } + setSubmitting(true); await onSubmit({ - name: values.name.trim(), - format: values.format, - content: values.content ?? "", + // Edit mode does not mount name/format fields, so validateFields() may + // omit them — fall back to the props used to open the drawer. + name, + format, + content: nextContent, }); - } catch { - // validation errors stay in the form + } catch (error) { + // Ant Design validation rejects with `{ errorFields }`; keep those silent. + // Real submit failures are handled inside `onSubmit` (toast) and must not + // be mistaken for a no-op click. + if ( + error && + typeof error === "object" && + "errorFields" in error && + Array.isArray((error as { errorFields?: unknown }).errorFields) + ) { + return; + } } finally { setSubmitting(false); }