349 lines
11 KiB
TypeScript
349 lines
11 KiB
TypeScript
import { flushPromises, mount } from '@vue/test-utils';
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import DocumentTable from './DocumentTable.vue';
|
|
|
|
const sseMocks = vi.hoisted(() => ({
|
|
abort: vi.fn(),
|
|
post: vi.fn().mockResolvedValue(undefined),
|
|
}));
|
|
const tableState = vi.hoisted(() => ({ rows: [] as any[] }));
|
|
|
|
vi.mock('element-plus', async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import('element-plus')>();
|
|
const { defineComponent, h } = await import('vue');
|
|
const ElTable = defineComponent({
|
|
name: 'ElTable',
|
|
props: ['data'],
|
|
emits: ['selection-change'],
|
|
setup(props, { expose, slots }) {
|
|
expose({
|
|
clearSelection: vi.fn(),
|
|
toggleRowSelection: vi.fn(),
|
|
});
|
|
return () => {
|
|
tableState.rows = props.data || [];
|
|
return h('div', { class: 'table-stub' }, slots.default?.());
|
|
};
|
|
},
|
|
});
|
|
const ElTableColumn = defineComponent({
|
|
name: 'ElTableColumn',
|
|
props: ['label', 'reserveSelection', 'selectable', 'type'],
|
|
setup(_props, { slots }) {
|
|
return () =>
|
|
h(
|
|
'div',
|
|
{ class: 'table-column-stub' },
|
|
tableState.rows.map((row) => slots.default?.({ row })),
|
|
);
|
|
},
|
|
});
|
|
return { ...actual, ElTable, ElTableColumn };
|
|
});
|
|
|
|
vi.mock('#/api/request', () => ({
|
|
api: {},
|
|
SseClient: class {
|
|
abort = sseMocks.abort;
|
|
post = sseMocks.post;
|
|
},
|
|
}));
|
|
|
|
vi.mock('@easyflow/locales', () => ({
|
|
$t: (key: string, params?: Record<string, number>) =>
|
|
params ? `${key}:${JSON.stringify(params)}` : key,
|
|
}));
|
|
|
|
const rows = [
|
|
{ id: '1', processStatus: 'COMPLETED', title: '一.docx' },
|
|
{ id: '2', processStatus: 'PARSING', title: '处理中.docx' },
|
|
{ id: '3', processStatus: 'COMPLETED', title: '三.pdf' },
|
|
{ id: '4', processStatus: 'PARSE_FAILED', title: '四.xlsx' },
|
|
{ id: '5', processStatus: 'COMPLETED', title: '五.docx' },
|
|
{ id: '6', processStatus: 'COMPLETED', title: '六.pdf' },
|
|
{ id: '7', processStatus: 'COMPLETED', title: '七.xlsx' },
|
|
];
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('documentTable', () => {
|
|
it('处理中行不可选,批量删除使用最多三个并发并汇总部分失败', async () => {
|
|
let active = 0;
|
|
let maxActive = 0;
|
|
const post = vi.fn().mockImplementation(async (_url: string, body: any) => {
|
|
active += 1;
|
|
maxActive = Math.max(maxActive, active);
|
|
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
active -= 1;
|
|
return body.id === '4' || body.id === '6'
|
|
? { data: false, errorCode: 0, message: '失败' }
|
|
: { data: true, errorCode: 0 };
|
|
});
|
|
const get = vi.fn().mockResolvedValue({
|
|
data: { records: rows, totalRow: rows.length },
|
|
});
|
|
vi.spyOn(ElMessageBox, 'confirm').mockResolvedValue('confirm' as never);
|
|
|
|
const wrapper = mount(DocumentTable, {
|
|
global: { directives: { loading: {} } },
|
|
props: {
|
|
knowledgeId: '10',
|
|
requestClient: { download: vi.fn(), get, post },
|
|
},
|
|
});
|
|
await flushPromises();
|
|
const warning = vi.spyOn(ElMessage, 'warning');
|
|
|
|
const table = wrapper.findComponent({ name: 'ElTable' });
|
|
const selectionColumn = table
|
|
.findAllComponents({ name: 'ElTableColumn' })
|
|
.find((column) => column.props('type') === 'selection');
|
|
expect(selectionColumn?.props('selectable')(rows[0])).toBe(true);
|
|
expect(selectionColumn?.props('selectable')(rows[1])).toBe(false);
|
|
expect(selectionColumn?.props('reserveSelection')).toBe(true);
|
|
|
|
table.vm.$emit('selection-change', [
|
|
rows[0],
|
|
rows[2],
|
|
rows[3],
|
|
rows[4],
|
|
rows[5],
|
|
rows[6],
|
|
]);
|
|
await wrapper.vm.$nextTick();
|
|
const deleteButton = wrapper
|
|
.findAll('.batch-toolbar button')
|
|
.find((button) =>
|
|
button.text().includes('documentCollection.batchDelete'),
|
|
);
|
|
if (!deleteButton) throw new Error('未找到批量删除按钮');
|
|
await deleteButton.trigger('click');
|
|
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
await flushPromises();
|
|
|
|
expect(post).toHaveBeenCalledTimes(6);
|
|
expect(maxActive).toBe(3);
|
|
expect(maxActive).toBeLessThanOrEqual(3);
|
|
expect(warning).toHaveBeenCalledWith(
|
|
'documentCollection.batchDeletePartial:{"failed":2,"success":4}',
|
|
);
|
|
expect(wrapper.find('.batch-toolbar').text()).toContain(
|
|
'documentCollection.selectedCount:{"count":2}',
|
|
);
|
|
wrapper.unmount();
|
|
});
|
|
|
|
it('批量删除确认期间阻止重复确认和重复请求', async () => {
|
|
let resolveConfirm!: (value: unknown) => void;
|
|
const confirm = vi.spyOn(ElMessageBox, 'confirm').mockReturnValue(
|
|
new Promise((resolve) => {
|
|
resolveConfirm = resolve;
|
|
}) as never,
|
|
);
|
|
const post = vi.fn().mockResolvedValue({ data: true, errorCode: 0 });
|
|
const get = vi.fn().mockResolvedValue({
|
|
data: { records: rows, totalRow: rows.length },
|
|
});
|
|
const wrapper = mount(DocumentTable, {
|
|
global: { directives: { loading: {} } },
|
|
props: {
|
|
knowledgeId: '10',
|
|
requestClient: { download: vi.fn(), get, post },
|
|
},
|
|
});
|
|
await flushPromises();
|
|
|
|
wrapper
|
|
.findComponent({ name: 'ElTable' })
|
|
.vm.$emit('selection-change', [rows[0], rows[2]]);
|
|
await wrapper.vm.$nextTick();
|
|
const deleteButton = wrapper
|
|
.findAll('.batch-toolbar button')
|
|
.find((button) =>
|
|
button.text().includes('documentCollection.batchDelete'),
|
|
);
|
|
if (!deleteButton) throw new Error('未找到批量删除按钮');
|
|
await deleteButton.trigger('click');
|
|
await deleteButton.trigger('click');
|
|
|
|
expect(confirm).toHaveBeenCalledTimes(1);
|
|
expect(post).not.toHaveBeenCalled();
|
|
|
|
resolveConfirm('confirm');
|
|
await flushPromises();
|
|
expect(post).toHaveBeenCalledTimes(2);
|
|
wrapper.unmount();
|
|
});
|
|
|
|
it('搜索和显式重载会清空当前页选择', async () => {
|
|
const get = vi.fn().mockResolvedValue({
|
|
data: { records: rows, totalRow: rows.length },
|
|
});
|
|
const wrapper = mount(DocumentTable, {
|
|
global: { directives: { loading: {} } },
|
|
props: {
|
|
knowledgeId: '10',
|
|
requestClient: { download: vi.fn(), get, post: vi.fn() },
|
|
},
|
|
});
|
|
await flushPromises();
|
|
|
|
const table = wrapper.findComponent({ name: 'ElTable' });
|
|
table.vm.$emit('selection-change', [rows[0]]);
|
|
await wrapper.vm.$nextTick();
|
|
expect(wrapper.find('.batch-toolbar').exists()).toBe(true);
|
|
|
|
wrapper.vm.search('关键字');
|
|
await wrapper.vm.$nextTick();
|
|
expect(wrapper.find('.batch-toolbar').exists()).toBe(false);
|
|
|
|
table.vm.$emit('selection-change', [rows[2]]);
|
|
await wrapper.vm.$nextTick();
|
|
wrapper.vm.reload();
|
|
await wrapper.vm.$nextTick();
|
|
expect(wrapper.find('.batch-toolbar').exists()).toBe(false);
|
|
wrapper.unmount();
|
|
});
|
|
|
|
it('后台状态更新只移除转为处理中的已选文档', async () => {
|
|
const get = vi.fn().mockResolvedValue({
|
|
data: { records: rows, totalRow: rows.length },
|
|
});
|
|
const wrapper = mount(DocumentTable, {
|
|
global: { directives: { loading: {} } },
|
|
props: {
|
|
knowledgeId: '10',
|
|
requestClient: { download: vi.fn(), get, post: vi.fn() },
|
|
},
|
|
});
|
|
await flushPromises();
|
|
|
|
const table = wrapper.findComponent({ name: 'ElTable' });
|
|
table.vm.$emit('selection-change', [rows[0], rows[2]]);
|
|
await wrapper.vm.$nextTick();
|
|
const streamOptions = sseMocks.post.mock.calls.at(-1)?.[2];
|
|
streamOptions?.onMessage?.({
|
|
data: JSON.stringify({
|
|
documentId: String(rows[1]?.id || ''),
|
|
knowledgeId: '10',
|
|
processStatus: 'PARSING',
|
|
type: 'document-status',
|
|
}),
|
|
event: 'document-status',
|
|
});
|
|
await wrapper.vm.$nextTick();
|
|
expect(wrapper.find('.batch-toolbar').text()).toContain(
|
|
'documentCollection.selectedCount:{"count":2}',
|
|
);
|
|
|
|
streamOptions?.onMessage?.({
|
|
data: JSON.stringify({
|
|
documentId: String(rows[2]?.id || ''),
|
|
knowledgeId: '10',
|
|
processStatus: 'INDEXING',
|
|
type: 'document-status',
|
|
}),
|
|
event: 'document-status',
|
|
});
|
|
await wrapper.vm.$nextTick();
|
|
expect(wrapper.find('.batch-toolbar').text()).toContain(
|
|
'documentCollection.selectedCount:{"count":1}',
|
|
);
|
|
wrapper.unmount();
|
|
});
|
|
|
|
it('后台静默重载按 ID 保留当前页有效选择', async () => {
|
|
const get = vi.fn().mockResolvedValue({
|
|
data: { records: rows, totalRow: rows.length },
|
|
});
|
|
const wrapper = mount(DocumentTable, {
|
|
global: { directives: { loading: {} } },
|
|
props: {
|
|
knowledgeId: '10',
|
|
requestClient: { download: vi.fn(), get, post: vi.fn() },
|
|
},
|
|
});
|
|
await flushPromises();
|
|
|
|
const table = wrapper.findComponent({ name: 'ElTable' });
|
|
table.vm.$emit('selection-change', [rows[0]]);
|
|
await wrapper.vm.$nextTick();
|
|
const pageData = wrapper.findComponent({ name: 'PageData' });
|
|
await (pageData.vm as any).reload({ silent: true });
|
|
await flushPromises();
|
|
|
|
expect(wrapper.find('.batch-toolbar').text()).toContain(
|
|
'documentCollection.selectedCount:{"count":1}',
|
|
);
|
|
wrapper.unmount();
|
|
});
|
|
|
|
it('单条删除请求期间锁定行操作并阻止重复提交', async () => {
|
|
let resolveDelete!: (value: unknown) => void;
|
|
const post = vi.fn().mockReturnValue(
|
|
new Promise((resolve) => {
|
|
resolveDelete = resolve;
|
|
}),
|
|
);
|
|
const get = vi.fn().mockResolvedValue({
|
|
data: { records: rows, totalRow: rows.length },
|
|
});
|
|
const confirm = vi
|
|
.spyOn(ElMessageBox, 'confirm')
|
|
.mockResolvedValue('confirm' as never);
|
|
const wrapper = mount(DocumentTable, {
|
|
global: { directives: { loading: {} } },
|
|
props: {
|
|
knowledgeId: '10',
|
|
requestClient: { download: vi.fn(), get, post },
|
|
},
|
|
});
|
|
await flushPromises();
|
|
|
|
const deleteButton = wrapper.get('button[aria-label="button.delete"]');
|
|
await deleteButton.trigger('click');
|
|
await deleteButton.trigger('click');
|
|
await flushPromises();
|
|
|
|
expect(confirm).toHaveBeenCalledTimes(1);
|
|
expect(post).toHaveBeenCalledTimes(1);
|
|
expect(
|
|
wrapper.get('button[aria-label="button.delete"]').attributes(),
|
|
).toHaveProperty('disabled');
|
|
|
|
resolveDelete({ data: true, errorCode: 0 });
|
|
await flushPromises();
|
|
wrapper.unmount();
|
|
});
|
|
|
|
it('只读权限不渲染选择列和批量入口', async () => {
|
|
const get = vi.fn().mockResolvedValue({
|
|
data: { records: rows, totalRow: rows.length },
|
|
});
|
|
const wrapper = mount(DocumentTable, {
|
|
global: { directives: { loading: {} } },
|
|
props: {
|
|
knowledgeId: '10',
|
|
permissions: { canDeleteContent: false },
|
|
requestClient: { download: vi.fn(), get, post: vi.fn() },
|
|
},
|
|
});
|
|
await flushPromises();
|
|
|
|
const columns = wrapper
|
|
.findComponent({ name: 'ElTable' })
|
|
.findAllComponents({ name: 'ElTableColumn' });
|
|
expect(columns.some((column) => column.props('type') === 'selection')).toBe(
|
|
false,
|
|
);
|
|
expect(wrapper.find('.batch-toolbar').exists()).toBe(false);
|
|
wrapper.unmount();
|
|
});
|
|
});
|