tmagic-editor/runtime/vue-runtime-help/tests/use-editor-dsl.test.ts
roymondchen a7198e2731 fix(editor): 修复连续 add 与批量插入时节点顺序错乱
通过 lastAdded 记忆插入锚点、批量 add 改为串行,并向 runtime 传递 index 以保证编辑器与画布 DSL 顺序一致。
2026-08-07 11:59:06 +08:00

154 lines
4.2 KiB
TypeScript

import { beforeEach, describe, expect, test } from 'vitest';
import type { MApp } from '@tmagic/core';
import Core, { NodeType } from '@tmagic/core';
import type { Runtime } from '@tmagic/stage';
import { useEditorDsl } from '../src';
const createDsl = () =>
({
type: NodeType.ROOT,
id: 'app',
items: [
{ type: NodeType.PAGE, id: 'p1', items: [{ id: 'btn', type: 'button' }] },
{ type: NodeType.PAGE, id: 'p2', items: [] },
{ type: NodeType.PAGE_FRAGMENT, id: 'f1', items: [] },
],
}) as unknown as MApp;
const setup = (curPageId?: string) => {
const app = new Core({});
let runtime: Runtime = {};
(window as any).magic = {
onRuntimeReady: (rt: Runtime) => {
runtime = rt;
},
};
useEditorDsl(app);
const dsl = createDsl();
runtime.updateRootConfig?.(dsl);
if (curPageId) {
runtime.updatePageId?.(curPageId);
}
return { app, runtime, dsl };
};
describe('useEditorDsl add', () => {
beforeEach(() => {
(window as any).magic = undefined;
});
test('传入 index 时按指定下标插入,不依赖 selectedId', () => {
const { runtime, dsl } = setup();
runtime.select?.('btn');
runtime.add?.({
config: { id: 'n1', type: 'text' } as any,
parentId: 'p1',
root: dsl,
index: 0,
});
expect(dsl.items[0].items?.map((item) => item.id)).toEqual(['n1', 'btn']);
});
test('不传 index 时保持原有逻辑:接在选中节点之后', () => {
const { runtime, dsl } = setup();
runtime.select?.('btn');
runtime.add?.({ config: { id: 'n1', type: 'text' } as any, parentId: 'p1', root: dsl });
expect(dsl.items[0].items?.map((item) => item.id)).toEqual(['btn', 'n1']);
});
test('不传 index 且选中的就是父容器时追加到末尾', () => {
const { runtime, dsl } = setup();
runtime.select?.('p1');
runtime.add?.({ config: { id: 'n1', type: 'text' } as any, parentId: 'p1', root: dsl });
expect(dsl.items[0].items?.map((item) => item.id)).toEqual(['btn', 'n1']);
});
test('连续按递增 index 插入时保持顺序', () => {
const { runtime, dsl } = setup();
runtime.select?.('btn');
runtime.add?.({
config: { id: 'n1', type: 'text' } as any,
parentId: 'p1',
root: dsl,
index: 1,
});
runtime.add?.({
config: { id: 'n2', type: 'text' } as any,
parentId: 'p1',
root: dsl,
index: 2,
});
expect(dsl.items[0].items?.map((item) => item.id)).toEqual(['btn', 'n1', 'n2']);
});
});
describe('useEditorDsl remove', () => {
beforeEach(() => {
(window as any).magic = undefined;
});
test('删除当前渲染的页面时销毁 page 实例', () => {
const { app, runtime, dsl } = setup();
expect(app.page?.data.id).toBe('p1');
runtime.remove?.({ id: 'p1', parentId: 'app', root: dsl });
expect(app.page).toBeUndefined();
expect(dsl.items.some((item) => item.id === 'p1')).toBe(false);
});
test('删除非当前页面时保留当前 page 实例,不清空画布', () => {
const { app, runtime, dsl } = setup();
expect(app.page?.data.id).toBe('p1');
runtime.remove?.({ id: 'p2', parentId: 'app', root: dsl });
expect(app.page?.data.id).toBe('p1');
expect(dsl.items.some((item) => item.id === 'p2')).toBe(false);
});
test('删除当前渲染的页面片时同样销毁 page 实例', () => {
const { app, runtime, dsl } = setup('f1');
expect(app.page?.data.id).toBe('f1');
runtime.remove?.({ id: 'f1', parentId: 'app', root: dsl });
expect(app.page).toBeUndefined();
expect(dsl.items.some((item) => item.id === 'f1')).toBe(false);
});
test('删除非当前页面片时保留当前 page 实例', () => {
const { app, runtime, dsl } = setup();
expect(app.page?.data.id).toBe('p1');
runtime.remove?.({ id: 'f1', parentId: 'app', root: dsl });
expect(app.page?.data.id).toBe('p1');
expect(dsl.items.some((item) => item.id === 'f1')).toBe(false);
});
test('删除普通节点时只从当前 page 移除该节点', () => {
const { app, runtime, dsl } = setup();
expect(app.getNode('btn')?.data.id).toBe('btn');
runtime.remove?.({ id: 'btn', parentId: 'p1', root: dsl });
expect(app.page?.data.id).toBe('p1');
expect(app.getNode('btn')).toBeUndefined();
});
});