mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-05-14 20:43:57 +00:00
844 lines
23 KiB
TypeScript
844 lines
23 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||
|
||
import TMagicApp, { type MApp, NodeType } from '@tmagic/core';
|
||
|
||
import { createDataSourceManager, DataSource, DataSourceManager } from '@data-source/index';
|
||
|
||
const createDsl = (): MApp => ({
|
||
type: NodeType.ROOT,
|
||
id: 'app_1',
|
||
items: [
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_1',
|
||
items: [
|
||
{
|
||
type: 'text',
|
||
id: 61705611,
|
||
text: '{{ds_bebcb2d5.text}}',
|
||
},
|
||
],
|
||
},
|
||
],
|
||
dataSourceDeps: {
|
||
ds_bebcb2d5: {
|
||
61705611: {
|
||
name: '文本',
|
||
keys: ['text'],
|
||
},
|
||
},
|
||
},
|
||
dataSources: [
|
||
{
|
||
id: 'ds_bebcb2d5',
|
||
type: 'http',
|
||
fields: [
|
||
{
|
||
name: 'text',
|
||
},
|
||
],
|
||
methods: [],
|
||
events: [],
|
||
},
|
||
],
|
||
});
|
||
|
||
afterEach(() => {
|
||
DataSourceManager.clearDataSourceClass();
|
||
});
|
||
|
||
describe('createDataSourceManager - 基础', () => {
|
||
test('instance', () => {
|
||
const manager = createDataSourceManager(new TMagicApp({ config: createDsl() }));
|
||
expect(manager).toBeInstanceOf(DataSourceManager);
|
||
});
|
||
|
||
test('dsl 中没有 dataSources 时返回 undefined', () => {
|
||
const app = new TMagicApp({
|
||
config: {
|
||
type: NodeType.ROOT,
|
||
id: 'app_no_ds',
|
||
items: [],
|
||
},
|
||
});
|
||
const manager = createDataSourceManager(app);
|
||
expect(manager).toBeUndefined();
|
||
});
|
||
|
||
test('app 没有 dsl 时返回 undefined', () => {
|
||
const app = new TMagicApp({});
|
||
const manager = createDataSourceManager(app);
|
||
expect(manager).toBeUndefined();
|
||
});
|
||
|
||
test('useMock 透传到 DataSourceManager', () => {
|
||
const manager = createDataSourceManager(new TMagicApp({ config: createDsl() }), true);
|
||
expect(manager?.useMock).toBe(true);
|
||
});
|
||
|
||
test('initialData 透传到 DataSourceManager', () => {
|
||
const manager = createDataSourceManager(new TMagicApp({ config: createDsl() }), false, {
|
||
ds_bebcb2d5: { text: 'preset' },
|
||
});
|
||
expect(manager?.initialData.ds_bebcb2d5).toEqual({ text: 'preset' });
|
||
expect(manager?.data.ds_bebcb2d5.text).toBe('preset');
|
||
});
|
||
});
|
||
|
||
describe('createDataSourceManager - 初始化阶段编译', () => {
|
||
test('platform!=editor && 存在 dataSourceCondDeps 时按节点写入 condResult', () => {
|
||
const dsl: MApp = {
|
||
type: NodeType.ROOT,
|
||
id: 'app_cond',
|
||
items: [
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_1',
|
||
items: [
|
||
{
|
||
type: 'text',
|
||
id: 'cond_node',
|
||
text: 'hello',
|
||
displayConds: [{ cond: [{ field: ['ds_1', 'a'], op: '=', value: 1 }] }],
|
||
} as any,
|
||
],
|
||
},
|
||
],
|
||
dataSourceCondDeps: {
|
||
ds_1: {
|
||
cond_node: { name: '文本', keys: ['displayConds'] },
|
||
},
|
||
},
|
||
dataSourceDeps: {},
|
||
dataSources: [
|
||
{
|
||
id: 'ds_1',
|
||
type: 'base',
|
||
fields: [{ name: 'a', defaultValue: 1 }],
|
||
methods: [],
|
||
events: [],
|
||
},
|
||
],
|
||
};
|
||
const app = new TMagicApp({ config: dsl, platform: 'mobile' });
|
||
createDataSourceManager(app);
|
||
const node: any = (app.dsl?.items[0] as any).items[0];
|
||
expect(node.condResult).toBe(true);
|
||
});
|
||
|
||
test('platform=editor 时初始化不写入 condResult', () => {
|
||
const dsl: MApp = {
|
||
type: NodeType.ROOT,
|
||
id: 'app_cond_editor',
|
||
items: [
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_1',
|
||
items: [
|
||
{
|
||
type: 'text',
|
||
id: 'cond_node',
|
||
text: 'hello',
|
||
displayConds: [{ cond: [{ field: ['ds_1', 'a'], op: '=', value: 1 }] }],
|
||
} as any,
|
||
],
|
||
},
|
||
],
|
||
dataSourceCondDeps: {
|
||
ds_1: {
|
||
cond_node: { name: '文本', keys: ['displayConds'] },
|
||
},
|
||
},
|
||
dataSourceDeps: {},
|
||
dataSources: [
|
||
{
|
||
id: 'ds_1',
|
||
type: 'base',
|
||
fields: [{ name: 'a', defaultValue: 1 }],
|
||
methods: [],
|
||
events: [],
|
||
},
|
||
],
|
||
};
|
||
const app = new TMagicApp({ config: dsl, platform: 'editor' });
|
||
createDataSourceManager(app);
|
||
const node: any = (app.dsl?.items[0] as any).items[0];
|
||
expect(node.condResult).toBeUndefined();
|
||
});
|
||
|
||
test('存在 dataSourceDeps 时初始化即编译节点字段(模板)', () => {
|
||
const dsl: MApp = {
|
||
type: NodeType.ROOT,
|
||
id: 'app_dep',
|
||
items: [
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_1',
|
||
items: [
|
||
{
|
||
type: 'text',
|
||
id: 'dep_node',
|
||
text: 'hello ${ds_1.name}',
|
||
} as any,
|
||
],
|
||
},
|
||
],
|
||
dataSourceDeps: {
|
||
ds_1: {
|
||
dep_node: { name: '文本', keys: ['text'] },
|
||
},
|
||
},
|
||
dataSources: [
|
||
{
|
||
id: 'ds_1',
|
||
type: 'base',
|
||
fields: [{ name: 'name', defaultValue: 'world' }],
|
||
methods: [],
|
||
events: [],
|
||
},
|
||
],
|
||
};
|
||
const app = new TMagicApp({ config: dsl, platform: 'mobile' });
|
||
createDataSourceManager(app);
|
||
const node: any = (app.dsl?.items[0] as any).items[0];
|
||
expect(node.text).toBe('hello world');
|
||
});
|
||
});
|
||
|
||
describe('createDataSourceManager - jsEngine=nodejs', () => {
|
||
test('nodejs 环境下不监听 change,触发 setData 不会走 update-data', () => {
|
||
const app = new TMagicApp({ config: createDsl(), jsEngine: 'nodejs' });
|
||
const manager = createDataSourceManager(app);
|
||
expect(manager).toBeInstanceOf(DataSourceManager);
|
||
expect(manager?.listenerCount('change')).toBe(0);
|
||
|
||
const updateSpy = vi.fn();
|
||
manager?.on('update-data', updateSpy);
|
||
const ds = manager?.get('ds_bebcb2d5');
|
||
ds?.setData({ text: 'changed' });
|
||
expect(updateSpy).not.toHaveBeenCalled();
|
||
});
|
||
});
|
||
|
||
describe('createDataSourceManager - change 事件', () => {
|
||
let app: TMagicApp;
|
||
let manager: DataSourceManager | undefined;
|
||
|
||
beforeEach(() => {
|
||
const dsl: MApp = {
|
||
type: NodeType.ROOT,
|
||
id: 'app_change',
|
||
items: [
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_1',
|
||
items: [
|
||
{
|
||
type: 'text',
|
||
id: 'text_1',
|
||
text: 'origin ${ds_1.name}',
|
||
} as any,
|
||
],
|
||
},
|
||
],
|
||
dataSourceDeps: {
|
||
ds_1: {
|
||
text_1: { name: '文本', keys: ['text'] },
|
||
},
|
||
},
|
||
dataSources: [
|
||
{
|
||
id: 'ds_1',
|
||
type: 'base',
|
||
fields: [{ name: 'name', defaultValue: 'world' }],
|
||
methods: [],
|
||
events: [],
|
||
},
|
||
],
|
||
};
|
||
app = new TMagicApp({ config: dsl, platform: 'mobile' });
|
||
manager = createDataSourceManager(app);
|
||
});
|
||
|
||
test('change 事件触发后会发出 update-data,并携带新节点 / sourceId / pageId', () => {
|
||
const update = vi.fn();
|
||
manager?.on('update-data', update);
|
||
|
||
const ds = manager?.get('ds_1');
|
||
ds?.setData({ name: 'new' });
|
||
|
||
expect(update).toHaveBeenCalledTimes(1);
|
||
const [newNodes, sourceId, , pageId] = update.mock.calls[0];
|
||
expect(sourceId).toBe('ds_1');
|
||
expect(pageId).toBe('page_1');
|
||
expect(newNodes[0].id).toBe('text_1');
|
||
expect(newNodes[0].text).toBe('origin new');
|
||
});
|
||
|
||
test('change 事件会调用 page.setData 并触发节点 setData', () => {
|
||
const node = app.getNode('text_1');
|
||
const setDataSpy = vi.spyOn(node!, 'setData');
|
||
|
||
const ds = manager?.get('ds_1');
|
||
ds?.setData({ name: 'second' });
|
||
|
||
expect(setDataSpy).toHaveBeenCalled();
|
||
const calledArg = setDataSpy.mock.calls[0][0] as any;
|
||
expect(calledArg.text).toBe('origin second');
|
||
});
|
||
|
||
test('依赖中的节点不存在时不会发出 update-data', () => {
|
||
const update = vi.fn();
|
||
manager?.on('update-data', update);
|
||
|
||
if (app.dsl?.dataSourceDeps) {
|
||
app.dsl.dataSourceDeps = {};
|
||
}
|
||
|
||
const ds = manager?.get('ds_1');
|
||
ds?.setData({ name: 'noop' });
|
||
|
||
expect(update).not.toHaveBeenCalled();
|
||
});
|
||
|
||
test('page 自身被命中时调用 app.page.setData', () => {
|
||
// 把 page 自己加入到依赖中
|
||
if (app.dsl?.dataSourceDeps) {
|
||
app.dsl.dataSourceDeps.ds_1 = {
|
||
page_1: { name: 'page', keys: ['style'] },
|
||
} as any;
|
||
}
|
||
const pageSetData = vi.spyOn(app.page!, 'setData');
|
||
const ds = manager?.get('ds_1');
|
||
ds?.setData({ name: 'X' });
|
||
expect(pageSetData).toHaveBeenCalled();
|
||
const arg: any = pageSetData.mock.calls[0][0];
|
||
expect(arg.id).toBe('page_1');
|
||
});
|
||
|
||
test('page 没有 instance 时通过 replaceChildNode 写回 page.data', () => {
|
||
const ds = manager?.get('ds_1');
|
||
expect(app.page?.instance).toBeFalsy();
|
||
|
||
ds?.setData({ name: 'replaced' });
|
||
|
||
const replacedText = (app.page?.data as any).items[0].text;
|
||
expect(replacedText).toBe('origin replaced');
|
||
});
|
||
});
|
||
|
||
describe('createDataSourceManager - editor 平台', () => {
|
||
test('editor 平台会遍历所有页面,而非仅当前页', () => {
|
||
const dsl: MApp = {
|
||
type: NodeType.ROOT,
|
||
id: 'app_editor',
|
||
items: [
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_1',
|
||
items: [{ type: 'text', id: 'text_a', text: 'a ${ds_1.name}' } as any],
|
||
},
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_2',
|
||
items: [{ type: 'text', id: 'text_b', text: 'b ${ds_1.name}' } as any],
|
||
},
|
||
],
|
||
dataSourceDeps: {
|
||
ds_1: {
|
||
text_a: { name: '文本', keys: ['text'] },
|
||
text_b: { name: '文本', keys: ['text'] },
|
||
},
|
||
},
|
||
dataSources: [
|
||
{
|
||
id: 'ds_1',
|
||
type: 'base',
|
||
fields: [{ name: 'name', defaultValue: 'init' }],
|
||
methods: [],
|
||
events: [],
|
||
},
|
||
],
|
||
};
|
||
const app = new TMagicApp({ config: dsl, platform: 'editor' });
|
||
const manager = createDataSourceManager(app);
|
||
|
||
const update = vi.fn();
|
||
manager?.on('update-data', update);
|
||
|
||
const ds = manager?.get('ds_1');
|
||
ds?.setData({ name: 'V' });
|
||
|
||
expect(update).toHaveBeenCalledTimes(2);
|
||
const pageIds = update.mock.calls.map((c) => c[3]);
|
||
expect(pageIds).toContain('page_1');
|
||
expect(pageIds).toContain('page_2');
|
||
});
|
||
|
||
test('非 editor 平台只处理当前页', () => {
|
||
const dsl: MApp = {
|
||
type: NodeType.ROOT,
|
||
id: 'app_runtime',
|
||
items: [
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_1',
|
||
items: [{ type: 'text', id: 'text_a', text: 'a ${ds_1.name}' } as any],
|
||
},
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_2',
|
||
items: [{ type: 'text', id: 'text_b', text: 'b ${ds_1.name}' } as any],
|
||
},
|
||
],
|
||
dataSourceDeps: {
|
||
ds_1: {
|
||
text_a: { name: '文本', keys: ['text'] },
|
||
text_b: { name: '文本', keys: ['text'] },
|
||
},
|
||
},
|
||
dataSources: [
|
||
{
|
||
id: 'ds_1',
|
||
type: 'base',
|
||
fields: [{ name: 'name', defaultValue: 'init' }],
|
||
methods: [],
|
||
events: [],
|
||
},
|
||
],
|
||
};
|
||
const app = new TMagicApp({ config: dsl, platform: 'mobile', curPage: 'page_1' });
|
||
const manager = createDataSourceManager(app);
|
||
|
||
const update = vi.fn();
|
||
manager?.on('update-data', update);
|
||
|
||
const ds = manager?.get('ds_1');
|
||
ds?.setData({ name: 'V' });
|
||
|
||
expect(update).toHaveBeenCalledTimes(1);
|
||
expect(update.mock.calls[0][3]).toBe('page_1');
|
||
});
|
||
|
||
test('非 editor 平台命中 isPageFragment 分支也会被处理', () => {
|
||
const dsl: MApp = {
|
||
type: NodeType.ROOT,
|
||
id: 'app_pf',
|
||
items: [
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_1',
|
||
items: [{ type: 'text', id: 'text_a', text: 'a' } as any],
|
||
},
|
||
{
|
||
type: NodeType.PAGE_FRAGMENT,
|
||
id: 'pf_1',
|
||
items: [{ type: 'text', id: 'text_b', text: 'b ${ds_1.name}' } as any],
|
||
} as any,
|
||
],
|
||
dataSourceDeps: {
|
||
ds_1: {
|
||
text_b: { name: '文本', keys: ['text'] },
|
||
},
|
||
},
|
||
dataSources: [
|
||
{
|
||
id: 'ds_1',
|
||
type: 'base',
|
||
fields: [{ name: 'name', defaultValue: 'init' }],
|
||
methods: [],
|
||
events: [],
|
||
},
|
||
],
|
||
};
|
||
const app = new TMagicApp({ config: dsl, platform: 'mobile', curPage: 'page_1' });
|
||
const manager = createDataSourceManager(app);
|
||
|
||
const update = vi.fn();
|
||
manager?.on('update-data', update);
|
||
|
||
const ds = manager?.get('ds_1');
|
||
ds?.setData({ name: 'V' });
|
||
|
||
expect(update).toHaveBeenCalledTimes(1);
|
||
expect(update.mock.calls[0][3]).toBe('pf_1');
|
||
});
|
||
});
|
||
|
||
describe('createDataSourceManager - pageFragments 同步', () => {
|
||
test('当 newNode 为 pageFragment 自身时,调用 pageFragment.setData', () => {
|
||
const dsl: MApp = {
|
||
type: NodeType.ROOT,
|
||
id: 'app_pf_self',
|
||
items: [
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_1',
|
||
items: [
|
||
{
|
||
type: 'page-fragment-container',
|
||
id: 'pfc_1',
|
||
pageFragmentId: 'pf_1',
|
||
items: [],
|
||
} as any,
|
||
],
|
||
},
|
||
{
|
||
type: NodeType.PAGE_FRAGMENT,
|
||
id: 'pf_1',
|
||
items: [{ type: 'text', id: 'pf_text', text: 'pf ${ds_1.name}' } as any],
|
||
extra: '${ds_1.name}',
|
||
} as any,
|
||
],
|
||
dataSourceDeps: {
|
||
ds_1: {
|
||
pf_1: { name: 'pf', keys: ['extra'] },
|
||
},
|
||
},
|
||
dataSources: [
|
||
{
|
||
id: 'ds_1',
|
||
type: 'base',
|
||
fields: [{ name: 'name', defaultValue: 'init' }],
|
||
methods: [],
|
||
events: [],
|
||
},
|
||
],
|
||
};
|
||
const app = new TMagicApp({ config: dsl, platform: 'editor', curPage: 'page_1' });
|
||
const manager = createDataSourceManager(app);
|
||
|
||
expect(app.pageFragments.size).toBeGreaterThan(0);
|
||
const pageFragment = app.pageFragments.get('pfc_1')!;
|
||
const pfSetData = vi.spyOn(pageFragment, 'setData');
|
||
|
||
const ds = manager?.get('ds_1');
|
||
ds?.setData({ name: 'X' });
|
||
|
||
expect(pfSetData).toHaveBeenCalled();
|
||
const arg: any = pfSetData.mock.calls[0][0];
|
||
expect(arg.id).toBe('pf_1');
|
||
});
|
||
|
||
test('当 newNode 是 pageFragment 内子节点时,pageFragment 内同步并 replaceChildNode', () => {
|
||
const dsl: MApp = {
|
||
type: NodeType.ROOT,
|
||
id: 'app_pf_child',
|
||
items: [
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_1',
|
||
items: [
|
||
{
|
||
type: 'page-fragment-container',
|
||
id: 'pfc_1',
|
||
pageFragmentId: 'pf_1',
|
||
items: [],
|
||
} as any,
|
||
],
|
||
},
|
||
{
|
||
type: NodeType.PAGE_FRAGMENT,
|
||
id: 'pf_1',
|
||
items: [{ type: 'text', id: 'pf_text', text: 'pf ${ds_1.name}' } as any],
|
||
} as any,
|
||
],
|
||
dataSourceDeps: {
|
||
ds_1: {
|
||
pf_text: { name: 'pf_text', keys: ['text'] },
|
||
},
|
||
},
|
||
dataSources: [
|
||
{
|
||
id: 'ds_1',
|
||
type: 'base',
|
||
fields: [{ name: 'name', defaultValue: 'init' }],
|
||
methods: [],
|
||
events: [],
|
||
},
|
||
],
|
||
};
|
||
const app = new TMagicApp({ config: dsl, platform: 'editor', curPage: 'page_1' });
|
||
const manager = createDataSourceManager(app);
|
||
|
||
const pageFragment = app.pageFragments.get('pfc_1')!;
|
||
const innerNode = pageFragment.getNode('pf_text', { strict: true })!;
|
||
const innerSetData = vi.spyOn(innerNode, 'setData');
|
||
|
||
const ds = manager?.get('ds_1');
|
||
ds?.setData({ name: 'Y' });
|
||
|
||
expect(innerSetData).toHaveBeenCalled();
|
||
const arg: any = innerSetData.mock.calls[0][0];
|
||
expect(arg.text).toBe('pf Y');
|
||
expect((pageFragment.data as any).items[0].text).toBe('pf Y');
|
||
});
|
||
});
|
||
|
||
describe('createDataSourceManager - app.page 不存在', () => {
|
||
test('app.page 缺失时跳过 page.setData / 节点 setData,但仍发出 update-data', () => {
|
||
const dsl: MApp = {
|
||
type: NodeType.ROOT,
|
||
id: 'app_no_page',
|
||
items: [
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_1',
|
||
items: [{ type: 'text', id: 'text_a', text: 'a ${ds_1.name}' } as any],
|
||
},
|
||
],
|
||
dataSourceDeps: {
|
||
ds_1: {
|
||
text_a: { name: '文本', keys: ['text'] },
|
||
},
|
||
},
|
||
dataSources: [
|
||
{
|
||
id: 'ds_1',
|
||
type: 'base',
|
||
fields: [{ name: 'name', defaultValue: 'init' }],
|
||
methods: [],
|
||
events: [],
|
||
},
|
||
],
|
||
};
|
||
// curPage 指向不存在的页,setPage 会调用 deletePage 让 app.page = undefined
|
||
const app = new TMagicApp({ config: dsl, platform: 'editor', curPage: 'not_exist' });
|
||
expect(app.page).toBeUndefined();
|
||
const manager = createDataSourceManager(app);
|
||
|
||
const update = vi.fn();
|
||
manager?.on('update-data', update);
|
||
|
||
const ds = manager?.get('ds_1');
|
||
expect(() => ds?.setData({ name: 'V' })).not.toThrow();
|
||
|
||
expect(update).toHaveBeenCalledTimes(1);
|
||
expect(update.mock.calls[0][3]).toBe('page_1');
|
||
});
|
||
});
|
||
|
||
describe('createDataSourceManager - pageFragment 与被遍历 page 同 id', () => {
|
||
test('editor 平台遍历到 pageFragment 自身页时进入 pageFragment.data.id === page.id 分支', () => {
|
||
const dsl: MApp = {
|
||
type: NodeType.ROOT,
|
||
id: 'app_pf_iter',
|
||
items: [
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_1',
|
||
items: [
|
||
{
|
||
type: 'page-fragment-container',
|
||
id: 'pfc_1',
|
||
pageFragmentId: 'pf_1',
|
||
items: [],
|
||
} as any,
|
||
],
|
||
},
|
||
{
|
||
type: NodeType.PAGE_FRAGMENT,
|
||
id: 'pf_1',
|
||
items: [{ type: 'text', id: 'pf_text', text: 'pf ${ds_1.name}' } as any],
|
||
} as any,
|
||
],
|
||
dataSourceDeps: {
|
||
ds_1: {
|
||
pf_text: { name: 'pf_text', keys: ['text'] },
|
||
},
|
||
},
|
||
dataSources: [
|
||
{
|
||
id: 'ds_1',
|
||
type: 'base',
|
||
fields: [{ name: 'name', defaultValue: 'init' }],
|
||
methods: [],
|
||
events: [],
|
||
},
|
||
],
|
||
};
|
||
const app = new TMagicApp({ config: dsl, platform: 'editor', curPage: 'page_1' });
|
||
const manager = createDataSourceManager(app);
|
||
|
||
const pageFragment = app.pageFragments.get('pfc_1')!;
|
||
const innerNode = pageFragment.getNode('pf_text', { strict: true })!;
|
||
const innerSetData = vi.spyOn(innerNode, 'setData');
|
||
|
||
const ds = manager?.get('ds_1');
|
||
ds?.setData({ name: 'Z' });
|
||
|
||
expect(innerSetData).toHaveBeenCalled();
|
||
const arg: any = innerSetData.mock.calls[0][0];
|
||
expect(arg.text).toBe('pf Z');
|
||
expect((pageFragment.data as any).items[0].text).toBe('pf Z');
|
||
});
|
||
});
|
||
|
||
describe('createDataSourceManager - pageFragment 边界分支', () => {
|
||
const buildDsl = (): MApp => ({
|
||
type: NodeType.ROOT,
|
||
id: 'app_pf_edge',
|
||
items: [
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_1',
|
||
items: [
|
||
{
|
||
type: 'page-fragment-container',
|
||
id: 'pfc_1',
|
||
pageFragmentId: 'pf_1',
|
||
items: [],
|
||
} as any,
|
||
],
|
||
},
|
||
{
|
||
type: NodeType.PAGE_FRAGMENT,
|
||
id: 'pf_1',
|
||
items: [{ type: 'text', id: 'pf_text', text: 'pf ${ds_1.name}' } as any],
|
||
} as any,
|
||
],
|
||
dataSourceDeps: {
|
||
ds_1: {
|
||
pf_text: { name: 'pf_text', keys: ['text'] },
|
||
},
|
||
},
|
||
dataSources: [
|
||
{
|
||
id: 'ds_1',
|
||
type: 'base',
|
||
fields: [{ name: 'name', defaultValue: 'init' }],
|
||
methods: [],
|
||
events: [],
|
||
},
|
||
],
|
||
});
|
||
|
||
test('pageFragment.getNode 返回 undefined 时安全跳过 setData', () => {
|
||
const app = new TMagicApp({ config: buildDsl(), platform: 'editor', curPage: 'page_1' });
|
||
const manager = createDataSourceManager(app);
|
||
|
||
const pageFragment = app.pageFragments.get('pfc_1')!;
|
||
// 模拟 pageFragment 内对应节点已被移除的边界
|
||
pageFragment.nodes.delete('pf_text');
|
||
|
||
const ds = manager?.get('ds_1');
|
||
expect(() => ds?.setData({ name: 'A' })).not.toThrow();
|
||
});
|
||
|
||
test('pageFragment 与当前遍历的 page、newNode 都无关时不会进入 pageFragment 同步分支', () => {
|
||
const dsl: MApp = {
|
||
type: NodeType.ROOT,
|
||
id: 'app_pf_unrelated',
|
||
items: [
|
||
{
|
||
type: NodeType.PAGE,
|
||
id: 'page_1',
|
||
items: [
|
||
{ type: 'text', id: 'plain_text', text: 'a ${ds_1.name}' } as any,
|
||
{
|
||
type: 'page-fragment-container',
|
||
id: 'pfc_1',
|
||
pageFragmentId: 'pf_1',
|
||
items: [],
|
||
} as any,
|
||
],
|
||
},
|
||
{
|
||
type: NodeType.PAGE_FRAGMENT,
|
||
id: 'pf_1',
|
||
items: [{ type: 'text', id: 'pf_text', text: 'pf' } as any],
|
||
} as any,
|
||
],
|
||
dataSourceDeps: {
|
||
ds_1: {
|
||
plain_text: { name: 'plain_text', keys: ['text'] },
|
||
},
|
||
},
|
||
dataSources: [
|
||
{
|
||
id: 'ds_1',
|
||
type: 'base',
|
||
fields: [{ name: 'name', defaultValue: 'init' }],
|
||
methods: [],
|
||
events: [],
|
||
},
|
||
],
|
||
};
|
||
const app = new TMagicApp({ config: dsl, platform: 'mobile', curPage: 'page_1' });
|
||
const manager = createDataSourceManager(app);
|
||
|
||
const pageFragment = app.pageFragments.get('pfc_1')!;
|
||
const pfSetData = vi.spyOn(pageFragment, 'setData');
|
||
|
||
const ds = manager?.get('ds_1');
|
||
ds?.setData({ name: 'C' });
|
||
|
||
// pageFragment 与本次更新无关,不会被同步
|
||
expect(pfSetData).not.toHaveBeenCalled();
|
||
});
|
||
|
||
test('pageFragment.instance 为真时跳过 replaceChildNode', () => {
|
||
const app = new TMagicApp({ config: buildDsl(), platform: 'editor', curPage: 'page_1' });
|
||
const manager = createDataSourceManager(app);
|
||
|
||
const pageFragment = app.pageFragments.get('pfc_1')!;
|
||
pageFragment.setInstance({ __isVue: true });
|
||
|
||
const before = (pageFragment.data as any).items[0].text;
|
||
const ds = manager?.get('ds_1');
|
||
ds?.setData({ name: 'B' });
|
||
|
||
// 因为 instance 存在,pageFragment.data 不会被 replaceChildNode 改写
|
||
expect((pageFragment.data as any).items[0].text).toBe(before);
|
||
});
|
||
});
|
||
|
||
describe('createDataSourceManager - 自定义数据源类型尚未注册', () => {
|
||
test('未知类型在初始化时不抛错,仅写入默认数据', () => {
|
||
const dsl: MApp = {
|
||
type: NodeType.ROOT,
|
||
id: 'app_pending',
|
||
items: [],
|
||
dataSources: [
|
||
{
|
||
id: 'ds_unknown',
|
||
type: 'custom-not-registered',
|
||
fields: [{ name: 'name', defaultValue: 'd' }],
|
||
methods: [],
|
||
events: [],
|
||
} as any,
|
||
],
|
||
};
|
||
const app = new TMagicApp({ config: dsl });
|
||
const manager = createDataSourceManager(app);
|
||
expect(manager).toBeInstanceOf(DataSourceManager);
|
||
expect(manager?.data.ds_unknown).toEqual({ name: 'd' });
|
||
expect(manager?.get('ds_unknown')).toBeUndefined();
|
||
});
|
||
|
||
test('在未注册期间通过 register 触发延迟初始化', () => {
|
||
const dsl: MApp = {
|
||
type: NodeType.ROOT,
|
||
id: 'app_lazy',
|
||
items: [],
|
||
dataSources: [
|
||
{
|
||
id: 'ds_lazy',
|
||
type: 'lazy-type',
|
||
fields: [{ name: 'name' }],
|
||
methods: [],
|
||
events: [],
|
||
} as any,
|
||
],
|
||
};
|
||
const app = new TMagicApp({ config: dsl });
|
||
const manager = createDataSourceManager(app);
|
||
expect(manager?.get('ds_lazy')).toBeUndefined();
|
||
|
||
class LazyDataSource extends DataSource {}
|
||
DataSourceManager.register('lazy-type', LazyDataSource as any);
|
||
|
||
expect(manager?.get('ds_lazy')).toBeInstanceOf(LazyDataSource);
|
||
});
|
||
});
|