/* * Tencent is pleased to support the open source community by making TMagicEditor available. * * Copyright (C) 2025 Tencent. */ import { beforeEach, describe, expect, test, vi } from 'vitest'; import { defineComponent, h, nextTick } from 'vue'; import { mount } from '@vue/test-utils'; import Sidebar from '@editor/layouts/sidebar/Sidebar.vue'; const depService = { get: vi.fn(() => false) }; const uiService = { get: vi.fn(() => ({ left: 200 })), set: vi.fn(), }; const propsService = { getDisabledDataSource: vi.fn(() => false), getDisabledCodeBlock: vi.fn(() => false), }; vi.mock('@editor/hooks/use-services', () => ({ useServices: () => ({ depService, uiService, propsService }), })); vi.mock('@editor/hooks/use-editor-content-height', () => ({ useEditorContentHeight: () => ({ height: { value: 600 } }), })); const dragstartHandler = vi.fn(); const dragendHandler = vi.fn(); vi.mock('@editor/hooks/use-float-box', () => ({ useFloatBox: () => ({ dragstartHandler, dragendHandler, floatBoxStates: { layer: { status: false }, 'code-block': { status: false }, 'data-source': { status: false } }, showingBoxKeys: { value: [] }, }), })); vi.mock('@editor/layouts/sidebar/code-block/CodeBlockListPanel.vue', () => ({ default: { name: 'CodeBlockListPanel', render: () => null }, })); vi.mock('@editor/layouts/sidebar/data-source/DataSourceListPanel.vue', () => ({ default: { name: 'DataSourceListPanel', render: () => null }, })); vi.mock('@editor/layouts/sidebar/layer/LayerPanel.vue', () => ({ default: { name: 'LayerPanel', render: () => null }, })); vi.mock('@editor/layouts/sidebar/ComponentListPanel.vue', () => ({ default: { name: 'ComponentListPanel', render: () => null }, })); const stub = (name: string) => defineComponent({ name, setup() { return () => h('div', { class: name }); }, }); vi.mock('@editor/components/Icon.vue', () => ({ default: defineComponent({ name: 'MIcon', props: ['icon'], setup: () => () => h('i', { class: 'fake-icon' }) }), })); vi.mock('@editor/components/FloatingBox.vue', () => ({ default: defineComponent({ name: 'FloatingBox', props: ['visible', 'width', 'height', 'title', 'position'], setup(_p, { slots }) { return () => h('div', { class: 'floating-box' }, slots.body?.()); }, }), })); vi.mock('@editor/type', async () => { const actual = await vi.importActual('@editor/type'); return { ...actual, SideItemKey: { COMPONENT_LIST: 'component-list', LAYER: 'layer', CODE_BLOCK: 'code-block', DATA_SOURCE: 'data-source', }, ColumnLayout: { LEFT: 'left', CENTER: 'center', RIGHT: 'right' }, }; }); beforeEach(() => { vi.clearAllMocks(); propsService.getDisabledDataSource.mockReturnValue(false); propsService.getDisabledCodeBlock.mockReturnValue(false); uiService.get.mockReturnValue({ left: 200 }); }); const baseProps = (extra: any = {}) => ({ data: { type: 'tabs', status: '组件', items: ['component-list', 'layer', 'code-block', 'data-source'] }, layerContentMenu: [], customContentMenu: (m: any) => m, ...extra, }); describe('Sidebar', () => { test('渲染 4 个 sidebar header 条目', () => { const wrapper = mount(Sidebar, { props: baseProps() as any }); expect(wrapper.findAll('.m-editor-sidebar-header-item').length).toBe(4); }); test('disabledDataSource 时不展示 data-source', () => { propsService.getDisabledDataSource.mockReturnValue(true); const wrapper = mount(Sidebar, { props: baseProps() as any }); expect(wrapper.findAll('.m-editor-sidebar-header-item').length).toBe(3); }); test('disabledCodeBlock 时不展示 code-block', () => { propsService.getDisabledCodeBlock.mockReturnValue(true); const wrapper = mount(Sidebar, { props: baseProps() as any }); expect(wrapper.findAll('.m-editor-sidebar-header-item').length).toBe(3); }); test('点击 header item 切换 activeTabName', async () => { const wrapper = mount(Sidebar, { props: baseProps() as any }); const items = wrapper.findAll('.m-editor-sidebar-header-item'); await items[1].trigger('click'); expect((wrapper.vm as any).activeTabName).toBe('已选组件'); }); test('items 为空时不渲染 sidebar', () => { const wrapper = mount(Sidebar, { props: baseProps({ data: { type: 'tabs', status: '', items: [] } }) as any, }); expect(wrapper.find('.m-editor-sidebar').exists()).toBe(false); }); test('sideBarItems 写入 uiService', () => { mount(Sidebar, { props: baseProps() as any }); expect(uiService.set).toHaveBeenCalledWith('sideBarItems', expect.any(Array)); }); test('data.status 变化时同步 activeTabName', async () => { const wrapper = mount(Sidebar, { props: baseProps() as any }); await wrapper.setProps({ data: { type: 'tabs', status: '数据源', items: ['data-source'] } }); await nextTick(); expect((wrapper.vm as any).activeTabName).toBe('数据源'); }); test('beforeClick 返回 false 时不切换', async () => { const beforeClick = vi.fn(async () => false); const wrapper = mount(Sidebar, { props: baseProps({ data: { type: 'tabs', status: 'A', items: [ { $key: 'a', text: 'A', component: stub('A'), beforeClick }, { $key: 'b', text: 'B', component: stub('B') }, ], }, }) as any, }); const items = wrapper.findAll('.m-editor-sidebar-header-item'); await items[0].trigger('click'); await nextTick(); expect(beforeClick).toHaveBeenCalled(); }); test('dragstartHandler 触发', async () => { const wrapper = mount(Sidebar, { props: baseProps() as any }); const items = wrapper.findAll('.m-editor-sidebar-header-item'); await items[0].trigger('dragstart'); expect(dragstartHandler).toHaveBeenCalled(); }); });