mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-08-08 13:58:32 +00:00
将节点树遍历与 target 匹配下沉到与 collectByWorker 共用的常驻 worker, 避免大页面时主线程卡死;通过可序列化 descriptor 重建内置 target, 失败时回退主线程空闲队列。
348 lines
12 KiB
TypeScript
348 lines
12 KiB
TypeScript
import { describe, expect, test } from 'vitest';
|
||
|
||
import { DataSchema, NODE_CONDS_KEY } from '@tmagic/schema';
|
||
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX } from '@tmagic/utils';
|
||
|
||
import Target from '../src/Target';
|
||
import { DepTargetType } from '../src/types';
|
||
import * as utils from '../src/utils';
|
||
|
||
describe('utils', () => {
|
||
test('createCodeBlockTarget', () => {
|
||
const target = utils.createCodeBlockTarget('code_5316', {
|
||
name: 'code',
|
||
content: () => false,
|
||
params: [],
|
||
});
|
||
|
||
expect(target.id).toBe('code_5316');
|
||
expect(target.name).toBe('code');
|
||
expect(target.type).toBe('code-block');
|
||
|
||
const isTarget = target.isTarget('created', {
|
||
hookType: 'code',
|
||
hookData: [
|
||
{
|
||
codeId: 'code_5336',
|
||
params: {
|
||
studentName: 'lisa',
|
||
age: 14,
|
||
},
|
||
},
|
||
{
|
||
codeId: 'code_5316',
|
||
params: {},
|
||
},
|
||
],
|
||
});
|
||
|
||
expect(isTarget).toBeTruthy();
|
||
|
||
const target1 = utils.createCodeBlockTarget('1', {
|
||
name: 'code',
|
||
content: () => false,
|
||
params: [],
|
||
});
|
||
|
||
const isTarget1 = target1.isTarget('created', {
|
||
hookType: 'code',
|
||
hookData: [
|
||
{
|
||
codeId: 'code_5316',
|
||
params: {},
|
||
},
|
||
],
|
||
});
|
||
|
||
expect(isTarget1).toBeFalsy();
|
||
});
|
||
|
||
test('isIncludeArrayField', () => {
|
||
const arrayFields: DataSchema[] = [{ fields: [{ name: 'a', type: 'string' }], type: 'array', name: 'array' }];
|
||
const objectFields: DataSchema[] = [{ fields: [{ name: 'a', type: 'string' }], type: 'object', name: 'object' }];
|
||
|
||
expect(utils.isIncludeArrayField(['array', 'a'], arrayFields)).toBeTruthy();
|
||
|
||
expect(utils.isIncludeArrayField(['array', '0', 'a'], arrayFields)).toBeFalsy();
|
||
expect(utils.isIncludeArrayField(['array', '0'], arrayFields)).toBeFalsy();
|
||
expect(utils.isIncludeArrayField(['array'], arrayFields)).toBeFalsy();
|
||
|
||
expect(utils.isIncludeArrayField(['object'], objectFields)).toBeFalsy();
|
||
expect(utils.isIncludeArrayField(['object', 'a'], objectFields)).toBeFalsy();
|
||
});
|
||
|
||
test('isDataSourceTemplate', () => {
|
||
expect(utils.isDataSourceTemplate('xxx${dsId.field}xxx${dsId.field}', { id: 'dsId', fields: [] })).toBeTruthy();
|
||
expect(utils.isDataSourceTemplate('${dsId.field}', { id: 'dsId', fields: [] })).toBeTruthy();
|
||
expect(utils.isDataSourceTemplate('${dsId}', { id: 'dsId', fields: [] })).toBeTruthy();
|
||
expect(utils.isDataSourceTemplate('${dsId.field}', { id: 'dsId1', fields: [] })).toBeFalsy();
|
||
expect(utils.isDataSourceTemplate('${dsId.field', { id: 'dsId', fields: [] })).toBeFalsy();
|
||
});
|
||
|
||
test('isSpecificDataSourceTemplate', () => {
|
||
expect(
|
||
utils.isSpecificDataSourceTemplate(
|
||
{
|
||
isBindDataSourceField: true,
|
||
dataSourceId: 'id',
|
||
template: 'xxx${field}xxx',
|
||
},
|
||
'id',
|
||
),
|
||
).toBeTruthy();
|
||
|
||
expect(
|
||
utils.isSpecificDataSourceTemplate(
|
||
{
|
||
isBindDataSourceField: true,
|
||
template: 'xxx${field}xxx',
|
||
},
|
||
'id',
|
||
),
|
||
).toBeFalsy();
|
||
|
||
expect(
|
||
utils.isSpecificDataSourceTemplate(
|
||
{
|
||
isBindDataSourceField: true,
|
||
dataSourceId: 'id',
|
||
template: 123,
|
||
},
|
||
'id',
|
||
),
|
||
).toBeFalsy();
|
||
|
||
expect(
|
||
utils.isSpecificDataSourceTemplate(
|
||
{
|
||
isBindDataSourceField: true,
|
||
dataSourceId: 'id',
|
||
template: 'xxx${field}xxx',
|
||
},
|
||
'id1',
|
||
),
|
||
).toBeFalsy();
|
||
});
|
||
|
||
test('isUseDataSourceField', () => {
|
||
expect(utils.isUseDataSourceField([`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}dsID`, 'field'], 'dsID')).toBeTruthy();
|
||
expect(utils.isUseDataSourceField([`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}dsID`, 'field'], 'dsID1')).toBeFalsy();
|
||
expect(utils.isUseDataSourceField(['abcdsID', 'field'], 'dsID')).toBeFalsy();
|
||
expect(utils.isUseDataSourceField([123, 'field'], 123)).toBeFalsy();
|
||
});
|
||
|
||
test('isDataSourceTemplateNotIncludeArrayField', () => {
|
||
const arrayFields: DataSchema[] = [{ fields: [{ name: 'a', type: 'string' }], type: 'array', name: 'array' }];
|
||
const objectFields: DataSchema[] = [{ fields: [{ name: 'a', type: 'string' }], type: 'object', name: 'object' }];
|
||
|
||
expect(
|
||
utils.isDataSourceTemplateNotIncludeArrayField('${dsId.array}', {
|
||
type: 'base',
|
||
id: 'dsId',
|
||
methods: [],
|
||
fields: arrayFields,
|
||
events: [],
|
||
}),
|
||
).toBeTruthy();
|
||
|
||
expect(
|
||
utils.isDataSourceTemplateNotIncludeArrayField('${dsId.array}${dsId.object.a}', {
|
||
type: 'base',
|
||
id: 'dsId',
|
||
methods: [],
|
||
fields: [...arrayFields, ...objectFields],
|
||
events: [],
|
||
}),
|
||
).toBeTruthy();
|
||
|
||
expect(
|
||
utils.isDataSourceTemplateNotIncludeArrayField('${dsId.array.a}${dsId.object.a}', {
|
||
type: 'base',
|
||
id: 'dsId',
|
||
methods: [],
|
||
fields: [...arrayFields, ...objectFields],
|
||
events: [],
|
||
}),
|
||
).toBeFalsy();
|
||
|
||
expect(
|
||
utils.isDataSourceTemplateNotIncludeArrayField('${dsId.array}${dsId.array.a}', {
|
||
type: 'base',
|
||
id: 'dsId',
|
||
methods: [],
|
||
fields: arrayFields,
|
||
events: [],
|
||
}),
|
||
).toBeFalsy();
|
||
|
||
expect(
|
||
utils.isDataSourceTemplateNotIncludeArrayField('${dsId.array.a}', {
|
||
type: 'base',
|
||
id: 'dsId',
|
||
methods: [],
|
||
fields: arrayFields,
|
||
events: [],
|
||
}),
|
||
).toBeFalsy();
|
||
|
||
expect(
|
||
utils.isDataSourceTemplateNotIncludeArrayField('${dsId.array[1].a}', {
|
||
type: 'base',
|
||
id: 'dsId',
|
||
methods: [],
|
||
fields: arrayFields,
|
||
events: [],
|
||
}),
|
||
).toBeTruthy();
|
||
});
|
||
|
||
test('isDataSourceTarget', () => {
|
||
const ds = { id: 'ds_1', fields: [{ name: 'name', type: 'string' }] as DataSchema[] };
|
||
|
||
expect(utils.isDataSourceTarget(ds, 'k', null)).toBe(false);
|
||
expect(utils.isDataSourceTarget(ds, 'k', 123)).toBe(false);
|
||
|
||
expect(utils.isDataSourceTarget(ds, `${NODE_CONDS_KEY}_x`, '${ds_1.name}')).toBe(false);
|
||
|
||
expect(utils.isDataSourceTarget(ds, 'text', '${ds_1.name}')).toBe(true);
|
||
expect(utils.isDataSourceTarget(ds, 'text', '${other.name}')).toBe(false);
|
||
|
||
expect(utils.isDataSourceTarget(ds, 'text', { isBindDataSource: true, dataSourceId: 'ds_1' })).toBe(true);
|
||
expect(utils.isDataSourceTarget(ds, 'text', { isBindDataSource: true, dataSourceId: 'other' })).toBe(false);
|
||
|
||
expect(
|
||
utils.isDataSourceTarget(ds, 'text', {
|
||
isBindDataSourceField: true,
|
||
dataSourceId: 'ds_1',
|
||
template: 'foo${name}',
|
||
}),
|
||
).toBe(true);
|
||
|
||
expect(utils.isDataSourceTarget(ds, 'text', [`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}ds_1`, 'name'])).toBe(true);
|
||
|
||
expect(
|
||
utils.isDataSourceTarget(
|
||
{ id: 'ds_1', fields: [{ name: 'arr', type: 'array', fields: [{ name: 'a' }] }] as DataSchema[] },
|
||
'text',
|
||
[`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}ds_1`, 'arr', 'a'],
|
||
true,
|
||
),
|
||
).toBe(true);
|
||
});
|
||
|
||
test('isDataSourceCondTarget', () => {
|
||
const ds = { id: 'ds_1', fields: [{ name: 'name' }] as DataSchema[] };
|
||
|
||
expect(utils.isDataSourceCondTarget(ds, 'k', 'not-array')).toBe(false);
|
||
expect(utils.isDataSourceCondTarget(ds, 'k', null as any)).toBe(false);
|
||
|
||
expect(utils.isDataSourceCondTarget(ds, `${NODE_CONDS_KEY}_x`, ['ds_1', 'name'])).toBe(true);
|
||
expect(utils.isDataSourceCondTarget(ds, 'k', ['ds_1', 'name'])).toBe(false);
|
||
expect(utils.isDataSourceCondTarget(ds, `${NODE_CONDS_KEY}_x`, ['other', 'name'])).toBe(false);
|
||
expect(utils.isDataSourceCondTarget(ds, `${NODE_CONDS_KEY}_x`, ['ds_1', 'unknown'])).toBe(false);
|
||
});
|
||
|
||
test('createDataSourceTarget / Cond / Method', () => {
|
||
const ds = { id: 'ds_1', fields: [{ name: 'name' }] as DataSchema[] };
|
||
const t1 = utils.createDataSourceTarget(ds);
|
||
expect(t1.type).toBe(DepTargetType.DATA_SOURCE);
|
||
expect(t1.isTarget('text', '${ds_1.name}')).toBe(true);
|
||
|
||
const t2 = utils.createDataSourceCondTarget(ds);
|
||
expect(t2.type).toBe(DepTargetType.DATA_SOURCE_COND);
|
||
expect(t2.isTarget(`${NODE_CONDS_KEY}_x`, ['ds_1', 'name'])).toBe(true);
|
||
|
||
const t3 = utils.createDataSourceMethodTarget({
|
||
id: 'ds_1',
|
||
methods: [{ name: 'load', content: () => undefined, params: [] } as any],
|
||
fields: [{ name: 'name' }] as DataSchema[],
|
||
});
|
||
expect(t3.type).toBe(DepTargetType.DATA_SOURCE_METHOD);
|
||
expect(t3.isTarget('k', ['ds_1', 'load'])).toBe(true);
|
||
expect(t3.isTarget('k', ['ds_1', 'name'])).toBe(false);
|
||
expect(t3.isTarget('k', ['other', 'load'])).toBe(false);
|
||
expect(t3.isTarget('k', 'not-array')).toBe(false);
|
||
expect(t3.isTarget('k', ['ds_1', ''])).toBe(false);
|
||
expect(t3.isTarget('k', ['ds_1', 'unknown'])).toBe(true);
|
||
});
|
||
|
||
test('内置 target 带可序列化描述,可在 worker 中重建', () => {
|
||
const ds = { id: 'ds_1', fields: [{ name: 'name' }] as DataSchema[] };
|
||
|
||
expect(utils.createCodeBlockTarget('code_1', { name: 'fn', content: () => false, params: [] }).descriptor).toEqual({
|
||
type: DepTargetType.CODE_BLOCK,
|
||
id: 'code_1',
|
||
codeBlock: { name: 'fn' },
|
||
});
|
||
const dsWithMethods = {
|
||
...ds,
|
||
methods: [{ name: 'load', content: () => undefined, params: [] } as any],
|
||
};
|
||
// DATA_SOURCE / COND 只带 id + fields,不透传完整数据源对象
|
||
expect(utils.createDataSourceTarget(dsWithMethods).descriptor).toEqual({
|
||
type: DepTargetType.DATA_SOURCE,
|
||
ds: { id: 'ds_1', fields: ds.fields },
|
||
});
|
||
expect(utils.createDataSourceCondTarget(dsWithMethods).descriptor).toEqual({
|
||
type: DepTargetType.DATA_SOURCE_COND,
|
||
ds: { id: 'ds_1', fields: ds.fields },
|
||
});
|
||
// METHOD 描述只保留名称,不带 content 等函数
|
||
expect(utils.createDataSourceMethodTarget(dsWithMethods).descriptor).toEqual({
|
||
type: DepTargetType.DATA_SOURCE_METHOD,
|
||
ds: { id: 'ds_1', methods: [{ name: 'load' }], fields: [{ name: 'name' }] },
|
||
});
|
||
// 自定义 target 无法序列化 isTarget,因此没有描述
|
||
expect(new Target({ id: 'custom', isTarget: () => true }).descriptor).toBeUndefined();
|
||
});
|
||
|
||
test('createTargetByDescriptor 用描述重建出等价的 target', () => {
|
||
const ds = { id: 'ds_1', fields: [{ name: 'name' }] as DataSchema[] };
|
||
|
||
const codeBlockTarget = utils.createTargetByDescriptor({
|
||
type: DepTargetType.CODE_BLOCK,
|
||
id: 'code_1',
|
||
codeBlock: { name: 'fn' },
|
||
});
|
||
expect(codeBlockTarget.type).toBe(DepTargetType.CODE_BLOCK);
|
||
expect(codeBlockTarget.name).toBe('fn');
|
||
expect(codeBlockTarget.isTarget('created', 'code_1')).toBe(true);
|
||
|
||
const dsTarget = utils.createTargetByDescriptor({ type: DepTargetType.DATA_SOURCE, ds });
|
||
expect(dsTarget.type).toBe(DepTargetType.DATA_SOURCE);
|
||
expect(dsTarget.isTarget('text', '${ds_1.name}')).toBe(true);
|
||
|
||
const condTarget = utils.createTargetByDescriptor({ type: DepTargetType.DATA_SOURCE_COND, ds });
|
||
expect(condTarget.isTarget(`${NODE_CONDS_KEY}_x`, ['ds_1', 'name'])).toBe(true);
|
||
|
||
const methodTarget = utils.createTargetByDescriptor(
|
||
{ type: DepTargetType.DATA_SOURCE_METHOD, ds: { ...ds, methods: [] } },
|
||
{ n1: { name: 'n1', keys: ['k'] } },
|
||
);
|
||
expect(methodTarget.isTarget('k', ['ds_1', 'load'])).toBe(true);
|
||
expect(methodTarget.deps.n1.keys).toEqual(['k']);
|
||
|
||
expect(() => utils.createTargetByDescriptor({ type: 'unknown' } as any)).toThrow();
|
||
});
|
||
|
||
test('traverseTarget 遍历所有 / 指定 type', () => {
|
||
const t1 = new Target({ id: '1', isTarget: () => true, type: 'a' });
|
||
const t2 = new Target({ id: '2', isTarget: () => true, type: 'b' });
|
||
const list = {
|
||
a: { 1: t1 },
|
||
b: { 2: t2 },
|
||
};
|
||
const visited: string[] = [];
|
||
utils.traverseTarget(list, (t) => visited.push(`${t.type}:${t.id}`));
|
||
expect(visited).toEqual(expect.arrayContaining(['a:1', 'b:2']));
|
||
|
||
const visitedA: string[] = [];
|
||
utils.traverseTarget(list, (t) => visitedA.push(`${t.type}:${t.id}`), 'a');
|
||
expect(visitedA).toEqual(['a:1']);
|
||
|
||
const visitedX: string[] = [];
|
||
utils.traverseTarget(list, (t) => visitedX.push(`${t.type}:${t.id}`), 'not-exist');
|
||
expect(visitedX).toEqual([]);
|
||
});
|
||
});
|