mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-05-30 20:28:04 +00:00
- Index.vue 透传 lastValues/isCompare 给各分类子组件,并冒泡 addDiffCount - pro 下 6 个分类组件接受新 props 并向 MContainer 传递 - Layout/Border 同时将新 props 传递给内部 Box/Border 组件 - components/Border.vue 接受新 props 并冒泡 MContainer 的 addDiffCount - components/Box.vue 接受 props 以保持接口一致 - 补充单元测试覆盖透传与事件冒泡
179 lines
6.1 KiB
TypeScript
179 lines
6.1 KiB
TypeScript
/*
|
||
* 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', 'lastValues', 'isCompare', 'size', 'disabled'],
|
||
emits: ['change', 'addDiffCount'],
|
||
setup(_p, { emit }) {
|
||
return () =>
|
||
h('div', {
|
||
class: name,
|
||
onClick: () => emit('change', { foo: 1 }, { changeRecords: [{ propPath: 'foo', value: 1 }] }),
|
||
onDblclick: () => emit('addDiffCount'),
|
||
});
|
||
},
|
||
});
|
||
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);
|
||
});
|
||
|
||
test('lastValues/isCompare 正确透传到子组件', () => {
|
||
const wrapper = mount(StyleSetter, {
|
||
props: {
|
||
model: { style: { color: 'red' } },
|
||
lastValues: { style: { color: 'blue' } },
|
||
isCompare: true,
|
||
name: 'style',
|
||
prop: 'style',
|
||
} as any,
|
||
});
|
||
const layout = wrapper.findComponent({ name: 'Layout' });
|
||
expect(layout.props('lastValues')).toEqual({ color: 'blue' });
|
||
expect(layout.props('isCompare')).toBe(true);
|
||
});
|
||
|
||
test('lastValues 为空时透传 undefined / isCompare 默认 false', () => {
|
||
const wrapper = mount(StyleSetter, {
|
||
props: { model: { style: {} }, name: 'style', prop: 'style' } as any,
|
||
});
|
||
const layout = wrapper.findComponent({ name: 'Layout' });
|
||
expect(layout.props('lastValues')).toBeUndefined();
|
||
// Boolean 类型 prop 未传时 Vue 默认为 false
|
||
expect(layout.props('isCompare')).toBe(false);
|
||
});
|
||
|
||
test('子组件 addDiffCount 事件向上冒泡', async () => {
|
||
const wrapper = mount(StyleSetter, {
|
||
props: {
|
||
model: { style: {} },
|
||
lastValues: { style: {} },
|
||
isCompare: true,
|
||
name: 'style',
|
||
prop: 'style',
|
||
} as any,
|
||
});
|
||
await wrapper.find('.Layout').trigger('dblclick');
|
||
expect(wrapper.emitted('addDiffCount')).toBeTruthy();
|
||
expect(wrapper.emitted('addDiffCount')?.length).toBe(1);
|
||
|
||
await wrapper.find('.Background').trigger('dblclick');
|
||
expect(wrapper.emitted('addDiffCount')?.length).toBe(2);
|
||
});
|
||
});
|