import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { flushPromises, mount, type VueWrapper } from '@vue/test-utils'; import { defineComponent, h } from 'vue'; import { ElTree, ElInput, ElButton, ElIcon } from 'element-plus'; import * as icons from '@element-plus/icons-vue'; import GroupTree from '../src/components/DraggableDeviceGroupDraggable/index.vue'; const api = vi.hoisted(() => ({ query: vi.fn(), editGroupSort: vi.fn(), error: vi.fn() })); vi.mock('../src/api/device/deviceGroup', () => ({ getDeviceGroupTree: api.query, editGroupSort: api.editGroupSort })); vi.mock('../src/hooks/message', () => ({ useMessage: () => ({ error: api.error }) })); const Slot = defineComponent({ setup(_, { slots }) { return () => h('div', slots.default?.()); } }); const groupData = () => [ { id: 'group-a', name: '值班员视图', deep: 1, visible: true, children: [ { id: 'a', parentId: 'group-a', name: '南门球机', code: 'channel-a', deep: 2, visible: true }, { id: 'b', parentId: 'group-a', name: '西围墙', code: 'channel-b', deep: 2, visible: true }, ] }, { id: 'group-b', name: '兵器场', deep: 1, visible: true, children: [ { id: 'c', parentId: 'group-b', name: '额外通道', code: 'channel-c', deep: 2, visible: true }, ] }, ]; let wrapper: VueWrapper; const checkedCodes = () => (wrapper.emitted('getSelectNode')!.at(-1)![1] as any[]).filter(item => item.deep === 2).map(item => item.code); beforeEach(() => { api.query.mockReset().mockResolvedValue({ code: 0, data: groupData() }); wrapper = mount(GroupTree, { global: { components: { ElTree, ElInput, ElButton, ElIcon, ...icons }, stubs: { Splitpanes: Slot, Pane: Slot, 'el-switch': true }, directives: { loading: () => undefined }, } }); }); afterEach(() => wrapper.unmount()); describe('分组树的真实勾选与筛选', () => { it('默认勾选第一个分组,按树顺序提交可播放通道', async () => { await flushPromises(); expect(checkedCodes()).toEqual(['channel-a', 'channel-b']); expect(wrapper.find('.selection-count').text()).toBe('已选 2'); }); it('搜索只过滤展示,保留当前勾选且不重新请求或触发播放', async () => { await flushPromises(); const count = wrapper.emitted('getSelectNode')!.length; await wrapper.find('.sidebar-search input').setValue('额外'); await flushPromises(); expect(api.query).toHaveBeenCalledTimes(1); expect(wrapper.emitted('getSelectNode')).toHaveLength(count); expect(wrapper.findComponent(ElTree).vm.getCheckedNodes(true).map((item: any) => item.code)).toEqual(['channel-a', 'channel-b']); const row = wrapper.findAll('.el-tree-node__content').find(item => item.text().includes('额外通道'))!; expect(row.isVisible()).toBe(true); await row.find('input[type="checkbox"]').setValue(true); await flushPromises(); expect(checkedCodes()).toEqual(['channel-a', 'channel-b', 'channel-c']); expect(wrapper.find('.selection-count').text()).toBe('已选 3'); }); it('清空选择会同步树和父页面,半选分组不会算成视频通道', async () => { await flushPromises(); const row = wrapper.findAll('.el-tree-node__content').find(item => item.text().includes('南门球机'))!; await row.find('input[type="checkbox"]').setValue(false); await flushPromises(); expect(checkedCodes()).toEqual(['channel-b']); expect(wrapper.find('.selection-count').text()).toBe('已选 1'); await wrapper.findAll('button').find(item => item.text() === '清空选择')!.trigger('click'); await flushPromises(); expect(checkedCodes()).toEqual([]); expect(wrapper.findComponent(ElTree).vm.getCheckedNodes()).toHaveLength(0); expect(wrapper.find('.selection-count').text()).toBe('已选 0'); }); it('空分组响应仍正常显示空树', async () => { api.query.mockResolvedValueOnce({ code: 0, data: null }); await flushPromises(); await wrapper.vm.getTreeData(); await flushPromises(); expect(wrapper.text()).toContain('暂无匹配的分组或通道'); }); });