/* * Tencent is pleased to support the open source community by making TMagicEditor available. * * Copyright (C) 2025 Tencent. */ import { describe, expect, test, vi } from 'vitest'; import { defineComponent, h } from 'vue'; import { mount } from '@vue/test-utils'; import StyleSetter from '@editor/fields/StyleSetter/Index.vue'; vi.mock('@tmagic/design', () => ({ TMagicCollapse: defineComponent({ name: 'TMagicCollapse', props: ['modelValue'], setup(_props, { slots }) { return () => h('div', { class: 'collapse' }, slots.default?.()); }, }), TMagicCollapseItem: defineComponent({ name: 'TMagicCollapseItem', props: ['name'], setup(_props, { slots }) { return () => h('div', { class: 'collapse-item' }, [slots.title?.(), slots.default?.()]); }, }), })); vi.mock('@editor/components/Icon.vue', () => ({ default: defineComponent({ name: 'MIcon', props: ['icon'], setup: () => () => h('i') }), })); vi.mock('@editor/fields/StyleSetter/pro/index', () => { const make = (name: string) => defineComponent({ name, props: ['values', 'size', 'disabled'], emits: ['change'], setup(_p, { emit }) { return () => h('div', { class: name, onClick: () => emit('change', { foo: 1 }, { changeRecords: [{ propPath: 'foo', value: 1 }] }), }); }, }); return { Layout: make('Layout'), Position: make('Position'), Background: make('Background'), Font: make('Font'), Border: make('Border'), Transform: make('Transform'), }; }); describe('StyleSetter Index', () => { test('渲染 6 个 collapse-item', () => { const wrapper = mount(StyleSetter, { props: { model: { style: {} }, name: 'style', prop: 'style' } as any, }); expect(wrapper.findAll('.collapse-item').length).toBe(6); }); test('change 时为 propPath 添加 prop 前缀', async () => { const wrapper = mount(StyleSetter, { props: { model: { style: {} }, name: 'style', prop: 'style' } as any, }); await wrapper.find('.Layout').trigger('click'); const events = wrapper.emitted('change'); expect(events).toBeTruthy(); expect((events?.[0]?.[1] as any).changeRecords[0].propPath).toBe('style.foo'); }); test('prop 与 name 不一致时,propPath 使用完整的 prop 路径而非 name', async () => { const wrapper = mount(StyleSetter, { props: { model: { style: {} }, name: 'style', prop: 'data.items.0.style' } as any, }); await wrapper.find('.Position').trigger('click'); const events = wrapper.emitted('change'); expect(events).toBeTruthy(); expect((events?.[0]?.[1] as any).changeRecords[0].propPath).toBe('data.items.0.style.foo'); }); test('change 透传原始 value 并保留 changeRecords 其他字段', async () => { const wrapper = mount(StyleSetter, { props: { model: { style: {} }, name: 'style', prop: 'style' } as any, }); await wrapper.find('.Background').trigger('click'); const events = wrapper.emitted('change'); expect(events).toBeTruthy(); const [value, eventData] = events![0] as any[]; expect(value).toEqual({ foo: 1 }); expect(eventData.changeRecords).toHaveLength(1); expect(eventData.changeRecords[0]).toEqual({ propPath: 'style.foo', value: 1 }); }); test('eventData 无 changeRecords 时也能正常 emit', async () => { const wrapper = mount(StyleSetter, { props: { model: { style: {} }, name: 'style', prop: 'style' } as any, global: { stubs: { Layout: defineComponent({ name: 'LayoutStub', emits: ['change'], setup(_p, { emit }) { return () => h('div', { class: 'Layout-no-records', onClick: () => emit('change', { foo: 2 }, {}) }); }, }), }, }, }); await wrapper.find('.Layout-no-records').trigger('click'); const events = wrapper.emitted('change'); expect(events).toBeTruthy(); expect((events?.[0]?.[1] as any).changeRecords).toBeUndefined(); }); test('values/size/disabled 正确透传到子组件', () => { const wrapper = mount(StyleSetter, { props: { model: { style: { color: 'red' } }, name: 'style', prop: 'style', size: 'small', disabled: true, } as any, }); const layout = wrapper.findComponent({ name: 'Layout' }); expect(layout.props('values')).toEqual({ color: 'red' }); expect(layout.props('size')).toBe('small'); expect(layout.props('disabled')).toBe(true); }); });