mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-09-13 15:38:39 +00:00
fix(editor): 避免 defaultSelected 早于 root 就绪时 select 抛出未处理错误
This commit is contained in:
parent
58f2a943b8
commit
08b610a4f2
@ -29,6 +29,7 @@ import { getDepNodeIds, getNodes, isPage, isValueIncludeDataSource } from '@tmag
|
|||||||
|
|
||||||
import PropsPanel from './layouts/PropsPanel.vue';
|
import PropsPanel from './layouts/PropsPanel.vue';
|
||||||
import { isIncludeDataSource } from './utils/editor';
|
import { isIncludeDataSource } from './utils/editor';
|
||||||
|
import { error as logError } from './utils/logger';
|
||||||
import { EditorProps } from './editorProps';
|
import { EditorProps } from './editorProps';
|
||||||
import { Services } from './type';
|
import { Services } from './type';
|
||||||
|
|
||||||
@ -36,6 +37,19 @@ export declare type LooseRequired<T> = {
|
|||||||
[P in string & keyof T]: T[P];
|
[P in string & keyof T]: T[P];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* select 为异步串行执行,节点不可选中(如已从 DSL 中移除)时会抛错。
|
||||||
|
* 在 watch 回调中调用不能把拒绝抛回调用方,否则会变成 Vue 的「Unhandled error during execution of watcher callback」,
|
||||||
|
* 这里统一兜底成日志。
|
||||||
|
*/
|
||||||
|
const selectNode = async (editorService: Services['editorService'], id: Id) => {
|
||||||
|
try {
|
||||||
|
await editorService.select(id);
|
||||||
|
} catch (e) {
|
||||||
|
logError(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const initServiceState = (
|
export const initServiceState = (
|
||||||
props: EditorProps,
|
props: EditorProps,
|
||||||
{
|
{
|
||||||
@ -193,9 +207,17 @@ export const initServiceState = (
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// defaultSelected 可能早于 modelValue 到位(immediate 首次执行时 root 还是空的,或节点尚未出现在 DSL 中),
|
||||||
|
// 此时 select 会因为「获取不到组件信息」而抛错,交由 root-change 中的选中逻辑在 root 就绪后补上即可。
|
||||||
watch(
|
watch(
|
||||||
() => props.defaultSelected,
|
() => props.defaultSelected,
|
||||||
(defaultSelected) => defaultSelected && editorService.select(defaultSelected),
|
(defaultSelected) => {
|
||||||
|
if (!defaultSelected || !editorService.getNodeById(defaultSelected)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectNode(editorService, defaultSelected);
|
||||||
|
},
|
||||||
{
|
{
|
||||||
immediate: true,
|
immediate: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,12 +4,13 @@
|
|||||||
* Copyright (C) 2025 Tencent.
|
* Copyright (C) 2025 Tencent.
|
||||||
*/
|
*/
|
||||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
import { defineComponent, h } from 'vue';
|
import { defineComponent, h, nextTick } from 'vue';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
|
|
||||||
import { DepTargetType } from '@tmagic/core';
|
import { DepTargetType } from '@tmagic/core';
|
||||||
|
|
||||||
import { initServiceEvents, initServiceState } from '@editor/initService';
|
import { initServiceEvents, initServiceState } from '@editor/initService';
|
||||||
|
import * as logger from '@editor/utils/logger';
|
||||||
|
|
||||||
const mkServices = () => {
|
const mkServices = () => {
|
||||||
const handlers: Record<string, Record<string, any[]>> = {};
|
const handlers: Record<string, Record<string, any[]>> = {};
|
||||||
@ -151,6 +152,10 @@ vi.mock('@editor/utils/editor', () => ({
|
|||||||
isIncludeDataSource: vi.fn(() => false),
|
isIncludeDataSource: vi.fn(() => false),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('@editor/utils/logger', () => ({
|
||||||
|
error: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
const Wrap = (props: any, services: any) =>
|
const Wrap = (props: any, services: any) =>
|
||||||
defineComponent({
|
defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
@ -233,11 +238,30 @@ describe('initServiceState', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('defaultSelected 调用 select', () => {
|
test('defaultSelected 调用 select', () => {
|
||||||
|
services.editorService.getNodeById.mockReturnValue({ id: 'n1' });
|
||||||
const props = { defaultSelected: 'n1' } as any;
|
const props = { defaultSelected: 'n1' } as any;
|
||||||
mount(Wrap(props, services));
|
mount(Wrap(props, services));
|
||||||
expect(services.editorService.select).toHaveBeenCalledWith('n1');
|
expect(services.editorService.select).toHaveBeenCalledWith('n1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('defaultSelected 对应节点不存在时不调用 select', () => {
|
||||||
|
services.editorService.getNodeById.mockReturnValue(null);
|
||||||
|
const props = { defaultSelected: 'n1' } as any;
|
||||||
|
mount(Wrap(props, services));
|
||||||
|
expect(services.editorService.select).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('defaultSelected select 失败时兜底成日志,不产生未处理的拒绝', async () => {
|
||||||
|
const err = new Error('获取不到组件信息');
|
||||||
|
vi.mocked(logger.error).mockClear();
|
||||||
|
services.editorService.getNodeById.mockReturnValue({ id: 'n1' });
|
||||||
|
services.editorService.select.mockRejectedValue(err);
|
||||||
|
const props = { defaultSelected: 'n1' } as any;
|
||||||
|
mount(Wrap(props, services));
|
||||||
|
await nextTick();
|
||||||
|
expect(logger.error).toHaveBeenCalledWith(err);
|
||||||
|
});
|
||||||
|
|
||||||
test('stageRect 设置 ui state', () => {
|
test('stageRect 设置 ui state', () => {
|
||||||
const props = { stageRect: { width: 100 } } as any;
|
const props = { stageRect: { width: 100 } } as any;
|
||||||
mount(Wrap(props, services));
|
mount(Wrap(props, services));
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user