tmagic-editor/packages/editor/tests/unit/fields/CodeSelectCol.spec.ts
roymondchen abcd875731 fix(form): 修复切换同类型节点时表单字段配置串台
- MForm 新增 formKey,数据实体切换时只重挂叶子字段,保留 tab 等容器状态;异步 initValue 仅采用最后一次结果
- 代码块 / 数据源方法参数配置改为按 model 派生,避免 setup 期快照残留旧节点配置
- 补充相关单测与 form-props 文档
2026-11-05 15:17:03 +08:00

209 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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, ref } from 'vue';
import { mount } from '@vue/test-utils';
import CodeSelectCol from '@editor/fields/CodeSelectCol.vue';
const codeBlockService = {
getCodeDsl: vi.fn(() => ({
c1: { name: 'C1', params: [{ name: 'p1', type: 'text' }] },
c2: { name: 'C2', params: [] },
})),
};
const uiService = {
get: vi.fn(() => [{ $key: 'code-block' }]),
set: vi.fn(),
};
vi.mock('@editor/hooks/use-services', () => ({
useServices: () => ({ codeBlockService, uiService }),
}));
vi.mock('@tmagic/form', async (importOriginal) => {
const actual = await importOriginal<any>();
return {
...actual,
filterFunction: (_form: any, fn: any, props: any) => (typeof fn === 'function' ? fn(_form, props) : fn),
createValues: vi.fn(() => ({ p1: '' })),
MSelect: defineComponent({
name: 'MSelect',
props: ['model', 'name', 'size', 'prop', 'config'],
emits: ['change'],
setup() {
return () => h('select', { class: 'fake-select' });
},
}),
MContainer: defineComponent({
name: 'MFormContainer',
props: ['model', 'lastValues', 'isCompare', 'size', 'prop', 'config'],
setup() {
return () => h('div', { class: 'fake-diff-select' });
},
}),
};
});
vi.mock('@tmagic/design', () => ({
TMagicButton: defineComponent({
name: 'TMagicButton',
props: ['size'],
emits: ['click'],
setup(_p, { emit, slots }) {
return () => h('button', { onClick: () => emit('click') }, slots.default?.());
},
}),
}));
vi.mock('@editor/components/Icon.vue', () => ({
default: defineComponent({ name: 'MIcon', props: ['icon'], setup: () => () => h('i') }),
}));
vi.mock('@editor/components/CodeParams.vue', () => ({
default: defineComponent({
name: 'CodeParams',
props: ['name', 'model', 'size', 'disabled', 'paramsConfig', 'lastValues', 'isCompare'],
emits: ['change'],
setup(_p, { emit }) {
return () =>
h('div', {
class: 'fake-params',
onClick: () => emit('change', null, { changeRecords: [{ propPath: 'p1', value: 'x' }] }),
});
},
}),
}));
beforeEach(() => {
vi.clearAllMocks();
codeBlockService.getCodeDsl.mockReturnValue({
c1: { name: 'C1', params: [{ name: 'p1', type: 'text' }] },
c2: { name: 'C2', params: [] },
});
uiService.get.mockReturnValue([{ $key: 'code-block' }]);
});
const baseProps = (extra: any = {}) => ({
config: { type: 'code-select-col', notEditable: false },
name: 'codeId',
prop: 'codeId',
model: { codeId: 'c1', params: { p1: 'old' } },
size: 'default',
disabled: false,
...extra,
});
describe('CodeSelectCol', () => {
test('val 存在时显示编辑按钮', () => {
const wrapper = mount(CodeSelectCol, { props: baseProps() as any });
expect(wrapper.find('button').exists()).toBe(true);
});
test('val 为空时不显示编辑按钮', () => {
const wrapper = mount(CodeSelectCol, { props: baseProps({ model: { codeId: '', params: {} } }) as any });
expect(wrapper.find('button').exists()).toBe(false);
});
test('paramsConfig 不为空时渲染 CodeParams', () => {
const wrapper = mount(CodeSelectCol, { props: baseProps() as any });
expect(wrapper.find('.fake-params').exists()).toBe(true);
});
test('选择无 params 的代码块不渲染 CodeParams', () => {
const wrapper = mount(CodeSelectCol, { props: baseProps({ model: { codeId: 'c2', params: {} } }) as any });
expect(wrapper.find('.fake-params').exists()).toBe(false);
});
test('代码块 DSL 异步就绪后参数字段配置随之补齐', async () => {
const dsl = ref<any>(null);
codeBlockService.getCodeDsl.mockImplementation(() => dsl.value);
const wrapper = mount(CodeSelectCol, { props: baseProps() as any });
expect(wrapper.find('.fake-params').exists()).toBe(false);
dsl.value = { c1: { name: 'C1', params: [{ name: 'p1', type: 'text' }] } };
await nextTick();
expect(wrapper.find('.fake-params').exists()).toBe(true);
expect((wrapper.findComponent({ name: 'CodeParams' }).props('paramsConfig') as any[])[0].name).toBe('p1');
});
test('onCodeIdChangeHandler emit change 包含 changeRecords', async () => {
const wrapper = mount(CodeSelectCol, { props: baseProps() as any });
await wrapper.findComponent({ name: 'MSelect' }).vm.$emit('change', 'c2');
const evts = wrapper.emitted('change');
expect(evts?.[0]?.[0]).toBe('c2');
expect((evts?.[0]?.[1] as any).changeRecords.length).toBe(2);
});
test('CodeParams change 事件: 调整 propPath 后 emit', async () => {
const wrapper = mount(CodeSelectCol, { props: baseProps() as any });
await wrapper.find('.fake-params').trigger('click');
const evts = wrapper.emitted('change');
expect(evts?.[0]?.[0]).toBe('c1');
expect(((evts?.[0]?.[1] as any).changeRecords[0] as any).propPath).toContain('p1');
});
test('编辑按钮 emit edit-code 并切换到代码块 tab', async () => {
const eventBus = { emit: vi.fn() };
const wrapper = mount(CodeSelectCol, {
props: baseProps() as any,
global: { provide: { eventBus } },
});
await wrapper.find('button').trigger('click');
expect(uiService.set).toHaveBeenCalledWith('sideBarActiveTabName', 'code-block');
expect(eventBus.emit).toHaveBeenCalledWith('edit-code', 'c1');
});
test('未启用代码块侧边栏时不显示编辑按钮', () => {
uiService.get.mockReturnValue([]);
const wrapper = mount(CodeSelectCol, { props: baseProps() as any });
expect(wrapper.find('button').exists()).toBe(false);
});
test('codeDsl 为空时 selectConfig.options 返回空数组', () => {
codeBlockService.getCodeDsl.mockReturnValue(null as any);
const wrapper = mount(CodeSelectCol, { props: baseProps({ model: { codeId: '', params: {} } }) as any });
const select = wrapper.findComponent({ name: 'MSelect' });
expect((select.props('config') as any).options()).toEqual([]);
});
describe('对比模式', () => {
test('isCompare 但无 lastValues 时仍渲染普通 MSelect', () => {
const wrapper = mount(CodeSelectCol, { props: baseProps({ isCompare: true }) as any });
expect(wrapper.findComponent({ name: 'MSelect' }).exists()).toBe(true);
expect(wrapper.find('.fake-diff-select').exists()).toBe(false);
});
test('对比模式下拉框改用 MFormContainer,并隐藏编辑按钮', () => {
const wrapper = mount(CodeSelectCol, {
props: baseProps({
isCompare: true,
lastValues: { codeId: 'c2', params: {} },
}) as any,
});
expect(wrapper.find('.fake-diff-select').exists()).toBe(true);
expect(wrapper.findComponent({ name: 'MSelect' }).exists()).toBe(false);
// 对比模式为只读,不展示查看/编辑按钮
expect(wrapper.find('button').exists()).toBe(false);
});
test('对比模式将 isCompare/lastValues 透传给参数表单 CodeParams', () => {
const wrapper = mount(CodeSelectCol, {
props: baseProps({
isCompare: true,
lastValues: { codeId: 'c1', params: { p1: 'old' } },
}) as any,
});
const params = wrapper.findComponent({ name: 'CodeParams' });
expect(params.exists()).toBe(true);
expect(params.props('isCompare')).toBe(true);
expect(params.props('lastValues')).toEqual({ codeId: 'c1', params: { p1: 'old' } });
});
});
});