Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

## [Unreleased]

### 修复

- 知识库文本文档编辑抽屉在「编辑」模式下点「保存」无响应(`name`/`format` 字段未挂载时 `validateFields` 缺值导致抛错被吞掉;#592)
- 知识库文本文档编辑内容未改时点「保存」只关闭抽屉,不触发更新与重建索引

## [0.9.32] - 2026-09-06

### 新增
Expand Down
101 changes: 101 additions & 0 deletions dashboard/src/pages/KnowledgeBases/TextDocumentEditorModal.test.tsx
Original file line number Diff line number Diff line change
@@ -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("<TextDocumentEditorModal />", () => {
it("submits edit-mode content without a mounted name field", async () => {
const user = userEvent.setup();
const onSubmit = vi.fn().mockResolvedValue(undefined);

render(
<TextDocumentEditorModal
open
mode="edit"
initialName="高德地图.md"
initialFormat="md"
initialContent={"line1\n"}
onCancel={() => 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(
<TextDocumentEditorModal
open
mode="edit"
initialName="高德地图.md"
initialFormat="md"
initialContent={"same body\n"}
onCancel={onCancel}
onSubmit={onSubmit}
/>,
);

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(
<TextDocumentEditorModal
open
mode="edit"
initialName="高德地图.md"
initialFormat="md"
initialContent={"same body"}
onCancel={onCancel}
onSubmit={onSubmit}
/>,
);

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();
});
});
32 changes: 27 additions & 5 deletions dashboard/src/pages/KnowledgeBases/TextDocumentEditorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Loading