This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { Container } from 'inversify';
|
||||
import {
|
||||
ASTFactory,
|
||||
ScopeChain,
|
||||
VariableContainerModule,
|
||||
} from '@flowgram.ai/variable-core';
|
||||
import { createPlaygroundContainer } from '@flowgram.ai/core';
|
||||
import {
|
||||
FreeLayoutScopeChain,
|
||||
FixedLayoutScopeChain,
|
||||
FlowNodeVariableData,
|
||||
VariableChainConfig,
|
||||
GlobalScope,
|
||||
bindGlobalScope,
|
||||
ScopeChainTransformService,
|
||||
} from '../src';
|
||||
import { EntityManager } from '@flowgram.ai/core';
|
||||
import { VariableEngine } from '@flowgram.ai/variable-core';
|
||||
import {
|
||||
FlowDocument,
|
||||
FlowDocumentContainerModule,
|
||||
} from '@flowgram.ai/document';
|
||||
import { WorkflowDocumentContainerModule } from '@flowgram.ai/free-layout-core';
|
||||
|
||||
export interface TestConfig extends VariableChainConfig {
|
||||
enableGlobalScope?: boolean;
|
||||
onInit?: (container: Container) => void;
|
||||
runExtraTest?: (container: Container) => void
|
||||
}
|
||||
|
||||
export function getContainer(layout: 'free' | 'fixed', config?: TestConfig): Container {
|
||||
const { enableGlobalScope, onInit, runExtraTest, ...layoutConfig } = config || {};
|
||||
|
||||
const container = createPlaygroundContainer() as Container;
|
||||
container.load(VariableContainerModule);
|
||||
container.load(FlowDocumentContainerModule);
|
||||
|
||||
if (layout === 'free') {
|
||||
container.load(WorkflowDocumentContainerModule);
|
||||
// container.get(WorkflowLinesManager).registerContribution(WorkflowSimpleLineContribution);
|
||||
//container.get(WorkflowLinesManager).switchLineType(WorkflowSimpleLineContribution.type);
|
||||
}
|
||||
|
||||
if (layoutConfig) {
|
||||
container.bind(VariableChainConfig).toConstantValue(layoutConfig);
|
||||
}
|
||||
if (layout === 'free') {
|
||||
container.bind(ScopeChain).to(FreeLayoutScopeChain).inSingletonScope();
|
||||
}
|
||||
if (layout === 'fixed') {
|
||||
container.bind(ScopeChain).to(FixedLayoutScopeChain).inSingletonScope();
|
||||
}
|
||||
|
||||
container.bind(ScopeChainTransformService).toSelf().inSingletonScope();
|
||||
|
||||
bindGlobalScope(container.bind.bind(container))
|
||||
|
||||
const entityManager = container.get<EntityManager>(EntityManager);
|
||||
const variableEngine = container.get<VariableEngine>(VariableEngine);
|
||||
const document = container.get<FlowDocument>(FlowDocument);
|
||||
|
||||
if (enableGlobalScope) {
|
||||
// when get global scope, it will auto create it if not exists
|
||||
container.get(GlobalScope).setVar(ASTFactory.createVariableDeclaration({
|
||||
key: 'GlobalScope',
|
||||
type: ASTFactory.createString(),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩展 FlowNodeVariableData
|
||||
*/
|
||||
entityManager.registerEntityData(
|
||||
FlowNodeVariableData,
|
||||
() => ({ variableEngine } as any),
|
||||
);
|
||||
document.registerNodeDatas(FlowNodeVariableData);
|
||||
|
||||
onInit?.(container);
|
||||
|
||||
return container;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { FlowDocumentJSON } from '@flowgram.ai/document';
|
||||
|
||||
export const fixLayout1: FlowDocumentJSON = {
|
||||
nodes: [
|
||||
{
|
||||
type: 'start',
|
||||
meta: {
|
||||
isStart: true,
|
||||
},
|
||||
id: 'start',
|
||||
},
|
||||
{
|
||||
type: 'getRecords',
|
||||
id: 'getRecords_07e97c55832',
|
||||
},
|
||||
{
|
||||
type: 'loop',
|
||||
id: 'forEach_260a8f85ff2',
|
||||
blocks: [
|
||||
{
|
||||
type: 'createRecord',
|
||||
id: 'createRecord_8f85ff2c11d',
|
||||
},
|
||||
{
|
||||
type: 'dynamicSplit',
|
||||
id: 'exclusiveSplit_ff2c11d0fb4',
|
||||
blocks: [
|
||||
{
|
||||
id: 'branch_f2c11d0fb42',
|
||||
},
|
||||
{
|
||||
id: 'branch_2c11d0fb42c',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'dynamicSplit',
|
||||
id: 'exclusiveSplit_88dbf2c60ae',
|
||||
meta: {
|
||||
defaultExpanded: true,
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
id: 'branch_8dbf2c60aee',
|
||||
blocks: [
|
||||
{
|
||||
type: 'dynamicSplit',
|
||||
id: 'exclusiveSplit_a59afaadc9a',
|
||||
blocks: [
|
||||
{
|
||||
id: 'branch_59afaadc9ac',
|
||||
},
|
||||
{
|
||||
id: 'branch_9afaadc9acd',
|
||||
blocks: [
|
||||
{
|
||||
type: 'deleteRecords',
|
||||
id: 'deleteRecords_c32807e97c5',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'branch_dbf2c60aee4',
|
||||
blocks: [
|
||||
{
|
||||
type: 'updateRecords',
|
||||
id: 'updateRecords_7ed2a172c32',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'end',
|
||||
id: 'end',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { WorkflowJSON } from "@flowgram.ai/free-layout-core";
|
||||
|
||||
export const freeLayout1: WorkflowJSON = {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "start_0",
|
||||
"type": "start",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": -264,
|
||||
"y": -79
|
||||
}
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "end_0",
|
||||
"type": "end",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 1515,
|
||||
"y": -191
|
||||
}
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "base_1",
|
||||
"type": "base",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 103.5,
|
||||
"y": -122.5
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "base_2",
|
||||
"type": "base",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 525.5,
|
||||
"y": -250.5
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "loop_1",
|
||||
"type": "loop",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 541.5,
|
||||
"y": 42.5
|
||||
},
|
||||
"canvasPosition": {
|
||||
"x": 1154.5,
|
||||
"y": 206.5
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"target": {
|
||||
"meta": {
|
||||
"name": "循环输出_"
|
||||
},
|
||||
"type": "Boolean"
|
||||
}
|
||||
},
|
||||
"blocks": [
|
||||
{
|
||||
"id": "base_in_loop_1",
|
||||
"type": "base",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 40,
|
||||
"y": 100
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "base_in_loop_2",
|
||||
"type": "base",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 62,
|
||||
"y": 299
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "base_in_loop_3",
|
||||
"type": "base",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 457,
|
||||
"y": 188
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"sourceNodeID": "base_in_loop_1",
|
||||
"targetNodeID": "base_in_loop_3"
|
||||
},
|
||||
{
|
||||
"sourceNodeID": "base_in_loop_2",
|
||||
"targetNodeID": "base_in_loop_3"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "base_3",
|
||||
"type": "base",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 1063.5,
|
||||
"y": -52.987060546875
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"sourceNodeID": "base_2",
|
||||
"targetNodeID": "end_0"
|
||||
},
|
||||
{
|
||||
"sourceNodeID": "base_3",
|
||||
"targetNodeID": "end_0"
|
||||
},
|
||||
{
|
||||
"sourceNodeID": "start_0",
|
||||
"targetNodeID": "base_1"
|
||||
},
|
||||
{
|
||||
"sourceNodeID": "base_1",
|
||||
"targetNodeID": "base_2"
|
||||
},
|
||||
{
|
||||
"sourceNodeID": "base_1",
|
||||
"targetNodeID": "loop_1"
|
||||
},
|
||||
{
|
||||
"sourceNodeID": "loop_1",
|
||||
"targetNodeID": "base_3",
|
||||
"sourcePortID": "loop-output"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { VariableEngine } from '@flowgram.ai/variable-core';
|
||||
import { ASTKind } from '@flowgram.ai/variable-core';
|
||||
import { FlowDocument, FlowDocumentJSON } from '@flowgram.ai/document';
|
||||
import {
|
||||
FlowNodeVariableData,
|
||||
} from '../src';
|
||||
import { TestConfig, getContainer } from './container'
|
||||
|
||||
export const runFixedLayoutTest = (testName:string, spec: FlowDocumentJSON, config?: TestConfig) => {
|
||||
describe(testName, () => {
|
||||
const container = getContainer('fixed', config);
|
||||
const flowDocument = container.get(FlowDocument);
|
||||
const variableEngine = container.get(VariableEngine);
|
||||
|
||||
variableEngine.onScopeChange(action => {
|
||||
const { type, scope } = action;
|
||||
|
||||
// 作用域默认创建一个默认变量
|
||||
if (type === 'add') {
|
||||
scope.ast.set('/', {
|
||||
kind: ASTKind.VariableDeclaration,
|
||||
type: ASTKind.String,
|
||||
key: String(scope.id),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 创建一个测试作用域
|
||||
variableEngine.createScope('testScope');
|
||||
|
||||
const traverseVariableDatas = () =>
|
||||
flowDocument
|
||||
.getAllNodes()
|
||||
// TODO 包含 $ 的节点不注册 variableData
|
||||
.filter(_node => !_node.id.startsWith('$'))
|
||||
.map(_node => _node.getData(FlowNodeVariableData))
|
||||
.filter(Boolean);
|
||||
|
||||
// 初始化所有节点的私有作用域
|
||||
const initAllNodePrivate = () => {
|
||||
traverseVariableDatas().forEach(_data => {
|
||||
_data.initPrivate();
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化所有节点的可用变量
|
||||
const printAllNodeAvailableMapping = (_scopeType: 'public' | 'private' = 'public') =>
|
||||
traverseVariableDatas().reduce((acm, _data) => {
|
||||
const scope = _data[_scopeType]!;
|
||||
acm.set(String(scope.id), scope.available.variableKeys);
|
||||
|
||||
return acm;
|
||||
}, new Map<string, string[]>());
|
||||
|
||||
// 初始化所有节点的覆盖作用域
|
||||
const printAllCovers = (_scopeType: 'public' | 'private' = 'public') =>
|
||||
traverseVariableDatas().reduce((acm, _data) => {
|
||||
const scope = _data[_scopeType]!;
|
||||
acm.set(
|
||||
String(scope.id),
|
||||
scope.coverScopes.map(_scope => String(_scope.id)),
|
||||
);
|
||||
|
||||
return acm;
|
||||
}, new Map<string, string[]>());
|
||||
|
||||
flowDocument.fromJSON(spec);
|
||||
|
||||
test('test get Deps', () => {
|
||||
expect(printAllNodeAvailableMapping()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('test get Covers', () => {
|
||||
expect(printAllCovers()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('test get Deps After Init Private', () => {
|
||||
initAllNodePrivate();
|
||||
expect(printAllNodeAvailableMapping()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('test get private scope Deps', () => {
|
||||
expect(printAllNodeAvailableMapping('private')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('test get Covers After Init Private', () => {
|
||||
expect(printAllCovers()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('test get private scope Covers', () => {
|
||||
expect(printAllCovers('private')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('test sort', () => {
|
||||
expect(variableEngine.getAllScopes({ sort: true }).map(_scope => _scope.id)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
config?.runExtraTest?.(container);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { VariableEngine } from '@flowgram.ai/variable-core';
|
||||
import { ASTKind } from '@flowgram.ai/variable-core';
|
||||
import {
|
||||
FlowNodeVariableData
|
||||
} from '../src';
|
||||
import { TestConfig, getContainer } from './container';
|
||||
import { WorkflowDocument, WorkflowJSON } from '@flowgram.ai/free-layout-core';
|
||||
|
||||
export const runFreeLayoutTest = (testName: string, spec: WorkflowJSON, config?: TestConfig) => {
|
||||
describe(testName, async () => {
|
||||
const container = getContainer('free', config);
|
||||
const flowDocument = container.get(WorkflowDocument);
|
||||
const variableEngine = container.get(VariableEngine);
|
||||
|
||||
variableEngine.onScopeChange(action => {
|
||||
const { type, scope } = action;
|
||||
|
||||
// 作用域默认创建一个默认变量
|
||||
if (type === 'add') {
|
||||
scope.ast.set('/', {
|
||||
kind: ASTKind.VariableDeclaration,
|
||||
type: ASTKind.String,
|
||||
key: String(scope.id),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 创建一个全局作用域
|
||||
variableEngine.createScope('testScope');
|
||||
|
||||
const traverseVariableDatas = () =>
|
||||
flowDocument
|
||||
.getAllNodes()
|
||||
// TODO 包含 $ 的节点不注册 variableData
|
||||
.filter(_node => !_node.id.startsWith('$'))
|
||||
.map(_node => _node.getData(FlowNodeVariableData))
|
||||
.filter(Boolean);
|
||||
|
||||
// 初始化所有节点的私有作用域
|
||||
const initAllNodePrivate = () => {
|
||||
traverseVariableDatas().forEach(_data => {
|
||||
_data.initPrivate();
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化所有节点的可用变量
|
||||
const printAllNodeAvailableMapping = (_scopeType: 'public' | 'private' = 'public') =>
|
||||
traverseVariableDatas().reduce((acm, _data) => {
|
||||
const scope = _data[_scopeType]!;
|
||||
acm.set(String(scope.id), scope.available.variableKeys);
|
||||
|
||||
return acm;
|
||||
}, new Map<string, string[]>());
|
||||
|
||||
// 初始化所有节点的覆盖作用域
|
||||
const printAllCovers = (_scopeType: 'public' | 'private' = 'public') =>
|
||||
traverseVariableDatas().reduce((acm, _data) => {
|
||||
const scope = _data[_scopeType]!;
|
||||
acm.set(
|
||||
String(scope.id),
|
||||
scope.coverScopes.map(_scope => String(_scope.id)),
|
||||
);
|
||||
|
||||
return acm;
|
||||
}, new Map<string, string[]>());
|
||||
|
||||
await flowDocument.fromJSON(spec);
|
||||
|
||||
test('test get Deps', () => {
|
||||
expect(printAllNodeAvailableMapping()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('test get Covers', () => {
|
||||
expect(printAllCovers()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('test get Deps After Init Private', () => {
|
||||
initAllNodePrivate();
|
||||
expect(printAllNodeAvailableMapping()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('test get private scope Deps', () => {
|
||||
expect(printAllNodeAvailableMapping('private')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('test get Covers After Init Private', () => {
|
||||
expect(printAllCovers()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('test get private scope Covers', () => {
|
||||
expect(printAllCovers('private')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('test sort', () => {
|
||||
expect(variableEngine.getAllScopes({ sort: true }).map(_scope => _scope.id)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
config?.runExtraTest?.(container);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user