mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-07-30 18:05:47 +00:00
fix(editor): 修复删除页面/页面片时画布被误清空的问题
删除当前页时先切换画布再通知 runtime 销毁;runtime 侧仅在删除当前渲染页时才调用 deletePage。
This commit is contained in:
parent
c4b98e3257
commit
aa42144738
@ -341,6 +341,20 @@ describe('App 配置/方法/组件注册', () => {
|
||||
expect(app.page).toBeUndefined();
|
||||
});
|
||||
|
||||
test('deletePage 销毁当前 page', () => {
|
||||
const app = new App({
|
||||
config: {
|
||||
type: NodeType.ROOT,
|
||||
id: 'app',
|
||||
items: [{ type: NodeType.PAGE, id: 'p1', items: [] }],
|
||||
},
|
||||
});
|
||||
expect(app.page?.data.id).toBe('p1');
|
||||
|
||||
app.deletePage();
|
||||
expect(app.page).toBeUndefined();
|
||||
});
|
||||
|
||||
test('runCode 执行代码块', async () => {
|
||||
const fn = vi.fn();
|
||||
const app = new App({
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { reactive, toRaw } from 'vue';
|
||||
import { nextTick, reactive, toRaw } from 'vue';
|
||||
import { cloneDeep, isEmpty, isEqual, isObject, mergeWith, uniq } from 'lodash-es';
|
||||
|
||||
import type { Id, MApp, MContainer, MNode, MPage, MPageFragment, TargetOptions } from '@tmagic/core';
|
||||
@ -30,6 +30,7 @@ import {
|
||||
guid,
|
||||
isPage,
|
||||
isPageFragment,
|
||||
isPageOrFragment,
|
||||
setValueByKeyPath,
|
||||
traverseNode,
|
||||
} from '@tmagic/utils';
|
||||
@ -272,7 +273,7 @@ class Editor extends BaseService {
|
||||
public isOnDifferentPage(node: MNode): boolean {
|
||||
const currentPageId = this.get('page')?.id;
|
||||
if (currentPageId === undefined || currentPageId === null) return false;
|
||||
if (isPage(node) || isPageFragment(node)) {
|
||||
if (isPageOrFragment(node)) {
|
||||
return `${node.id}` !== `${currentPageId}`;
|
||||
}
|
||||
const nodePage = this.getNodeInfo(node.id, false).page;
|
||||
@ -413,11 +414,11 @@ class Editor extends BaseService {
|
||||
|
||||
if (!curNode) throw new Error('当前选中节点为空');
|
||||
|
||||
if ((parent.type === NodeType.ROOT || curNode?.type === NodeType.ROOT) && !(isPage(node) || isPageFragment(node))) {
|
||||
if ((parent.type === NodeType.ROOT || curNode?.type === NodeType.ROOT) && !isPageOrFragment(node)) {
|
||||
throw new Error('app下不能添加组件');
|
||||
}
|
||||
|
||||
if (parent.id !== curNode.id && !(isPage(node) || isPageFragment(node))) {
|
||||
if (parent.id !== curNode.id && !isPageOrFragment(node)) {
|
||||
const index = parent.items.indexOf(curNode);
|
||||
parent.items?.splice(index + 1, 0, node);
|
||||
} else {
|
||||
@ -487,7 +488,7 @@ class Editor extends BaseService {
|
||||
const newNodes = await Promise.all(
|
||||
addNodes.map((node) => {
|
||||
const root = this.get('root');
|
||||
if ((isPage(node) || isPageFragment(node)) && root) {
|
||||
if (isPageOrFragment(node) && root) {
|
||||
return this.doAdd(node, root);
|
||||
}
|
||||
const parentNode = parent ?? getAddParent(node);
|
||||
@ -523,7 +524,7 @@ class Editor extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
if (!(isPage(newNodes[0]) || isPageFragment(newNodes[0]))) {
|
||||
if (!isPageOrFragment(newNodes[0])) {
|
||||
const pageForOp = this.getNodeInfo(newNodes[0].id, false).page;
|
||||
if (!doNotPushHistory) {
|
||||
const parentId = (this.getParentById(newNodes[0].id, false) ?? this.get('root'))!.id;
|
||||
@ -554,8 +555,8 @@ class Editor extends BaseService {
|
||||
doNotPushHistory,
|
||||
});
|
||||
|
||||
// 页面 / 页面片新增不入历史栈(见上方 isPage / isPageFragment 分支),这里合并补发一次结构变更通知
|
||||
const addedPages = newNodes.filter((node) => isPage(node) || isPageFragment(node)) as (MPage | MPageFragment)[];
|
||||
// 页面 / 页面片新增不入历史栈(见上方 isPageOrFragment 分支),这里合并补发一次结构变更通知
|
||||
const addedPages = newNodes.filter((node) => isPageOrFragment(node)) as (MPage | MPageFragment)[];
|
||||
if (addedPages.length) {
|
||||
historyService.notifyPageStructureChange({ add: addedPages, remove: [] });
|
||||
}
|
||||
@ -578,12 +579,14 @@ class Editor extends BaseService {
|
||||
|
||||
if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点');
|
||||
|
||||
parent.items?.splice(index, 1);
|
||||
const stage = this.get('stage');
|
||||
stage?.remove({ id: node.id, parentId: parent.id, root: cloneDeep(root) });
|
||||
const currentPage = this.get('page');
|
||||
const isDeletingCurrentPage = isPageOrFragment(node) && !!currentPage && `${currentPage.id}` === `${node.id}`;
|
||||
|
||||
parent.items?.splice(index, 1);
|
||||
|
||||
// 始终清理已删除节点在 state 中的残留引用:
|
||||
// - 即使后续会调用 selectDefault / select(parent) 覆盖,跳过这些调用(doNotSelect / doNotSwitchPage)时也不能让 state 持有已删除节点
|
||||
// - 即使后续会调用 select 覆盖,跳过这些调用(doNotSelect / doNotSwitchPage)时也不能让 state 持有已删除节点
|
||||
const selectedNodes = this.get('nodes');
|
||||
const removedSelectedIndex = selectedNodes.findIndex((n: MNode) => `${n.id}` === `${node.id}`);
|
||||
if (removedSelectedIndex !== -1) {
|
||||
@ -591,38 +594,44 @@ class Editor extends BaseService {
|
||||
nextSelected.splice(removedSelectedIndex, 1);
|
||||
this.set('nodes', nextSelected);
|
||||
}
|
||||
if (isPage(node) || isPageFragment(node)) {
|
||||
const currentPage = this.get('page');
|
||||
if (currentPage && `${currentPage.id}` === `${node.id}`) {
|
||||
this.set('page', null);
|
||||
}
|
||||
}
|
||||
|
||||
const selectDefault = async (pages: MNode[]) => {
|
||||
if (pages[0]) {
|
||||
await this.select(pages[0]);
|
||||
stage?.select(pages[0].id);
|
||||
} else {
|
||||
this.selectRoot();
|
||||
}
|
||||
};
|
||||
|
||||
const removeData = { id: node.id, parentId: parent.id, root: cloneDeep(root) };
|
||||
const rootItems = root.items || [];
|
||||
// 删非当前页时画布不应该变动,所以只有删的是当前页才重新选中
|
||||
const shouldReselect = isDeletingCurrentPage && !doNotSelect && !doNotSwitchPage;
|
||||
// 还有剩余页面才能切过去,否则退回选中 root
|
||||
const shouldSwitchPage = shouldReselect && rootItems.length > 0;
|
||||
|
||||
if (isPage(node)) {
|
||||
this.state.pageLength -= 1;
|
||||
|
||||
// 删除页面后默认会切到首个剩余页面(selectDefault),doNotSwitchPage 时跳过这次自动切换
|
||||
if (!doNotSelect && !doNotSwitchPage) {
|
||||
await selectDefault(rootItems);
|
||||
if (isPageOrFragment(node)) {
|
||||
if (isPage(node)) {
|
||||
this.state.pageLength -= 1;
|
||||
} else {
|
||||
this.state.pageFragmentLength -= 1;
|
||||
}
|
||||
} else if (isPageFragment(node)) {
|
||||
this.state.pageFragmentLength -= 1;
|
||||
|
||||
if (!doNotSelect && !doNotSwitchPage) {
|
||||
await selectDefault(rootItems);
|
||||
if (shouldSwitchPage) {
|
||||
// runtime 收到页面删除时会销毁当前渲染的 page 实例,必须先把画布切到剩余页面再通知删除,
|
||||
// 否则画布会被清空,且要等下一次 updatePageId 才能恢复。
|
||||
// nextTick 用于等 Stage 的 page watch 把新页面 id 同步给 runtime。
|
||||
await this.select(rootItems[0]);
|
||||
stage?.select(rootItems[0].id);
|
||||
await nextTick();
|
||||
stage?.remove(removeData);
|
||||
} else {
|
||||
// page 置空会让 Workspace 卸载 Stage 并销毁 renderer,删除通知必须在卸载前发出,
|
||||
// 且不能 await:runtime 未 ready 时 getRuntime 的监听会随 destroy 一起被移除,永远不会 resolve
|
||||
stage?.remove(removeData);
|
||||
|
||||
if (shouldReselect) {
|
||||
// 页面已全部删完
|
||||
this.selectRoot();
|
||||
} else if (isDeletingCurrentPage) {
|
||||
this.set('page', null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stage?.remove(removeData);
|
||||
|
||||
if (!doNotSelect) {
|
||||
await this.select(parent);
|
||||
stage?.select(parent.id);
|
||||
@ -664,7 +673,7 @@ class Editor extends BaseService {
|
||||
|
||||
const removedItems: StepDiffItem<MNode>[] = [];
|
||||
let pageForOp: { name: string; id: Id } | null = null;
|
||||
if (!(isPage(nodes[0]) || isPageFragment(nodes[0]))) {
|
||||
if (!isPageOrFragment(nodes[0])) {
|
||||
for (const n of nodes) {
|
||||
const { parent, node: curNode, page } = this.getNodeInfo(n.id, false);
|
||||
if (parent && curNode) {
|
||||
@ -702,8 +711,8 @@ class Editor extends BaseService {
|
||||
this.emit('remove', nodes);
|
||||
this.emit('change', { type: 'remove', data: changeItems, historySource, doNotPushHistory });
|
||||
|
||||
// 页面 / 页面片删除不入历史栈(见上方 isPage / isPageFragment 分支),这里合并补发一次结构变更通知
|
||||
const removedPages = nodes.filter((node) => isPage(node) || isPageFragment(node)) as (MPage | MPageFragment)[];
|
||||
// 页面 / 页面片删除不入历史栈(见上方 isPageOrFragment 分支),这里合并补发一次结构变更通知
|
||||
const removedPages = nodes.filter((node) => isPageOrFragment(node)) as (MPage | MPageFragment)[];
|
||||
if (removedPages.length) {
|
||||
historyService.notifyPageStructureChange({ add: [], remove: removedPages });
|
||||
}
|
||||
@ -764,7 +773,7 @@ class Editor extends BaseService {
|
||||
}
|
||||
|
||||
// 只有被更新节点正好是当前选中页面时才同步 state.page,避免「更新非当前页」误将编辑器切到该页
|
||||
if (isPage(newConfig) || isPageFragment(newConfig)) {
|
||||
if (isPageOrFragment(newConfig)) {
|
||||
const currentPage = this.get('page');
|
||||
if (currentPage && `${currentPage.id}` === `${newConfig.id}`) {
|
||||
this.set('page', newConfig as MPage | MPageFragment);
|
||||
@ -1139,7 +1148,7 @@ class Editor extends BaseService {
|
||||
}: DslOpOptions = {},
|
||||
): Promise<MNode | MNode[]> {
|
||||
const isBatch = Array.isArray(config);
|
||||
const configs = (isBatch ? config : [config]).filter((item) => !(isPage(item) || isPageFragment(item)));
|
||||
const configs = (isBatch ? config : [config]).filter((item) => !isPageOrFragment(item));
|
||||
|
||||
if (configs.length === 0) {
|
||||
throw new Error('没有可移动的节点');
|
||||
@ -1760,7 +1769,7 @@ class Editor extends BaseService {
|
||||
private getPageOfNode(id: Id): MPage | MPageFragment | null {
|
||||
const { node, page } = this.getNodeInfo(id, false);
|
||||
if (page) return page;
|
||||
if (node && (isPage(node) || isPageFragment(node))) return node as MPage | MPageFragment;
|
||||
if (node && isPageOrFragment(node)) return node as MPage | MPageFragment;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import type { MApp, MContainer, MNode } from '@tmagic/core';
|
||||
@ -552,6 +552,153 @@ describe('remove', () => {
|
||||
// 自动切到剩余首个页面
|
||||
expect(editorService.get('page')?.id).toBe(addedId);
|
||||
});
|
||||
|
||||
test('默认删除当前页面并自动切页时,不会先把 page 置为 null', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
const rootNode = editorService.get('root');
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
const newPage = await editorService.add({ type: NodeType.PAGE }, rootNode);
|
||||
const addedId = Array.isArray(newPage) ? newPage[0].id : newPage.id;
|
||||
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
|
||||
const pageValues: Array<MNode | null> = [];
|
||||
const originalSet = editorService.set.bind(editorService);
|
||||
const setSpy = vi.spyOn(editorService, 'set').mockImplementation((name, value, options) => {
|
||||
if (name === 'page') {
|
||||
pageValues.push(value as MNode | null);
|
||||
}
|
||||
return originalSet(name, value, options);
|
||||
});
|
||||
|
||||
try {
|
||||
await editorService.remove({ id: NodeId.PAGE_ID, type: NodeType.PAGE });
|
||||
} finally {
|
||||
setSpy.mockRestore();
|
||||
}
|
||||
|
||||
expect(pageValues.some((page) => page === null)).toBe(false);
|
||||
expect(editorService.get('page')?.id).toBe(addedId);
|
||||
});
|
||||
|
||||
test('删除非当前页面时保持当前页选中,不强制切到首个页面', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
const rootNode = editorService.get('root');
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
const newPage = await editorService.add({ type: NodeType.PAGE }, rootNode);
|
||||
const addedId = Array.isArray(newPage) ? newPage[0].id : newPage.id;
|
||||
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
expect(editorService.get('page')?.id).toBe(NodeId.PAGE_ID);
|
||||
|
||||
await editorService.remove({ id: addedId, type: NodeType.PAGE });
|
||||
|
||||
expect(editorService.getNodeById(addedId)).toBeNull();
|
||||
expect(editorService.get('page')?.id).toBe(NodeId.PAGE_ID);
|
||||
});
|
||||
|
||||
test('删除最后一个页面后退回选中 root', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
const rootNode = editorService.get('root');
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
expect(rootNode?.items.length).toBe(1);
|
||||
|
||||
await editorService.remove({ id: NodeId.PAGE_ID, type: NodeType.PAGE });
|
||||
|
||||
expect(rootNode?.items.length).toBe(0);
|
||||
expect(editorService.get('page')).toBeNull();
|
||||
// root 下已无页面,编辑器应回到「选中 root」状态,而不是什么都没选中
|
||||
expect(editorService.get('nodes')).toEqual([rootNode]);
|
||||
expect(editorService.get('parent')).toBeNull();
|
||||
});
|
||||
|
||||
describe('与画布的调用顺序', () => {
|
||||
const createStageStub = () => {
|
||||
const calls: string[] = [];
|
||||
return {
|
||||
calls,
|
||||
stage: {
|
||||
remove: vi.fn(() => {
|
||||
calls.push('remove');
|
||||
return Promise.resolve();
|
||||
}),
|
||||
select: vi.fn(() => {
|
||||
calls.push('select');
|
||||
return Promise.resolve();
|
||||
}),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
editorService.set('stage', null);
|
||||
});
|
||||
|
||||
test('删除当前页面时先把画布切到剩余页面,再通知画布删除', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
const rootNode = editorService.get('root');
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
const newPage = await editorService.add({ type: NodeType.PAGE }, rootNode);
|
||||
const addedId = Array.isArray(newPage) ? newPage[0].id : newPage.id;
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
|
||||
const { calls, stage } = createStageStub();
|
||||
editorService.set('stage', stage as any);
|
||||
|
||||
await editorService.remove({ id: NodeId.PAGE_ID, type: NodeType.PAGE });
|
||||
|
||||
// runtime 删除 page 时会销毁当前渲染的实例,必须切页在前,否则画布会被清空
|
||||
expect(calls).toEqual(['select', 'remove']);
|
||||
expect(stage.select).toHaveBeenCalledWith(addedId);
|
||||
expect(editorService.get('page')?.id).toBe(addedId);
|
||||
});
|
||||
|
||||
test('删除当前页面片时同样先切页再通知画布删除', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
const rootNode = editorService.get('root');
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
const fragment = await editorService.add({ type: NodeType.PAGE_FRAGMENT }, rootNode);
|
||||
const fragmentId = Array.isArray(fragment) ? fragment[0].id : fragment.id;
|
||||
// 新增页面片后当前页已切到页面片
|
||||
expect(editorService.get('page')?.id).toBe(fragmentId);
|
||||
|
||||
const { calls, stage } = createStageStub();
|
||||
editorService.set('stage', stage as any);
|
||||
|
||||
await editorService.remove({ id: fragmentId, type: NodeType.PAGE_FRAGMENT });
|
||||
|
||||
expect(calls).toEqual(['select', 'remove']);
|
||||
expect(editorService.get('page')?.id).toBe(NodeId.PAGE_ID);
|
||||
});
|
||||
|
||||
test('删除普通节点时先通知画布删除,再选中父节点', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
await editorService.select(NodeId.NODE_ID);
|
||||
|
||||
const { calls, stage } = createStageStub();
|
||||
editorService.set('stage', stage as any);
|
||||
|
||||
await editorService.remove({ id: NodeId.NODE_ID, type: 'text' });
|
||||
|
||||
expect(calls).toEqual(['remove', 'select']);
|
||||
expect(stage.select).toHaveBeenCalledWith(NodeId.PAGE_ID);
|
||||
});
|
||||
|
||||
test('删除最后一个页面时在 Stage 卸载前发出删除通知', async () => {
|
||||
editorService.set('root', cloneDeep(root));
|
||||
await editorService.select(NodeId.PAGE_ID);
|
||||
|
||||
const { calls, stage } = createStageStub();
|
||||
editorService.set('stage', stage as any);
|
||||
|
||||
await editorService.remove({ id: NodeId.PAGE_ID, type: NodeType.PAGE });
|
||||
|
||||
// page 置空会让 Workspace 卸载 Stage,删除通知必须在这之前发出
|
||||
expect(calls).toEqual(['remove']);
|
||||
// selectRoot 会顺带清掉 stage 引用
|
||||
expect(editorService.get('stage')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { computed, nextTick, onBeforeUnmount, reactive, ref } from 'vue';
|
||||
|
||||
import type { Id, MApp, MNode } from '@tmagic/core';
|
||||
import TMagicApp from '@tmagic/core';
|
||||
import TMagicApp, { NodeType } from '@tmagic/core';
|
||||
import type { FormConfig, MForm, RemoveData, UpdateData } from '@tmagic/editor';
|
||||
import { getElById, getNodePath, initValue, replaceChildNode } from '@tmagic/editor';
|
||||
|
||||
@ -136,8 +136,11 @@ export const useFormConfig = (props: AppProps) => {
|
||||
const parent = getNodePath(parentId, [root.value]).pop();
|
||||
if (!parent) throw new Error('未找到父元素');
|
||||
|
||||
if (node.type === 'page') {
|
||||
app?.deletePage();
|
||||
// 页面与页面片都由 app.page 承载,只有删的正是当前渲染的那个才销毁实例,避免误清其它页的画布
|
||||
if (node.type === NodeType.PAGE || node.type === NodeType.PAGE_FRAGMENT) {
|
||||
if (`${app?.page?.data.id}` === `${node.id}`) {
|
||||
app?.deletePage();
|
||||
}
|
||||
} else {
|
||||
app?.page?.deleteNode(node.id);
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { computed, inject, nextTick, reactive, ref, watch } from 'vue';
|
||||
|
||||
import type TMagicApp from '@tmagic/core';
|
||||
import type { Id, MApp, MNode, MPage, MPageFragment } from '@tmagic/core';
|
||||
import { asyncLoadCss, getElById, getNodePath, replaceChildNode } from '@tmagic/core';
|
||||
import { asyncLoadCss, getElById, getNodePath, NodeType, replaceChildNode } from '@tmagic/core';
|
||||
import type { Magic, RemoveData, Runtime, UpdateData } from '@tmagic/stage';
|
||||
|
||||
declare global {
|
||||
@ -11,6 +11,8 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const isPageNode = (node: MNode) => node.type === NodeType.PAGE || node.type === NodeType.PAGE_FRAGMENT;
|
||||
|
||||
let styleEl: HTMLStyleElement | null = null;
|
||||
|
||||
const createCss = async (config: MPage | MPageFragment) => {
|
||||
@ -153,8 +155,12 @@ export const useEditorDsl = (app = inject<TMagicApp>('app'), runtimeApi: Runtime
|
||||
const parent = getNodePath(parentId, [root.value]).pop();
|
||||
if (!parent) throw new Error('未找到父元素');
|
||||
|
||||
if (node.type === 'page') {
|
||||
app?.deletePage();
|
||||
// 页面与页面片都由 app.page 承载,只有删的正是当前渲染的那个才销毁实例:
|
||||
// 无条件销毁会清空画布,且编辑器删页时已先切到其它页,会把刚切过去的页面误清掉
|
||||
if (isPageNode(node)) {
|
||||
if (`${app?.page?.data.id}` === `${node.id}`) {
|
||||
app?.deletePage();
|
||||
}
|
||||
} else {
|
||||
app?.page?.deleteNode(node.id);
|
||||
}
|
||||
|
||||
95
runtime/vue-runtime-help/tests/use-editor-dsl.test.ts
Normal file
95
runtime/vue-runtime-help/tests/use-editor-dsl.test.ts
Normal file
@ -0,0 +1,95 @@
|
||||
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 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();
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user