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
126 lines
6.7 KiB
TypeScript
126 lines
6.7 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { flushPromises, mount, type VueWrapper } from '@vue/test-utils';
|
|
import { defineComponent, h } from 'vue';
|
|
import GroupPlay from '../src/views/device/deviceGroupPlay/index.vue';
|
|
|
|
const messages = vi.hoisted(() => ({ error: vi.fn(), warning: vi.fn() }));
|
|
vi.mock('../src/hooks/message', () => ({ useMessage: () => messages }));
|
|
vi.mock('../src/hooks/dict', () => ({ useDict: () => ({ dh_replay_source: [], dh_video_stream_type: [] }) }));
|
|
|
|
const players: any[] = [];
|
|
const Tree = defineComponent({ name: 'DraggableDeviceGroupDraggable', props: ['windowAssignments'], emits: ['getSelectNode'], setup(_, { slots }) { return () => h('div', slots.default?.()); } });
|
|
const Player = defineComponent({
|
|
name: 'RtspPluginVideo', props: ['windowLabels', 'division'], emits: ['changePage'],
|
|
setup(_, { expose }) {
|
|
const api = { startReal: vi.fn(), startPlayback: vi.fn(), closeVideo: vi.fn(() => Promise.resolve()), setTotal: vi.fn() };
|
|
expose(api); players.push(api);
|
|
return () => h('div', { class: 'player' });
|
|
},
|
|
});
|
|
const Tabs = defineComponent({
|
|
props: ['modelValue'], emits: ['update:modelValue', 'tab-change'],
|
|
setup(_, { emit }) {
|
|
return () => h('div', ['PREVIEW', 'REPLAY'].map(name => h('button', {
|
|
onClick: () => { emit('update:modelValue', name); emit('tab-change', name); },
|
|
}, name)));
|
|
},
|
|
});
|
|
const Slot = defineComponent({ setup(_, { slots }) { return () => h('div', slots.default?.()); } });
|
|
const Form = defineComponent({ setup(_, { slots, expose }) { expose({ resetFields: vi.fn() }); return () => h('div', slots.default?.()); } });
|
|
const DatePicker = defineComponent({ props: ['modelValue'], emits: ['update:modelValue'], render: () => h('input') });
|
|
const nodes = (count: number) => Array.from({ length: count }, (_, i) => ({ id: `item-${i}`, code: `camera-${i}`, name: `通道 ${i}`, deep: 2 }));
|
|
let wrapper: VueWrapper;
|
|
async function select(items: any[]) {
|
|
wrapper.findComponent(Tree).vm.$emit('getSelectNode', {}, items, false, []);
|
|
await flushPromises();
|
|
}
|
|
beforeEach(() => {
|
|
players.length = 0;
|
|
wrapper = mount(GroupPlay, { global: {
|
|
stubs: { DraggableDeviceGroupDraggable: Tree, RtspPluginVideo: Player, 'el-tabs': Tabs, 'el-tab-pane': true,
|
|
'el-row': Slot, 'el-form': Form, 'el-form-item': Slot, 'el-date-picker': DatePicker,
|
|
'dict-select': true, 'el-button': Slot, 'el-icon': true, Grid: true },
|
|
directives: { loading: () => undefined }, mocks: { $t: (key: string) => key },
|
|
} });
|
|
});
|
|
afterEach(() => wrapper.unmount());
|
|
|
|
describe('分组播放的窗口与通道对应关系', () => {
|
|
it('九个窗口按树顺序播放,左侧编号与 SDK 的 snum 一一对应', async () => {
|
|
const selected = nodes(9).reverse();
|
|
await select(selected);
|
|
const payload = players[0].startReal.mock.calls[0][0];
|
|
expect(payload.map((item: any) => item.channelId)).toEqual(selected.map(item => item.code));
|
|
expect(payload.map((item: any) => item.snum)).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
|
const labels = wrapper.findComponent(Player).props('windowLabels');
|
|
expect(labels.map((item: any) => item.label)).toEqual(['1-1', '1-2', '1-3', '2-1', '2-2', '2-3', '3-1', '3-2', '3-3']);
|
|
expect(wrapper.findComponent(Tree).props('windowAssignments')).toEqual(labels);
|
|
expect(payload.every((item: any) => item.streamType === 2)).toBe(true);
|
|
});
|
|
|
|
it('同一通道出现在不同分组时,保留各自的树节点身份和窗口', async () => {
|
|
const selected = [{ ...nodes(1)[0], id: 'group-a-camera' }, { ...nodes(1)[0], id: 'group-b-camera' }];
|
|
await select(selected);
|
|
expect(players[0].startReal.mock.calls[0][0]).toEqual(selected.map((item, snum) => ({
|
|
channelId: item.code, channelName: item.name, snum, streamType: 2,
|
|
})));
|
|
expect(wrapper.findComponent(Tree).props('windowAssignments').map((item: any) => item.id))
|
|
.toEqual(['group-a-camera', 'group-b-camera']);
|
|
});
|
|
|
|
it('超过九路按每页九路播放,第二页重新从 1-1 编号,重新勾选回第一页', async () => {
|
|
await select(nodes(11));
|
|
expect(players[0].startReal.mock.calls[0][0]).toHaveLength(9);
|
|
wrapper.findComponent(Player).vm.$emit('changePage', { current: 2, size: 9 });
|
|
await flushPromises();
|
|
expect(players[0].startReal.mock.lastCall[0].map((item: any) => item.channelId)).toEqual(['camera-9', 'camera-10']);
|
|
expect(wrapper.findComponent(Tree).props('windowAssignments').map((item: any) => item.label)).toEqual(['1-1', '1-2']);
|
|
expect(players[0].setTotal).toHaveBeenLastCalledWith(11, 2);
|
|
await select(nodes(3));
|
|
expect(players[0].setTotal).toHaveBeenLastCalledWith(3, 1);
|
|
expect(players[0].startReal.mock.lastCall[0]).toHaveLength(3);
|
|
});
|
|
|
|
it('不足九路和清空选择都会移除旧标注,空窗口继续保留位置编号', async () => {
|
|
await select(nodes(9));
|
|
await select(nodes(2));
|
|
expect(wrapper.findComponent(Tree).props('windowAssignments')).toHaveLength(2);
|
|
let labels = wrapper.findComponent(Player).props('windowLabels');
|
|
expect(labels.filter((item: any) => item.channelId)).toHaveLength(2);
|
|
await select([]);
|
|
labels = wrapper.findComponent(Player).props('windowLabels');
|
|
expect(labels).toHaveLength(9);
|
|
expect(labels.every((item: any) => !item.channelId)).toBe(true);
|
|
expect(players[0].setTotal).toHaveBeenLastCalledWith(0, 1);
|
|
expect(players[0].startReal).toHaveBeenCalledTimes(2);
|
|
expect(players[0].closeVideo).toHaveBeenCalledTimes(3);
|
|
});
|
|
|
|
it('快速改变勾选时,晚到的旧关闭回包不会重新播放旧通道', async () => {
|
|
let finish!: () => void;
|
|
players[0].closeVideo.mockImplementation(() => new Promise<void>(resolve => { finish = resolve; }));
|
|
const first = select(nodes(9));
|
|
await first;
|
|
const oldFinish = finish;
|
|
await select(nodes(2));
|
|
oldFinish(); finish();
|
|
await flushPromises();
|
|
expect(players[0].startReal).toHaveBeenCalledTimes(1);
|
|
expect(players[0].startReal.mock.lastCall[0]).toHaveLength(2);
|
|
});
|
|
|
|
it('回放沿用相同顺序、九宫格和录像参数,清空日期后查询会提示', async () => {
|
|
await select(nodes(4));
|
|
await wrapper.get('button:nth-child(2)').trigger('click');
|
|
await flushPromises();
|
|
const replay = players.at(-1);
|
|
expect(replay.startPlayback.mock.lastCall[0].map((item: any) => item.channelId)).toEqual(nodes(4).map(item => item.code));
|
|
expect(replay.startPlayback.mock.lastCall[0][0]).toMatchObject({ snum: 0, streamType: 1, recordSource: 2 });
|
|
wrapper.findComponent(DatePicker).vm.$emit('update:modelValue', null);
|
|
await select(nodes(2));
|
|
expect(messages.warning).toHaveBeenCalledWith('请选择完整的录像时间段');
|
|
expect(replay.startPlayback).toHaveBeenCalledTimes(1);
|
|
expect(replay.closeVideo).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|