fix(knowledge): separate creation management and selection
This commit is contained in:
1 parent
a589a864d7
commit
8e0e52f30e
11 files changed
+223
-112
No files matched your search
@@ -19,9 +19,7 @@ function Harness({ initial = [], disabled = false, agentSlug = slug }: { initial
|
||||
return <ConfigurationFields slug={agentSlug} value={value} disabled={disabled} onChange={next => { changes(next); setValue(next); }} />;
|
||||
}
|
||||
function choose(id: string) { fireEvent.change(screen.getByRole('combobox', { name: '选择知识库' }), { target: { value: id } }); }
|
||||
function openUpload() { fireEvent.click(screen.getByText('上传自己的文件')); }
|
||||
async function openOwn(readyText = '上传第一份文档,让智能体用你的资料回答。') {
|
||||
openUpload();
|
||||
await within(screen.getByRole('combobox', { name: '管理的知识库' })).findByRole('option', { name: own.name });
|
||||
fireEvent.change(screen.getByRole('combobox', { name: '管理的知识库' }), { target: { value: own.kb_id } });
|
||||
await screen.findByText(readyText);
|
||||
@@ -47,6 +45,20 @@ it('joins readable IDs to the owned-only catalog and selects multiple libraries
|
||||
const sharedGroup = await screen.findByRole('group', { name: 'MakeLore知识' });
|
||||
expect(within(sharedGroup).getByRole('option', { name: '创作指南' })).toBeInTheDocument();
|
||||
expect(within(screen.getByRole('group', { name: '我的知识库' })).getByRole('option', { name: own.name })).toBeInTheDocument();
|
||||
const creation = screen.getByRole('region', { name: '上传自己的文件', exact: true });
|
||||
const management = screen.getByRole('region', { name: '查看我的知识库', exact: true });
|
||||
const selection = screen.getByRole('region', { name: '选择知识库', exact: true });
|
||||
expect(within(creation).getByRole('button', { name: '新建知识库', exact: true })).toBeVisible();
|
||||
expect(within(creation).queryByRole('combobox', { name: '管理的知识库' })).not.toBeInTheDocument();
|
||||
expect(within(creation).queryByRole('button', { name: '从电脑上传', exact: true })).not.toBeInTheDocument();
|
||||
expect(within(management).getByRole('combobox', { name: '管理的知识库' })).toBeVisible();
|
||||
expect(within(management).getByRole('button', { name: '从电脑上传', exact: true })).toBeDisabled();
|
||||
expect(within(management).queryByRole('button', { name: '新建知识库', exact: true })).not.toBeInTheDocument();
|
||||
expect(within(selection).getByRole('combobox', { name: '选择知识库', exact: true })).toBeVisible();
|
||||
expect(creation.compareDocumentPosition(management) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
expect(management.compareDocumentPosition(selection) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
expect(screen.queryByText('添加到我的知识库', { exact: true })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('选择或新建自己的知识库,再从电脑上传资料。选用会加入右侧配置,保存并应用后生效。', { exact: true })).not.toBeInTheDocument();
|
||||
expect(screen.getByText('可多选,每次选择一个知识库加入配置。')).toBeVisible();
|
||||
choose('shared'); choose(own.kb_id); choose('other-shared');
|
||||
expect(within(screen.getByRole('list', { name: '已选知识库' })).getAllByRole('listitem')).toHaveLength(3);
|
||||
@@ -60,7 +72,7 @@ it('joins readable IDs to the owned-only catalog and selects multiple libraries
|
||||
it('never offers shared libraries to document management or enables their upload controls', async () => {
|
||||
databases = [];
|
||||
render(<Harness initial={['shared']} />);
|
||||
await screen.findByText('MakeLore知识 · 只读'); openUpload();
|
||||
await screen.findByText('MakeLore知识 · 只读');
|
||||
const manager = screen.getByRole('combobox', { name: '管理的知识库' });
|
||||
expect(within(manager).queryByRole('option', { name: '创作指南' })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '从电脑上传', exact: true })).toBeDisabled();
|
||||
@@ -76,7 +88,7 @@ it('allows readable binding after an owned-list failure while withholding docume
|
||||
render(<Harness />);
|
||||
await screen.findByText('我的知识库目录暂不可用,仍可选用可读取的知识;上传文件请刷新重试。');
|
||||
expect(screen.queryByRole('group', { name: 'MakeLore知识' })).not.toBeInTheDocument();
|
||||
choose('shared'); openUpload();
|
||||
choose('shared');
|
||||
expect(screen.getByRole('combobox', { name: '管理的知识库' })).toBeDisabled();
|
||||
expect(screen.getByRole('button', { name: '从电脑上传', exact: true })).toBeDisabled();
|
||||
failed = false;
|
||||
@@ -99,48 +111,92 @@ it('retains missing IDs for removal during catalog failure instead of treating t
|
||||
expect(changes).toHaveBeenLastCalledWith(expect.objectContaining({ knowledges: [] }));
|
||||
});
|
||||
|
||||
it('enforces the 100-library limit and restores selection after removal', async () => {
|
||||
it('limits bindings to 100 libraries without preventing browsing or creation', async () => {
|
||||
const selected = Array.from({ length: 100 }, (_, index) => 'old-' + index);
|
||||
const original = api.call.getMockImplementation()!;
|
||||
const created = { kb_id: 'new-own', name: '新资料' };
|
||||
api.call.mockImplementation((operation, input) => {
|
||||
if (operation === 'createKnowledge') {
|
||||
databases.push(created);
|
||||
catalog.resources.knowledges!.push({ key: created.kb_id, name: created.name });
|
||||
return Promise.resolve(created);
|
||||
}
|
||||
return original(operation, input);
|
||||
});
|
||||
render(<Harness initial={selected} />);
|
||||
await screen.findByRole('group', { name: 'MakeLore知识' });
|
||||
expect(screen.getByRole('combobox', { name: '选择知识库' })).toBeDisabled();
|
||||
openUpload(); expect(screen.getByRole('button', { name: '新建知识库' })).toBeDisabled();
|
||||
await openOwn();
|
||||
expect(screen.getByRole('button', { name: '从电脑上传', exact: true })).toBeEnabled();
|
||||
expect(changes).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '新建知识库' }));
|
||||
fireEvent.change(screen.getByRole('textbox', { name: '知识库名称' }), { target: { value: created.name } });
|
||||
fireEvent.change(screen.getByRole('combobox', { name: '向量模型' }), { target: { value: 'embedding' } });
|
||||
expect(screen.getByRole('button', { name: '创建知识库', exact: true })).toBeEnabled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '创建知识库', exact: true }));
|
||||
await waitFor(() => expect(screen.getByRole('combobox', { name: '管理的知识库' })).toHaveValue(created.kb_id));
|
||||
expect(changes).not.toHaveBeenCalled();
|
||||
expect(screen.getByRole('combobox', { name: '选择知识库' })).toBeDisabled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '移除知识库 old-0' }));
|
||||
choose('shared');
|
||||
await within(screen.getByRole('combobox', { name: '选择知识库' })).findByRole('option', { name: created.name });
|
||||
choose(created.kb_id);
|
||||
expect(changes.mock.calls.at(-1)?.[0].knowledges).toHaveLength(100);
|
||||
expect(changes.mock.calls.at(-1)?.[0].knowledges).toContain('shared');
|
||||
expect(changes.mock.calls.at(-1)?.[0].knowledges).toContain(created.kb_id);
|
||||
});
|
||||
|
||||
it('selects an existing owned library for uploads and cancelling a file picker does not process a file', async () => {
|
||||
it('browses an owned library without binding it and only explicit selection changes the draft', async () => {
|
||||
render(<Harness initial={['shared']} />);
|
||||
await openOwn();
|
||||
expect(changes).toHaveBeenLastCalledWith(expect.objectContaining({ knowledges: ['shared', own.kb_id] }));
|
||||
expect(changes).not.toHaveBeenCalled();
|
||||
expect(screen.queryByRole('button', { name: '移除知识库 ' + own.name })).not.toBeInTheDocument();
|
||||
api.uploadKnowledge.mockResolvedValue(null);
|
||||
fireEvent.click(screen.getByRole('button', { name: '从电脑上传', exact: true }));
|
||||
await waitFor(() => expect(api.uploadKnowledge).toHaveBeenCalledTimes(1));
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '从电脑上传', exact: true })).toBeEnabled());
|
||||
expect(api.call.mock.calls.every(([operation]) => operation !== 'processKnowledge')).toBe(true);
|
||||
expect(screen.getByRole('button', { name: '移除知识库 创作指南' })).toBeVisible();
|
||||
expect(changes).not.toHaveBeenCalled();
|
||||
choose(own.kb_id);
|
||||
expect(changes).toHaveBeenLastCalledWith(expect.objectContaining({ knowledges: ['shared', own.kb_id] }));
|
||||
});
|
||||
|
||||
it('appends a late creation result to the latest bindings after interleaved selection and removal', async () => {
|
||||
it('keeps an upload failure in the management column while the creation form is open', async () => {
|
||||
api.uploadKnowledge.mockRejectedValue(new Error('上传结果未确认'));
|
||||
render(<Harness initial={['shared']} />);
|
||||
await openOwn();
|
||||
const creation = screen.getByRole('region', { name: '上传自己的文件', exact: true });
|
||||
const management = screen.getByRole('region', { name: '查看我的知识库', exact: true });
|
||||
fireEvent.click(within(creation).getByRole('button', { name: '新建知识库', exact: true }));
|
||||
fireEvent.change(within(creation).getByRole('textbox', { name: '知识库名称' }), { target: { value: '还没创建的资料' } });
|
||||
fireEvent.click(within(management).getByRole('button', { name: '从电脑上传', exact: true }));
|
||||
expect(await within(management).findByRole('alert')).toHaveTextContent('上传结果未确认');
|
||||
expect(within(creation).queryByRole('alert')).not.toBeInTheDocument();
|
||||
expect(within(creation).getByRole('textbox', { name: '知识库名称' })).toHaveValue('还没创建的资料');
|
||||
expect(changes).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps a late creation separate from interleaved draft changes until explicitly selected', async () => {
|
||||
catalog.models.push({ id: 'model2', name: '另一个模型' });
|
||||
let resolve!: (value: CloudKnowledge) => void;
|
||||
const original = api.call.getMockImplementation()!;
|
||||
api.call.mockImplementation((operation, input) => operation === 'createKnowledge'
|
||||
? new Promise(value => { resolve = value; }) : original(operation, input));
|
||||
render(<Harness initial={['shared']} />);
|
||||
await screen.findByRole('group', { name: '我的知识库' }); openUpload();
|
||||
await screen.findByRole('group', { name: '我的知识库' });
|
||||
fireEvent.click(screen.getByRole('button', { name: '新建知识库' }));
|
||||
fireEvent.change(screen.getByRole('textbox', { name: '知识库名称' }), { target: { value: '新资料' } });
|
||||
fireEvent.change(screen.getByRole('combobox', { name: '向量模型' }), { target: { value: 'embedding' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '创建并选用' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '创建知识库' }));
|
||||
choose('other-shared'); fireEvent.change(screen.getByRole('combobox', { name: '模型' }), { target: { value: 'model2' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '移除知识库 创作指南' }));
|
||||
const created = { kb_id: 'new-own', name: '新资料' }; databases.push(created);
|
||||
catalog.resources.knowledges!.push({ key: created.kb_id, name: created.name });
|
||||
await act(async () => resolve(created));
|
||||
await screen.findByRole('button', { name: '移除知识库 新资料' });
|
||||
await waitFor(() => expect(screen.getByRole('combobox', { name: '管理的知识库' })).toHaveValue(created.kb_id));
|
||||
expect(screen.queryByRole('button', { name: '移除知识库 新资料' })).not.toBeInTheDocument();
|
||||
expect(changes).toHaveBeenLastCalledWith(expect.objectContaining({ model: 'model2', knowledges: ['other-shared'] }));
|
||||
await within(screen.getByRole('combobox', { name: '选择知识库' })).findByRole('option', { name: created.name });
|
||||
choose(created.kb_id);
|
||||
expect(changes).toHaveBeenLastCalledWith(expect.objectContaining({ model: 'model2', knowledges: ['other-shared', 'new-own'] }));
|
||||
});
|
||||
|
||||
@@ -284,22 +340,24 @@ it('finishes an authorized upload through a failed ownership refresh and does no
|
||||
expect(api.call.mock.calls.filter(([operation]) => operation === 'processKnowledge')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('adds an already authorized creation result while the editor is temporarily disabled by saving', async () => {
|
||||
it('retains an authorized creation for management while saving without altering draft bindings', async () => {
|
||||
let resolve!: (value: CloudKnowledge) => void;
|
||||
const original = api.call.getMockImplementation()!;
|
||||
api.call.mockImplementation((operation, input) => operation === 'createKnowledge'
|
||||
? new Promise(value => { resolve = value; }) : original(operation, input));
|
||||
const view = render(<Harness initial={['shared']} />);
|
||||
await screen.findByRole('group', { name: '我的知识库' }); openUpload();
|
||||
await screen.findByRole('group', { name: '我的知识库' });
|
||||
fireEvent.click(screen.getByRole('button', { name: '新建知识库' }));
|
||||
fireEvent.change(screen.getByRole('textbox', { name: '知识库名称' }), { target: { value: '新资料' } });
|
||||
fireEvent.change(screen.getByRole('combobox', { name: '向量模型' }), { target: { value: 'embedding' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '创建并选用' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '创建知识库' }));
|
||||
view.rerender(<Harness initial={['shared']} disabled />);
|
||||
const created = { kb_id: 'new-own', name: '新资料' }; databases.push(created);
|
||||
catalog.resources.knowledges!.push({ key: created.kb_id, name: created.name });
|
||||
await act(async () => resolve(created));
|
||||
expect(changes).toHaveBeenLastCalledWith(expect.objectContaining({ knowledges: ['shared', 'new-own'] }));
|
||||
await waitFor(() => expect(screen.getByRole('combobox', { name: '管理的知识库' })).toHaveValue(created.kb_id));
|
||||
expect(changes).not.toHaveBeenCalled();
|
||||
expect(screen.queryByRole('button', { name: '移除知识库 新资料' })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('combobox', { name: '选择知识库' })).toBeDisabled();
|
||||
});
|
||||
|
||||
@@ -307,7 +365,7 @@ it('keeps an owned but unusable library manageable without adding an invalid bin
|
||||
const unusable = { kb_id: 'disabled-embedding', name: '旧资料' }; databases.push(unusable);
|
||||
render(<Harness initial={['shared', unusable.kb_id]} />);
|
||||
await screen.findByRole('button', { name: '移除知识库 旧资料' });
|
||||
expect(screen.getByText('已不可用')).toBeVisible(); openUpload();
|
||||
expect(screen.getByText('已不可用')).toBeVisible();
|
||||
const manager = screen.getByRole('combobox', { name: '管理的知识库' });
|
||||
expect(within(manager).getByRole('option', { name: '旧资料(当前暂不可选用)' })).toBeEnabled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '移除知识库 旧资料' }));
|
||||
|
||||
Reference in new issue
Block a user