2026-05-14 15:26:22 +08:00

448 lines
11 KiB
TypeScript

import { describe, expect, test, vi } from 'vitest';
import { MApp, NodeType } from '@tmagic/schema';
import App from '../src/App';
import TMagicIteratorContainer from '../src/IteratorContainer';
import Node from '../src/Node';
const createAppDsl = (pageLength: number, nodeLength = 0) => {
const dsl: MApp = {
type: NodeType.ROOT,
id: 'app_1',
dataSources: [
{
id: 'ds_1',
fields: [
{
type: 'array',
name: 'array',
title: 'array',
enable: true,
fields: [
{
type: 'array',
name: 'arr',
title: 'arr',
defaultValue: [],
enable: true,
fields: [],
},
],
},
],
events: [],
methods: [],
type: 'base',
},
],
dataSourceDeps: {},
dataSourceCondDeps: {},
items: [
...new Array(pageLength)
.fill({
type: NodeType.PAGE,
items: new Array(nodeLength)
.fill({
type: 'text',
})
.map((node, index) => ({
...node,
id: `text_${index}`,
})),
})
.map((page, index) => ({
...page,
id: `page_${index}`,
})),
{
type: NodeType.PAGE_FRAGMENT,
id: 'page_fragment_1',
items: [
{
type: 'text',
id: 'text_page_fragment',
text: 'text_page_fragment',
},
],
},
],
};
return dsl;
};
describe('App', () => {
test('instance', () => {
const app = new App({});
expect(app).toBeInstanceOf(App);
});
test('page', () => {
const app = new App({
config: createAppDsl(2),
});
expect(app.getNode('page_0')?.data.id).toBe('page_0');
expect(app.page?.data.id).toBe('page_0');
app.setConfig(createAppDsl(3), 'page_1');
expect(app.page?.data.id).toBe('page_1');
app.setPage('page_2');
expect(app.page?.data.id).toBe('page_2');
});
test('node', () => {
const app = new App({
config: createAppDsl(1, 10),
});
expect(app.getNode('text_1')?.data.id).toBe('text_1');
});
test('iterator-container', () => {
const dsl = createAppDsl(1, 10);
dsl.items[0].items.push({
type: 'iterator-container',
id: 'iterator-container_1',
items: [
{
type: 'text',
id: 'text',
},
],
});
const app = new App({
config: dsl,
});
const ic = app.getNode('iterator-container_1') as unknown as TMagicIteratorContainer;
expect(ic?.data.id).toBe('iterator-container_1');
ic?.setNodes(
[
{
type: 'text',
id: 'text',
text: '1',
},
{
type: 'page-fragment-container',
id: 'page_fragment_container_1',
pageFragmentId: 'page_fragment_1',
},
{
type: 'iterator-container',
id: 'iterator-container_11',
items: [
{
type: 'text',
id: 'text',
},
],
},
],
0,
);
ic?.setNodes(
[
{
type: 'text',
id: 'text',
text: '2',
},
{
type: 'iterator-container',
id: 'iterator-container_11',
items: [
{
type: 'text',
id: 'text',
},
],
},
],
1,
);
expect(app.getNode('text', { iteratorContainerId: ['iterator-container_1'], iteratorIndex: [0] })?.data.text).toBe(
'1',
);
expect(app.getNode('text', { iteratorContainerId: ['iterator-container_1'], iteratorIndex: [1] })?.data.text).toBe(
'2',
);
expect(
app.getNode('text_page_fragment', { iteratorContainerId: ['iterator-container_1'], iteratorIndex: [0] })?.data
.text,
).toBe('text_page_fragment');
const ic1 = app.getNode('iterator-container_11', {
iteratorContainerId: ['iterator-container_1'],
iteratorIndex: [0],
}) as unknown as TMagicIteratorContainer;
ic1?.setNodes(
[
{
type: 'text',
id: 'text',
text: '111',
},
],
0,
);
ic1?.setNodes(
[
{
type: 'text',
id: 'text',
text: '222',
},
],
1,
);
const ic2 = app.getNode('iterator-container_11', {
iteratorContainerId: ['iterator-container_1'],
iteratorIndex: [1],
}) as unknown as TMagicIteratorContainer;
ic2?.setNodes(
[
{
type: 'text',
id: 'text',
text: '11',
},
],
0,
);
ic2?.setNodes(
[
{
type: 'text',
id: 'text',
text: '22',
},
],
1,
);
expect(
app.getNode('text', {
iteratorContainerId: ['iterator-container_1', 'iterator-container_11'],
iteratorIndex: [0, 0],
})?.data.text,
).toBe('111');
expect(
app.getNode('text', {
iteratorContainerId: ['iterator-container_1', 'iterator-container_11'],
iteratorIndex: [0, 1],
})?.data.text,
).toBe('222');
expect(
app.getNode('text', {
iteratorContainerId: ['iterator-container_1', 'iterator-container_11'],
iteratorIndex: [1, 0],
})?.data.text,
).toBe('11');
expect(
app.getNode('text', {
iteratorContainerId: ['iterator-container_1', 'iterator-container_11'],
iteratorIndex: [1, 1],
})?.data.text,
).toBe('22');
ic.resetNodes();
expect(ic2?.nodes.length).toBe(0);
});
});
describe('App 配置/方法/组件注册', () => {
test('platform=editor 时不创建 eventHelper', () => {
const app = new App({ platform: 'editor' });
expect(app.eventHelper).toBeUndefined();
expect(app.platform).toBe('editor');
});
test('disabledFlexible 时不创建 flexible', () => {
const app = new App({ disabledFlexible: true });
expect((app as any).flexible).toBeUndefined();
});
test('设置自定义 iteratorContainerType / pageFragmentContainerType', () => {
const app = new App({
iteratorContainerType: ['my-iter', 'custom-iter'],
pageFragmentContainerType: 'my-frag',
});
expect(app.iteratorContainerType.has('my-iter')).toBe(true);
expect(app.iteratorContainerType.has('custom-iter')).toBe(true);
expect(app.pageFragmentContainerType.has('my-frag')).toBe(true);
});
test('useMock=true 透传到 DataSourceManager', () => {
const app = new App({ useMock: true });
expect(app.useMock).toBe(true);
});
test('registerComponent / resolveComponent / unregisterComponent', () => {
const app = new App({});
const comp = { tag: 'x' };
app.registerComponent('my', comp);
expect(app.resolveComponent('my')).toBe(comp);
app.unregisterComponent('my');
expect(app.resolveComponent('my')).toBeUndefined();
});
test('registerNode 静态方法存入 nodeClassMap', () => {
class Custom extends Node {}
App.registerNode('custom-type', Custom);
expect(App.nodeClassMap.get('custom-type')).toBe(Custom);
});
test('setEnv 接受字符串/Env 实例', () => {
const app = new App({});
app.setEnv();
expect(app.env).toBeDefined();
app.setEnv('Mozilla/5.0');
expect(app.env).toBeDefined();
});
test('getPage / getNode 默认返回当前 page', () => {
const app = new App({
config: {
type: NodeType.ROOT,
id: 'app',
items: [{ type: NodeType.PAGE, id: 'p1', items: [{ id: 'btn', type: 'button' }] }],
},
});
expect(app.getPage()).toBe(app.page);
expect(app.getPage('p1')).toBe(app.page);
expect(app.getPage('not-exist')).toBeUndefined();
expect(app.getNode('btn')?.data.id).toBe('btn');
});
test('setPage 不存在时清空当前 page', () => {
const app = new App({
config: {
type: NodeType.ROOT,
id: 'app',
items: [{ type: NodeType.PAGE, id: 'p1', items: [] }],
},
});
app.setPage('not-exist');
expect(app.page).toBeUndefined();
});
test('runCode 执行代码块', async () => {
const fn = vi.fn();
const app = new App({
config: {
type: NodeType.ROOT,
id: 'app',
items: [{ type: NodeType.PAGE, id: 'p1', items: [] }],
codeBlocks: { c1: { name: 'c1', content: fn, params: [] } },
},
});
await app.runCode('c1', { p: 1 }, []);
expect(fn).toHaveBeenCalled();
});
test('runCode 抛错时进入 errorHandler', async () => {
const errorHandler = vi.fn();
const app = new App({
errorHandler,
config: {
type: NodeType.ROOT,
id: 'app',
items: [{ type: NodeType.PAGE, id: 'p1', items: [] }],
codeBlocks: {
c1: {
name: 'c1',
content: () => {
throw new Error('boom');
},
params: [],
},
},
},
});
await app.runCode('c1', {}, []);
expect(errorHandler).toHaveBeenCalled();
});
test('runDataSourceMethod 调用 schema methods 中的 content', async () => {
const fn = vi.fn().mockResolvedValue('ok');
const app = new App({
config: {
type: NodeType.ROOT,
id: 'app',
items: [{ type: NodeType.PAGE, id: 'p1', items: [] }],
dataSources: [
{
type: 'base',
id: 'ds_1',
fields: [],
methods: [{ name: 'doIt', content: fn, params: [] }],
events: [],
},
],
} as any,
});
await app.runDataSourceMethod('ds_1', 'doIt', { p: 1 }, []);
expect(fn).toHaveBeenCalled();
});
test('runDataSourceMethod 不存在的数据源直接返回', async () => {
const app = new App({
config: {
type: NodeType.ROOT,
id: 'app',
items: [{ type: NodeType.PAGE, id: 'p1', items: [] }],
},
});
await expect(app.runDataSourceMethod('not', 'm', {}, [])).resolves.toBeUndefined();
await expect(app.runDataSourceMethod('', '', {}, [])).resolves.toBeUndefined();
});
test('emit 触发 node 事件时走 eventHelper', () => {
const app = new App({
config: {
type: NodeType.ROOT,
id: 'app',
items: [
{
type: NodeType.PAGE,
id: 'p1',
items: [{ id: 'btn', type: 'button', events: [{ name: 'click', actions: [] }] }],
},
],
} as any,
});
const node = app.getNode('btn')!;
const result = app.emit('click', node, 'arg1');
expect(typeof result).toBe('boolean');
});
test('destroy 清理所有资源', () => {
const app = new App({
config: {
type: NodeType.ROOT,
id: 'app',
items: [{ type: NodeType.PAGE, id: 'p1', items: [{ id: 'btn', type: 'button' }] }],
},
});
app.destroy();
expect(app.page).toBeUndefined();
expect(app.dsl).toBeUndefined();
expect(app.components.size).toBe(0);
});
});