/* @vitest-environment happy-dom */
|
/* eslint-disable vue/one-component-per-file */
|
|
import type { DmsPermissionResource } from '#/api/x/dms/permission';
|
|
import { flushPromises, shallowMount } from '@vue/test-utils';
|
import { defineComponent, h, unref } from 'vue';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import ResourceExplorer from '../ResourceExplorer.vue';
|
|
const apiMocks = vi.hoisted(() => ({
|
copyDmsFolder: vi.fn(),
|
deleteDmsFolder: vi.fn(),
|
getPermissionResourceTree: vi.fn(),
|
moveDmsFolder: vi.fn(),
|
reorderDmsFolder: vi.fn(),
|
}));
|
const messageMocks = vi.hoisted(() => ({
|
createConfirm: vi.fn(),
|
error: vi.fn(),
|
success: vi.fn(),
|
}));
|
const modalMocks = vi.hoisted(() => ({
|
index: 0,
|
openOperationLog: vi.fn(),
|
openRenameFolder: vi.fn(),
|
}));
|
const previewMocks = vi.hoisted(() => ({ openDmsFilePreview: vi.fn() }));
|
const tableMocks = vi.hoisted(() => ({ options: undefined as any }));
|
|
vi.mock('#/api/x/dms/permission', () => ({
|
copyDmsFolder: apiMocks.copyDmsFolder,
|
deleteDmsFolder: apiMocks.deleteDmsFolder,
|
getPermissionResourceTree: apiMocks.getPermissionResourceTree,
|
moveDmsFolder: apiMocks.moveDmsFolder,
|
reorderDmsFolder: apiMocks.reorderDmsFolder,
|
}));
|
vi.mock('#/utils/jnpf', () => ({ onlineUtils: { sign: vi.fn() } }));
|
vi.mock('@jnpf/hooks', () => ({
|
useMessage: () => ({
|
createConfirm: messageMocks.createConfirm,
|
createMessage: messageMocks,
|
}),
|
}));
|
vi.mock('@jnpf/ui', () => ({ BasicTree: { name: 'BasicTree', template: '<div />' } }));
|
vi.mock('@jnpf/ui/modal', () => ({
|
useModal: () => {
|
const openModal = modalMocks.index++ === 0 ? modalMocks.openRenameFolder : modalMocks.openOperationLog;
|
return [vi.fn(), { openModal }];
|
},
|
}));
|
vi.mock('@jnpf/ui/vxeTable', () => ({
|
BasicVxeTable: { name: 'BasicVxeTable', template: '<div />' },
|
TableAction: { name: 'TableAction', template: '<div />' },
|
useVxeTable: (options: any) => {
|
tableMocks.options = options;
|
return [vi.fn(), {}];
|
},
|
}));
|
vi.mock('@jnpf/utils', async (importOriginal) => ({
|
...(await importOriginal<typeof import('@jnpf/utils')>()),
|
formatToDateTime: (value: string) => value,
|
}));
|
vi.mock('../../shared/dmsFilePreview', () => ({
|
dmsPreviewErrorMessage: () => '文件预览失败',
|
openDmsFilePreview: previewMocks.openDmsFilePreview,
|
}));
|
vi.mock('../../file-center/FileOperationLogModal.vue', () => ({ default: { name: 'FileOperationLogModal', template: '<div />' } }));
|
vi.mock('../RenameFolderModal.vue', () => ({ default: { name: 'RenameFolderModal', template: '<div />' } }));
|
|
const BasicTreeStub = defineComponent({
|
name: 'BasicTree',
|
inheritAttrs: false,
|
props: {
|
beforeRightClick: { default: undefined, type: Function },
|
expandedKeys: { default: () => [], type: Array },
|
selectedKeys: { default: () => [], type: Array },
|
treeData: { default: () => [], type: Array },
|
},
|
setup: () => () => h('div', { class: 'basic-tree' }),
|
});
|
|
const BasicVxeTableStub = defineComponent({
|
name: 'BasicVxeTable',
|
inheritAttrs: false,
|
emits: ['cell-click', 'cell-dblclick', 'register'],
|
setup(_, { emit, slots }) {
|
return () => {
|
const rows = (unref(tableMocks.options?.dataSource) || []) as DmsPermissionResource[];
|
return h(
|
'div',
|
{ class: 'basic-vxe-table' },
|
rows.map((record) =>
|
h(
|
'div',
|
{
|
class: 'detail-table-row',
|
'data-id': record.id,
|
onClick: () => emit('cell-click', { row: record }),
|
onDblclick: () => emit('cell-dblclick', { row: record }),
|
},
|
[slots.entryName?.({ record }), slots.action?.({ record })],
|
),
|
),
|
);
|
};
|
},
|
});
|
|
const TableActionStub = defineComponent({
|
name: 'TableAction',
|
inheritAttrs: false,
|
props: {
|
actions: { default: () => [], type: Array },
|
stopButtonPropagation: Boolean,
|
},
|
setup(props) {
|
return () =>
|
h(
|
'div',
|
{ class: 'table-action' },
|
props.actions.map((action: any) =>
|
h(
|
'button',
|
{
|
onClick: (event: MouseEvent) => {
|
if (props.stopButtonPropagation) event.stopPropagation();
|
action.onClick?.();
|
},
|
type: 'button',
|
},
|
action.label,
|
),
|
),
|
);
|
},
|
});
|
|
const rootFolder: DmsPermissionResource = {
|
createdAt: '2026-09-01T00:00:00Z',
|
id: 'root-folder',
|
name: '质量文件',
|
resourceType: 'FOLDER',
|
revision: 1,
|
sourceId: 'root-folder',
|
sourceType: 'SYSTEM',
|
};
|
const childFolder: DmsPermissionResource = {
|
createdAt: '2026-09-02T00:00:00Z',
|
id: 'child-folder',
|
name: '受控文件',
|
parentId: rootFolder.id,
|
resourceType: 'FOLDER',
|
revision: 1,
|
sourceId: 'child-folder',
|
sourceType: 'MANUAL',
|
};
|
const fileVersion: DmsPermissionResource = {
|
createdAt: '2026-09-03T00:00:00Z',
|
fileName: '检验规程.pdf',
|
id: 'file-version',
|
name: '检验规程.pdf',
|
parentId: rootFolder.id,
|
resourceType: 'FILE_VERSION',
|
sourceId: 'file-version',
|
sourceType: 'UPLOAD',
|
versionNo: 2,
|
};
|
|
async function mountExplorer() {
|
const wrapper = shallowMount(ResourceExplorer, {
|
global: {
|
directives: { loading: {} },
|
stubs: {
|
'a-button': true,
|
'a-button-group': true,
|
'a-tooltip': true,
|
'jnpf-empty': true,
|
'jnpf-input': true,
|
BasicTree: BasicTreeStub,
|
BasicVxeTable: BasicVxeTableStub,
|
FileOperationLogModal: true,
|
RenameFolderModal: true,
|
TableAction: TableActionStub,
|
},
|
},
|
});
|
await flushPromises();
|
return wrapper;
|
}
|
|
beforeEach(() => {
|
vi.clearAllMocks();
|
modalMocks.index = 0;
|
tableMocks.options = undefined;
|
apiMocks.getPermissionResourceTree.mockImplementation(({ parentId }: { parentId?: string }) => {
|
if (!parentId) return Promise.resolve({ data: [rootFolder] });
|
if (parentId === rootFolder.id) return Promise.resolve({ data: [childFolder, fileVersion] });
|
return Promise.resolve({ data: [] });
|
});
|
previewMocks.openDmsFilePreview.mockResolvedValue(undefined);
|
});
|
|
afterEach(() => {
|
vi.useRealTimers();
|
});
|
|
describe('permission resource explorer standard components', () => {
|
it('returns the complete folder context menu through BasicTree', async () => {
|
const wrapper = await mountExplorer();
|
const tree = wrapper.getComponent(BasicTreeStub);
|
const beforeRightClick = tree.props('beforeRightClick') as (node: { key: string }, event: MouseEvent) => Array<{ disabled?: boolean; label: string }>;
|
|
const menu = beforeRightClick({ key: rootFolder.id }, new MouseEvent('contextmenu'));
|
|
expect(menu.map((item) => item.label)).toEqual(['新增', '修改', '删除', '上移', '下移', '剪切', '复制', '粘贴', '刷新']);
|
expect(menu.find((item) => item.label === '删除')?.disabled).toBe(true);
|
expect(menu.find((item) => item.label === '剪切')?.disabled).toBe(true);
|
wrapper.unmount();
|
});
|
|
it('keeps delayed cell selection and double-click preview behavior', async () => {
|
vi.useFakeTimers();
|
const wrapper = await mountExplorer();
|
const row = wrapper.get('[data-id="file-version"]');
|
|
await row.trigger('click');
|
vi.advanceTimersByTime(250);
|
await flushPromises();
|
expect(wrapper.emitted('select')?.at(-1)?.[0]).toMatchObject({ id: fileVersion.id });
|
|
await row.trigger('dblclick');
|
await flushPromises();
|
expect(previewMocks.openDmsFilePreview).toHaveBeenCalledWith({
|
fileName: fileVersion.fileName,
|
resourceId: fileVersion.id,
|
resourceType: 'FILE_VERSION',
|
});
|
wrapper.unmount();
|
});
|
|
it('keeps operation clicks and double-clicks from opening the resource', async () => {
|
vi.useFakeTimers();
|
const wrapper = await mountExplorer();
|
const table = wrapper.getComponent(BasicVxeTableStub);
|
|
await wrapper.get('[data-id="file-version"] .table-action button').trigger('click');
|
vi.runAllTimers();
|
await flushPromises();
|
|
expect(modalMocks.openOperationLog).toHaveBeenCalledWith(true, { item: fileVersion });
|
expect(previewMocks.openDmsFilePreview).not.toHaveBeenCalled();
|
expect(wrapper.emitted('select')?.at(-1)?.[0]).toMatchObject({ id: fileVersion.id });
|
|
table.vm.$emit('cell-dblclick', { column: { field: 'action' }, row: fileVersion });
|
table.vm.$emit('cell-dblclick', { column: { property: 'action' }, row: fileVersion });
|
await wrapper.get('[data-id="file-version"] .entry-action').trigger('dblclick');
|
await flushPromises();
|
|
expect(previewMocks.openDmsFilePreview).not.toHaveBeenCalled();
|
wrapper.unmount();
|
});
|
});
|