mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-07-23 01:01:40 +08:00
226 lines
8.6 KiB
TypeScript
226 lines
8.6 KiB
TypeScript
/*
|
||
* 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 { NODE_CONDS_RESULT_KEY } from '@tmagic/core';
|
||
import { validateForm } from '@tmagic/form';
|
||
|
||
import {
|
||
advancedTabConfig,
|
||
arrayOptions,
|
||
booleanOptions,
|
||
displayTabConfig,
|
||
eqOptions,
|
||
eventTabConfig,
|
||
fillConfig,
|
||
getCondOpOptionsByFieldType,
|
||
numberOptions,
|
||
styleTabConfig,
|
||
validatePropsForm,
|
||
} from '@editor/utils/props';
|
||
|
||
vi.mock('@tmagic/design', () => ({
|
||
tMagicMessage: {
|
||
success: vi.fn(),
|
||
error: vi.fn(),
|
||
},
|
||
}));
|
||
|
||
vi.mock('@tmagic/form', () => ({
|
||
validateForm: vi.fn(),
|
||
}));
|
||
|
||
describe('props 选项常量', () => {
|
||
test('eqOptions / arrayOptions / numberOptions / booleanOptions 内容稳定', () => {
|
||
expect(eqOptions.map((o) => o.value)).toEqual(['=', '!=']);
|
||
expect(arrayOptions.map((o) => o.value)).toEqual(['include', 'not_include']);
|
||
expect(numberOptions.map((o) => o.value)).toEqual(['>', '>=', '<', '<=', 'between', 'not_between']);
|
||
expect(booleanOptions.map((o) => o.value)).toEqual(['is', 'not']);
|
||
});
|
||
|
||
test('getCondOpOptionsByFieldType 按类型返回选项', () => {
|
||
expect(getCondOpOptionsByFieldType('array')).toEqual(arrayOptions);
|
||
expect(getCondOpOptionsByFieldType('boolean')).toEqual(booleanOptions);
|
||
expect(getCondOpOptionsByFieldType('null')).toEqual(booleanOptions);
|
||
expect(getCondOpOptionsByFieldType('number').map((o) => o.value)).toEqual([
|
||
...eqOptions.map((o) => o.value),
|
||
...numberOptions.map((o) => o.value),
|
||
]);
|
||
expect(getCondOpOptionsByFieldType('string').map((o) => o.value)).toEqual([
|
||
...arrayOptions.map((o) => o.value),
|
||
...eqOptions.map((o) => o.value),
|
||
]);
|
||
expect(getCondOpOptionsByFieldType('any').map((o) => o.value)).toEqual([
|
||
...arrayOptions.map((o) => o.value),
|
||
...eqOptions.map((o) => o.value),
|
||
...numberOptions.map((o) => o.value),
|
||
]);
|
||
expect(getCondOpOptionsByFieldType('').map((o) => o.value)).not.toContain('is');
|
||
});
|
||
|
||
test('styleTabConfig / eventTabConfig / displayTabConfig 基础结构', () => {
|
||
expect(styleTabConfig.title).toBe('样式');
|
||
expect(eventTabConfig.title).toBe('事件');
|
||
expect(displayTabConfig.title).toBe('显示条件');
|
||
});
|
||
|
||
test('advancedTabConfig 中包含 created/mounted/display', () => {
|
||
const names = (advancedTabConfig.items as any[]).map((i) => i.name);
|
||
expect(names).toEqual(expect.arrayContaining(['created', 'mounted', 'display']));
|
||
});
|
||
});
|
||
|
||
describe('fillConfig', () => {
|
||
test('默认会补充 type/id/name 三个基础字段', () => {
|
||
const result = fillConfig() as any;
|
||
const tab = result[0];
|
||
const propsTab = tab.items[0];
|
||
const names = propsTab.items.map((i: any) => i.name);
|
||
expect(names).toEqual(expect.arrayContaining(['type', 'id', 'name']));
|
||
});
|
||
|
||
test('已有 id/type/name 时不重复添加', () => {
|
||
const result = fillConfig([
|
||
{ name: 'id', text: 'id', type: 'text' } as any,
|
||
{ name: 'type', text: 'type', type: 'hidden' } as any,
|
||
{ name: 'name', text: 'name' } as any,
|
||
]) as any;
|
||
const propsTab = result[0].items[0];
|
||
const idItems = propsTab.items.filter((i: any) => i.name === 'id');
|
||
expect(idItems).toHaveLength(1);
|
||
});
|
||
|
||
test('disabledDataSource=true 时不追加 displayTab', () => {
|
||
const result = fillConfig([], { disabledDataSource: true }) as any;
|
||
const titles = result[0].items.map((i: any) => i.title);
|
||
expect(titles).not.toContain('显示条件');
|
||
});
|
||
|
||
test('支持自定义 labelWidth', () => {
|
||
const result = fillConfig([], { labelWidth: '120px' }) as any;
|
||
expect(result[0].labelWidth).toBe('120px');
|
||
});
|
||
|
||
test('styleTabConfig.display 跟随 uiService showStylePanel', () => {
|
||
const services = { uiService: { get: () => false } };
|
||
expect(styleTabConfig.display!({ services } as any)).toBe(true);
|
||
services.uiService.get = () => true;
|
||
expect(styleTabConfig.display!({ services } as any)).toBe(false);
|
||
services.uiService.get = () => undefined;
|
||
expect(styleTabConfig.display!({ services } as any)).toBe(false);
|
||
});
|
||
|
||
test('styleTabConfig 内 transform 项 defaultValue 返回 {}', () => {
|
||
const styleField = (styleTabConfig.items as any[])[0];
|
||
const transform = styleField.items.find((i: any) => i.name === 'transform');
|
||
expect(transform.defaultValue()).toEqual({});
|
||
});
|
||
|
||
test('displayTabConfig.display 当节点为 page 时返回 false', () => {
|
||
expect(displayTabConfig.display!({} as any, { model: { type: 'page' } } as any)).toBe(false);
|
||
expect(displayTabConfig.display!({} as any, { model: { type: 'text' } } as any)).toBe(true);
|
||
});
|
||
|
||
test('displayTabConfig select 项 extra 文案随 NODE_CONDS_RESULT_KEY 变化', () => {
|
||
const selectItem = (displayTabConfig.items as any[]).find((i) => i.type === 'select');
|
||
expect(typeof selectItem.extra).toBe('function');
|
||
const text1 = selectItem.extra({}, { model: { [NODE_CONDS_RESULT_KEY]: true } });
|
||
const text2 = selectItem.extra({}, { model: { [NODE_CONDS_RESULT_KEY]: false } });
|
||
expect(text1).toContain('隐藏');
|
||
expect(text2).toContain('显示');
|
||
});
|
||
|
||
test('id 字段 append.handler 触发 navigator.clipboard.writeText', async () => {
|
||
const result = fillConfig() as any;
|
||
const propsTab = result[0].items[0];
|
||
const idField = propsTab.items.find((i: any) => i.name === 'id');
|
||
const writeText = vi.fn().mockResolvedValue(undefined);
|
||
Object.defineProperty(navigator, 'clipboard', { value: { writeText }, configurable: true });
|
||
await idField.append.handler({}, { model: { id: 'abc' } });
|
||
expect(writeText).toHaveBeenCalledWith('abc');
|
||
});
|
||
|
||
test('id 字段 append.handler 复制失败时调用 error', async () => {
|
||
const result = fillConfig() as any;
|
||
const propsTab = result[0].items[0];
|
||
const idField = propsTab.items.find((i: any) => i.name === 'id');
|
||
const writeText = vi.fn().mockRejectedValue(new Error('fail'));
|
||
Object.defineProperty(navigator, 'clipboard', { value: { writeText }, configurable: true });
|
||
await idField.append.handler({}, { model: { id: 'xx' } });
|
||
expect(writeText).toHaveBeenCalled();
|
||
});
|
||
|
||
test('disabledCodeBlock=true 时高级 tab 仅保留非 code-select 项', () => {
|
||
const result = fillConfig([], { disabledCodeBlock: true }) as any;
|
||
const titles = result[0].items.map((i: any) => i.title);
|
||
expect(titles).toContain('高级');
|
||
const advanced = result[0].items.find((i: any) => i.title === '高级');
|
||
advanced.items.forEach((it: any) => {
|
||
expect(it.type).not.toBe('code-select');
|
||
});
|
||
});
|
||
|
||
test('advancedTabConfig 中所有 type=code-select 都有 labelPosition=top', () => {
|
||
const codeSelects = (advancedTabConfig.items as any[]).filter((i) => i.type === 'code-select');
|
||
codeSelects.forEach((cs: any) => {
|
||
expect(cs.labelPosition).toBe('top');
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('validatePropsForm', () => {
|
||
beforeEach(() => {
|
||
vi.mocked(validateForm).mockReset();
|
||
});
|
||
|
||
test('合入 appContext.provides、注入 stage/services 并合并外部 extendState,debug 默认 undefined', async () => {
|
||
vi.mocked(validateForm).mockResolvedValue('err-text');
|
||
|
||
const services = { uiService: {} } as any;
|
||
const stage = { id: 'stage' };
|
||
const appContext = { app: {}, provides: { foo: 1 } } as any;
|
||
|
||
const result = await validatePropsForm({
|
||
config: [],
|
||
values: { a: 1 },
|
||
appContext,
|
||
services,
|
||
stage,
|
||
extendState: async () => ({ extra: true }),
|
||
});
|
||
|
||
// 直接返回 validateForm 的结果
|
||
expect(result).toBe('err-text');
|
||
|
||
const arg = vi.mocked(validateForm).mock.calls[0][0];
|
||
expect(arg.config).toEqual([]);
|
||
expect(arg.debug).toBeUndefined();
|
||
expect(arg.initValues).toEqual({ a: 1 });
|
||
// appContext 保留原字段,但 provides 被替换为 { services }
|
||
expect(arg.appContext).toEqual({ app: {}, provides: { services } });
|
||
|
||
// extendState 合并外部返回并注入 stage/services
|
||
const state = await arg.extendState!({} as any);
|
||
expect(state).toEqual({ extra: true, stage, services });
|
||
});
|
||
|
||
test('appContext 缺省为 null,extendState 缺省时仍注入 stage/services,可覆盖 debug', async () => {
|
||
vi.mocked(validateForm).mockResolvedValue('');
|
||
|
||
const services = {} as any;
|
||
const stage = {};
|
||
|
||
await validatePropsForm({ config: [], values: {}, services, stage, debug: false });
|
||
|
||
const arg = vi.mocked(validateForm).mock.calls[0][0];
|
||
expect(arg.appContext).toBeNull();
|
||
expect(arg.debug).toBe(false);
|
||
|
||
const state = await arg.extendState!({} as any);
|
||
expect(state).toEqual({ stage, services });
|
||
});
|
||
});
|