build / task (16.x, npm, macOS-latest) (push) Canceled after 0s
build / task (16.x, npm, ubuntu-latest) (push) Canceled after 0s
build / task (16.x, npm, windows-latest) (push) Canceled after 0s
build / task (16.x, pnpm, macOS-latest) (push) Canceled after 0s
build / task (16.x, pnpm, ubuntu-latest) (push) Canceled after 0s
build / task (16.x, pnpm, windows-latest) (push) Canceled after 0s
build / task (16.x, yarn, macOS-latest) (push) Canceled after 0s
build / task (16.x, yarn, ubuntu-latest) (push) Canceled after 0s
build / task (16.x, yarn, windows-latest) (push) Canceled after 0s
build / task (18.x, npm, macOS-latest) (push) Canceled after 0s
build / task (18.x, npm, ubuntu-latest) (push) Canceled after 0s
build / task (18.x, npm, windows-latest) (push) Canceled after 0s
build / task (18.x, pnpm, macOS-latest) (push) Canceled after 0s
build / task (18.x, pnpm, ubuntu-latest) (push) Canceled after 0s
build / task (18.x, pnpm, windows-latest) (push) Canceled after 0s
build / task (18.x, yarn, macOS-latest) (push) Canceled after 0s
build / task (18.x, yarn, ubuntu-latest) (push) Canceled after 0s
build / task (18.x, yarn, windows-latest) (push) Canceled after 0s
build / task (20.x, npm, macOS-latest) (push) Canceled after 0s
build / task (20.x, npm, ubuntu-latest) (push) Canceled after 0s
build / task (20.x, npm, windows-latest) (push) Canceled after 0s
build / task (20.x, pnpm, macOS-latest) (push) Canceled after 0s
build / task (20.x, pnpm, ubuntu-latest) (push) Canceled after 0s
build / task (20.x, pnpm, windows-latest) (push) Canceled after 0s
build / task (20.x, yarn, macOS-latest) (push) Canceled after 0s
build / task (20.x, yarn, ubuntu-latest) (push) Canceled after 0s
build / task (20.x, yarn, windows-latest) (push) Canceled after 0s
213 lines
8.4 KiB
TypeScript
213 lines
8.4 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { defineComponent, h } from 'vue';
|
|
import { flushPromises, mount, type VueWrapper } from '@vue/test-utils';
|
|
import ElementPlus from 'element-plus';
|
|
import DeviceRowActions from '../src/views/device/components/device-row-actions.vue';
|
|
import type { DeviceRowAction } from '../src/views/device/components/device-row-actions';
|
|
import CameraList from '../src/views/device/components/camera-list.vue';
|
|
import PlatformCompareButton from '../src/views/collection/video/components/platform-compare-button.vue';
|
|
|
|
const spies = vi.hoisted(() => ({
|
|
permissions: new Set<string>(),
|
|
preview: vi.fn(),
|
|
replay: vi.fn(),
|
|
edit: vi.fn(),
|
|
push: vi.fn(),
|
|
deviceQuery: vi.fn(),
|
|
channelQuery: vi.fn(),
|
|
}));
|
|
vi.mock('/@/utils/authFunction', () => ({ auth: (permission: string) => spies.permissions.has(permission) }));
|
|
vi.mock('vue-router', () => ({ useRouter: () => ({ push: spies.push }) }));
|
|
vi.mock('/@/api/device/deviceManage', () => ({ cameraPage: vi.fn(), delObjs: vi.fn() }));
|
|
vi.mock('/@/api/device/deviceChannel', () => ({ cameraChannelPage: vi.fn(), delObjs: vi.fn() }));
|
|
vi.mock('/@/hooks/dict', () => ({
|
|
useDict: () => ({ device_category: [], is_sync: [], is_nline: [], dh_device_manufacturer: [], camera_type: [] }),
|
|
}));
|
|
vi.mock('/@/hooks/table', () => ({
|
|
useTable: (state: any) => {
|
|
const channel = state.createdIsNeed === false;
|
|
state.dataList = channel
|
|
? [{ id: 'channel-id', channelName: '测试通道', channelCode: 'channel-code', deviceCode: 'device-code' }]
|
|
: [{ id: 'device-id', deviceName: '测试设备', deviceCode: 'device-code', deviceIp: '192.0.2.10', devicePort: 80 }];
|
|
state.pagination = { total: 1, current: 1, size: 20 };
|
|
return { getDataList: channel ? spies.channelQuery : spies.deviceQuery };
|
|
},
|
|
}));
|
|
vi.mock('/@/hooks/message', () => ({ useMessage: () => ({}), useMessageBox: () => ({}) }));
|
|
vi.mock('/@/views/collection/video/components/platform-compare-button.vue', () => ({
|
|
default: { name: 'PlatformCompareButton', props: ['resourceType'], emits: ['changed'], render: () => null },
|
|
}));
|
|
|
|
const actions: DeviceRowAction[] = [
|
|
{ key: 'preview', label: '预览' },
|
|
{ key: 'replay', label: '回放' },
|
|
{ key: 'edit', label: '编辑', permission: 'edit' },
|
|
{ key: 'delete', label: '删除', permission: 'delete', danger: true },
|
|
];
|
|
const mounted: VueWrapper[] = [];
|
|
const slotStub = defineComponent({
|
|
setup:
|
|
(_, { slots }) =>
|
|
() =>
|
|
h('div', slots.default?.()),
|
|
});
|
|
const dialogStub = (openDialog: (...args: any[]) => any) =>
|
|
defineComponent({
|
|
setup: (_, { expose }) => {
|
|
expose({ openDialog });
|
|
return () => null;
|
|
},
|
|
});
|
|
|
|
function renderActions(value = actions) {
|
|
const wrapper = mount(DeviceRowActions, { props: { actions: value }, global: { plugins: [ElementPlus] } });
|
|
document.body.appendChild(wrapper.element);
|
|
mounted.push(wrapper);
|
|
return wrapper;
|
|
}
|
|
function renderCamera(props = {}) {
|
|
const wrapper = mount(CameraList, {
|
|
props,
|
|
|
|
global: {
|
|
plugins: [ElementPlus],
|
|
mocks: { $t: (key: string) => key },
|
|
directives: {
|
|
auth: {
|
|
mounted: (el, binding) => {
|
|
if (!spies.permissions.has(binding.value)) el.remove();
|
|
},
|
|
},
|
|
},
|
|
stubs: {
|
|
DraggableOrgTree: slotStub,
|
|
DictSelect: slotStub,
|
|
DictTag: slotStub,
|
|
RightToolbar: slotStub,
|
|
Pagination: slotStub,
|
|
UploadExcel: slotStub,
|
|
FormDialog: dialogStub(spies.edit),
|
|
DeviceChannerForm: dialogStub(spies.edit),
|
|
PreviewVideoDialog: dialogStub(spies.preview),
|
|
ReplayVideoDialog: dialogStub(spies.replay),
|
|
PlatformCompareButton: true,
|
|
},
|
|
},
|
|
});
|
|
document.body.appendChild(wrapper.element);
|
|
mounted.push(wrapper);
|
|
return wrapper;
|
|
}
|
|
function button(wrapper: VueWrapper, label: string) {
|
|
const match = wrapper.findAll('button').find((item) => item.text() === label);
|
|
if (!match) throw new Error(`找不到按钮:${label}`);
|
|
return match;
|
|
}
|
|
async function menuItem(label: string) {
|
|
await flushPromises();
|
|
const match = [...document.querySelectorAll<HTMLElement>('[role="menuitem"]')].find((item) => item.textContent?.trim() === label);
|
|
if (!match) throw new Error(`找不到菜单项:${label}`);
|
|
return match;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
spies.permissions.clear();
|
|
});
|
|
afterEach(() => {
|
|
mounted.splice(0).forEach((wrapper) => wrapper.unmount());
|
|
document.body.innerHTML = '';
|
|
});
|
|
|
|
describe('设备列表行操作', () => {
|
|
it('先过滤权限,常用按钮不留空位或空的更多菜单', () => {
|
|
const wrapper = renderActions([actions[2], actions[0], actions[3]]);
|
|
expect(wrapper.text()).toBe('预览');
|
|
expect(wrapper.findAll('button')).toHaveLength(1);
|
|
});
|
|
|
|
it('更多菜单保留有权限的操作,点击前不触发操作', async () => {
|
|
spies.permissions.add('edit');
|
|
spies.permissions.add('delete');
|
|
const wrapper = renderActions();
|
|
await button(wrapper, '预览').trigger('click');
|
|
expect(wrapper.emitted('select')).toEqual([['preview']]);
|
|
await button(wrapper, '更多').trigger('click');
|
|
expect((await menuItem('删除')).textContent).toContain('删除');
|
|
expect(wrapper.emitted('select')).toHaveLength(1);
|
|
(await menuItem('编辑')).click();
|
|
await flushPromises();
|
|
expect(wrapper.emitted('select')).toEqual([['preview'], ['edit']]);
|
|
});
|
|
|
|
it('没有任何可用操作时不渲染菜单,加载中操作无法重复触发', async () => {
|
|
const empty = renderActions([actions[2], actions[3]]);
|
|
expect(empty.findAll('button')).toHaveLength(0);
|
|
const wrapper = renderActions([{ key: 'sync', label: '同步 ICC', loading: true }]);
|
|
await button(wrapper, '同步 ICC').trigger('click');
|
|
expect(wrapper.emitted('select')).toBeUndefined();
|
|
});
|
|
|
|
it('权限变化后,已经打开的菜单也不能发出无权限操作', async () => {
|
|
spies.permissions.add('delete');
|
|
const wrapper = renderActions();
|
|
await button(wrapper, '更多').trigger('click');
|
|
const item = await menuItem('删除');
|
|
spies.permissions.delete('delete');
|
|
item.click();
|
|
await flushPromises();
|
|
expect(wrapper.emitted('select')).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('摄像头列表操作入口', () => {
|
|
it('设备预览传设备 ID,回放传设备编码,详情导航仍可用', async () => {
|
|
const wrapper = renderCamera();
|
|
await flushPromises();
|
|
await button(wrapper, '预览').trigger('click');
|
|
await button(wrapper, '回放').trigger('click');
|
|
expect(spies.preview).toHaveBeenCalledWith('device', 'device-id');
|
|
expect(spies.replay).toHaveBeenCalledWith('device', 'device-code');
|
|
await button(wrapper, '更多').trigger('click');
|
|
(await menuItem('详情')).click();
|
|
await flushPromises();
|
|
expect(spies.push).toHaveBeenCalledWith({ path: '/device/detail/index', query: { id: 'device-id' } });
|
|
});
|
|
|
|
it('首次进入通道才查询,预览及回放使用通道编码', async () => {
|
|
const wrapper = renderCamera({ enableCrud: false, actions: ['preview', 'replay'] });
|
|
await flushPromises();
|
|
expect(spies.channelQuery).not.toHaveBeenCalled();
|
|
const channelTab = wrapper.findAll('[role="tab"]').find((item) => item.text() === '通道')!;
|
|
await channelTab.trigger('click');
|
|
await flushPromises();
|
|
expect(spies.channelQuery).toHaveBeenCalledOnce();
|
|
const pane = wrapper.findAll('.device-list-surface > div').find((item) => item.text().includes('测试通道'))!;
|
|
await button(pane, '预览').trigger('click');
|
|
await button(pane, '回放').trigger('click');
|
|
expect(spies.preview).toHaveBeenCalledWith('channel', 'channel-code');
|
|
expect(spies.replay).toHaveBeenCalledWith('channel', 'channel-code');
|
|
});
|
|
|
|
it('设备和通道工具栏打开各自类型的对比,修复后刷新已加载的两张表', async () => {
|
|
const wrapper = renderCamera();
|
|
await flushPromises();
|
|
let entries = wrapper.findAllComponents(PlatformCompareButton);
|
|
expect(entries.map(entry => entry.props('resourceType'))).toEqual(['DEVICE']);
|
|
entries[0].vm.$emit('changed');
|
|
await flushPromises();
|
|
expect(spies.deviceQuery).toHaveBeenCalledOnce();
|
|
expect(spies.channelQuery).not.toHaveBeenCalled();
|
|
await wrapper.findAll('[role="tab"]').find(item => item.text() === '通道')!.trigger('click');
|
|
await flushPromises();
|
|
entries = wrapper.findAllComponents(PlatformCompareButton);
|
|
expect(entries.map(entry => entry.props('resourceType'))).toEqual(['DEVICE', 'CHANNEL']);
|
|
spies.deviceQuery.mockClear();
|
|
spies.channelQuery.mockClear();
|
|
entries[1].vm.$emit('changed');
|
|
await flushPromises();
|
|
expect(spies.deviceQuery).toHaveBeenCalledOnce();
|
|
expect(spies.channelQuery).toHaveBeenCalledOnce();
|
|
});
|
|
});
|