mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-06-04 22:33:09 +08:00
feat(editor): 支持自定义组件树节点是否可展开的判断函数
新增 layerNodeIsExpandable 配置项,业务方可自定义"已选组件"面板中 节点是否显示为可展开形态。同时导出默认实现 defaultIsExpandable 与 类型 IsExpandableFunction 供第三方复用。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
cce8b63fc3
commit
3cde69f6f9
@ -1171,6 +1171,46 @@ const customContentMenu = (menus, { node }) => {
|
|||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## layerNodeIsExpandable
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
用于自定义判断"已选组件"面板中组件树节点是否可展开(即是否要展示为拥有子节点的形态)
|
||||||
|
|
||||||
|
该函数返回 `true` 时,节点会显示展开图标,并在展开后渲染子节点容器;返回 `false` 时,展开图标显示为透明占位,且不渲染子节点容器
|
||||||
|
|
||||||
|
默认行为:当节点的 `items` 中至少存在一个 `visible` 状态为 `true` 的子节点时认为可展开(被搜索过滤隐藏的子节点不会让父节点显示为可展开)
|
||||||
|
|
||||||
|
- **默认值:** `defaultIsExpandable`
|
||||||
|
|
||||||
|
- **类型:** `(data: TreeNodeData, nodeStatusMap: Map<Id, LayerNodeStatus>) => boolean`
|
||||||
|
|
||||||
|
- **示例:**
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<m-editor :layer-node-is-expandable="layerNodeIsExpandable"></m-editor>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { defaultIsExpandable } from '@tmagic/editor';
|
||||||
|
|
||||||
|
// 即使没有可见子节点,特定类型的容器节点也保持展开图标可见
|
||||||
|
const layerNodeIsExpandable = (data, nodeStatusMap) => {
|
||||||
|
if (data.type === 'my-special-container') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return defaultIsExpandable(data, nodeStatusMap);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
该函数仅作用于"已选组件"面板的组件树节点,不影响代码块、数据源等其它面板内的树。
|
||||||
|
|
||||||
|
第三方业务可从 `@tmagic/editor` 直接导入 `defaultIsExpandable` 复用默认逻辑作为兜底。
|
||||||
|
:::
|
||||||
|
|
||||||
## extendFormState
|
## extendFormState
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
:custom-content-menu="customContentMenu"
|
:custom-content-menu="customContentMenu"
|
||||||
:indent="treeIndent"
|
:indent="treeIndent"
|
||||||
:next-level-indent-increment="treeNextLevelIndentIncrement"
|
:next-level-indent-increment="treeNextLevelIndentIncrement"
|
||||||
|
:layer-node-is-expandable="layerNodeIsExpandable"
|
||||||
>
|
>
|
||||||
<template #layer-panel-header>
|
<template #layer-panel-header>
|
||||||
<slot name="layer-panel-header"></slot>
|
<slot name="layer-panel-header"></slot>
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
:indent="indent"
|
:indent="indent"
|
||||||
:next-level-indent-increment="nextLevelIndentIncrement"
|
:next-level-indent-increment="nextLevelIndentIncrement"
|
||||||
:node-status-map="nodeStatusMap"
|
:node-status-map="nodeStatusMap"
|
||||||
|
:is-expandable="isExpandable"
|
||||||
>
|
>
|
||||||
<template #tree-node-content="{ data: nodeData }">
|
<template #tree-node-content="{ data: nodeData }">
|
||||||
<slot name="tree-node-content" :data="nodeData"> </slot>
|
<slot name="tree-node-content" :data="nodeData"> </slot>
|
||||||
@ -33,7 +34,7 @@ import { provide } from 'vue';
|
|||||||
|
|
||||||
import type { Id } from '@tmagic/core';
|
import type { Id } from '@tmagic/core';
|
||||||
|
|
||||||
import type { LayerNodeStatus, TreeNodeData } from '@editor/type';
|
import type { IsExpandableFunction, LayerNodeStatus, TreeNodeData } from '@editor/type';
|
||||||
|
|
||||||
import TreeNode from './TreeNode.vue';
|
import TreeNode from './TreeNode.vue';
|
||||||
|
|
||||||
@ -66,6 +67,8 @@ withDefaults(
|
|||||||
indent?: number;
|
indent?: number;
|
||||||
nextLevelIndentIncrement?: number;
|
nextLevelIndentIncrement?: number;
|
||||||
emptyText?: string;
|
emptyText?: string;
|
||||||
|
/** 自定义判断节点是否可展开(即是否要展示为拥有子节点的形态)的函数 */
|
||||||
|
isExpandable?: IsExpandableFunction;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
indent: 0,
|
indent: 0,
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
>
|
>
|
||||||
<MIcon
|
<MIcon
|
||||||
class="expand-icon"
|
class="expand-icon"
|
||||||
:style="hasChildren ? '' : 'color: transparent; cursor: default'"
|
:style="isExpandable(data, nodeStatusMap) ? '' : 'color: transparent; cursor: default'"
|
||||||
:icon="expanded ? ArrowDown : ArrowRight"
|
:icon="expanded ? ArrowDown : ArrowRight"
|
||||||
@click="expandHandler"
|
@click="expandHandler"
|
||||||
></MIcon>
|
></MIcon>
|
||||||
@ -37,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="hasChildren && expanded" class="m-editor-tree-node-children">
|
<div v-if="isExpandable(data, nodeStatusMap) && expanded" class="m-editor-tree-node-children">
|
||||||
<TreeNode
|
<TreeNode
|
||||||
v-for="item in data.items"
|
v-for="item in data.items"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@ -46,6 +46,7 @@
|
|||||||
:parentsId="[...parentsId, data.id]"
|
:parentsId="[...parentsId, data.id]"
|
||||||
:node-status-map="nodeStatusMap"
|
:node-status-map="nodeStatusMap"
|
||||||
:indent="indent + nextLevelIndentIncrement"
|
:indent="indent + nextLevelIndentIncrement"
|
||||||
|
:is-expandable="isExpandable"
|
||||||
>
|
>
|
||||||
<template #tree-node-content="{ data: nodeData }">
|
<template #tree-node-content="{ data: nodeData }">
|
||||||
<slot name="tree-node-content" :data="nodeData"> </slot>
|
<slot name="tree-node-content" :data="nodeData"> </slot>
|
||||||
@ -68,8 +69,8 @@ import { ArrowDown, ArrowRight } from '@element-plus/icons-vue';
|
|||||||
import type { Id } from '@tmagic/core';
|
import type { Id } from '@tmagic/core';
|
||||||
|
|
||||||
import MIcon from '@editor/components/Icon.vue';
|
import MIcon from '@editor/components/Icon.vue';
|
||||||
import type { LayerNodeStatus, TreeNodeData } from '@editor/type';
|
import type { IsExpandableFunction, LayerNodeStatus, TreeNodeData } from '@editor/type';
|
||||||
import { updateStatus } from '@editor/utils/tree';
|
import { defaultIsExpandable, updateStatus } from '@editor/utils/tree';
|
||||||
|
|
||||||
defineSlots<{
|
defineSlots<{
|
||||||
'tree-node-label'(_props: { data: TreeNodeData }): any;
|
'tree-node-label'(_props: { data: TreeNodeData }): any;
|
||||||
@ -100,11 +101,14 @@ const props = withDefaults(
|
|||||||
nodeStatusMap: Map<Id, LayerNodeStatus>;
|
nodeStatusMap: Map<Id, LayerNodeStatus>;
|
||||||
indent?: number;
|
indent?: number;
|
||||||
nextLevelIndentIncrement?: number;
|
nextLevelIndentIncrement?: number;
|
||||||
|
/** 自定义判断节点是否可展开(即是否要展示为拥有子节点的形态)的函数 */
|
||||||
|
isExpandable?: IsExpandableFunction;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
indent: 0,
|
indent: 0,
|
||||||
nextLevelIndentIncrement: 11,
|
nextLevelIndentIncrement: 11,
|
||||||
parentsId: () => [],
|
parentsId: () => [],
|
||||||
|
isExpandable: defaultIsExpandable,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -123,10 +127,6 @@ const selected = computed(() => nodeStatus.value.selected);
|
|||||||
const visible = computed(() => nodeStatus.value.visible);
|
const visible = computed(() => nodeStatus.value.visible);
|
||||||
const draggable = computed(() => nodeStatus.value.draggable);
|
const draggable = computed(() => nodeStatus.value.draggable);
|
||||||
|
|
||||||
const hasChildren = computed(
|
|
||||||
() => Array.isArray(props.data.items) && props.data.items.some((item) => props.nodeStatusMap.get(item.id)?.visible),
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDragStart = (event: DragEvent) => {
|
const handleDragStart = (event: DragEvent) => {
|
||||||
treeEmit?.('node-dragstart', event, props.data);
|
treeEmit?.('node-dragstart', event, props.data);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import type {
|
|||||||
ComponentGroup,
|
ComponentGroup,
|
||||||
CustomContentMenuFunction,
|
CustomContentMenuFunction,
|
||||||
DatasourceTypeOption,
|
DatasourceTypeOption,
|
||||||
|
IsExpandableFunction,
|
||||||
MenuBarData,
|
MenuBarData,
|
||||||
MenuButton,
|
MenuButton,
|
||||||
MenuComponent,
|
MenuComponent,
|
||||||
@ -98,6 +99,8 @@ export interface EditorProps {
|
|||||||
isContainer?: (el: HTMLElement) => boolean | Promise<boolean>;
|
isContainer?: (el: HTMLElement) => boolean | Promise<boolean>;
|
||||||
/** 用于自定义组件树与画布的右键菜单 */
|
/** 用于自定义组件树与画布的右键菜单 */
|
||||||
customContentMenu?: CustomContentMenuFunction;
|
customContentMenu?: CustomContentMenuFunction;
|
||||||
|
/** 用于自定义判断组件树节点是否可展开(即是否要展示为拥有子节点的形态) */
|
||||||
|
layerNodeIsExpandable?: IsExpandableFunction;
|
||||||
/** 画布双击前的钩子函数,返回 false 则阻止默认的双击行为 */
|
/** 画布双击前的钩子函数,返回 false 则阻止默认的双击行为 */
|
||||||
beforeDblclick?: (event: MouseEvent) => Promise<boolean | void> | boolean | void;
|
beforeDblclick?: (event: MouseEvent) => Promise<boolean | void> | boolean | void;
|
||||||
extendFormState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
extendFormState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
||||||
|
|||||||
@ -164,6 +164,7 @@ import { useServices } from '@editor/hooks/use-services';
|
|||||||
import {
|
import {
|
||||||
ColumnLayout,
|
ColumnLayout,
|
||||||
CustomContentMenuFunction,
|
CustomContentMenuFunction,
|
||||||
|
type IsExpandableFunction,
|
||||||
type MenuButton,
|
type MenuButton,
|
||||||
type MenuComponent,
|
type MenuComponent,
|
||||||
type SideBarData,
|
type SideBarData,
|
||||||
@ -191,6 +192,8 @@ const props = withDefaults(
|
|||||||
indent?: number;
|
indent?: number;
|
||||||
nextLevelIndentIncrement?: number;
|
nextLevelIndentIncrement?: number;
|
||||||
customContentMenu: CustomContentMenuFunction;
|
customContentMenu: CustomContentMenuFunction;
|
||||||
|
/** 自定义判断组件树节点是否可展开(即是否要展示为拥有子节点的形态)的函数 */
|
||||||
|
layerNodeIsExpandable?: IsExpandableFunction;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
data: () => ({
|
data: () => ({
|
||||||
@ -248,6 +251,7 @@ const getItemConfig = (data: SideItem): SideComponent => {
|
|||||||
customContentMenu: props.customContentMenu,
|
customContentMenu: props.customContentMenu,
|
||||||
indent: props.indent,
|
indent: props.indent,
|
||||||
nextLevelIndentIncrement: props.nextLevelIndentIncrement,
|
nextLevelIndentIncrement: props.nextLevelIndentIncrement,
|
||||||
|
isExpandable: props.layerNodeIsExpandable,
|
||||||
},
|
},
|
||||||
component: LayerPanel,
|
component: LayerPanel,
|
||||||
slots: {},
|
slots: {},
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
:node-status-map="nodeStatusMap"
|
:node-status-map="nodeStatusMap"
|
||||||
:indent="indent"
|
:indent="indent"
|
||||||
:next-level-indent-increment="nextLevelIndentIncrement"
|
:next-level-indent-increment="nextLevelIndentIncrement"
|
||||||
|
:is-expandable="isExpandable"
|
||||||
@node-dragover="handleDragOver"
|
@node-dragover="handleDragOver"
|
||||||
@node-dragstart="handleDragStart"
|
@node-dragstart="handleDragStart"
|
||||||
@node-dragleave="handleDragLeave"
|
@node-dragleave="handleDragLeave"
|
||||||
@ -56,7 +57,14 @@ import SearchInput from '@editor/components/SearchInput.vue';
|
|||||||
import Tree from '@editor/components/Tree.vue';
|
import Tree from '@editor/components/Tree.vue';
|
||||||
import { useFilter } from '@editor/hooks/use-filter';
|
import { useFilter } from '@editor/hooks/use-filter';
|
||||||
import { useServices } from '@editor/hooks/use-services';
|
import { useServices } from '@editor/hooks/use-services';
|
||||||
import type { CustomContentMenuFunction, LayerPanelSlots, MenuButton, MenuComponent, TreeNodeData } from '@editor/type';
|
import type {
|
||||||
|
CustomContentMenuFunction,
|
||||||
|
IsExpandableFunction,
|
||||||
|
LayerPanelSlots,
|
||||||
|
MenuButton,
|
||||||
|
MenuComponent,
|
||||||
|
TreeNodeData,
|
||||||
|
} from '@editor/type';
|
||||||
|
|
||||||
import LayerMenu from './LayerMenu.vue';
|
import LayerMenu from './LayerMenu.vue';
|
||||||
import LayerNodeTool from './LayerNodeTool.vue';
|
import LayerNodeTool from './LayerNodeTool.vue';
|
||||||
@ -76,6 +84,8 @@ defineProps<{
|
|||||||
indent?: number;
|
indent?: number;
|
||||||
nextLevelIndentIncrement?: number;
|
nextLevelIndentIncrement?: number;
|
||||||
customContentMenu: CustomContentMenuFunction;
|
customContentMenu: CustomContentMenuFunction;
|
||||||
|
/** 自定义判断组件树节点是否可展开(即是否要展示为拥有子节点的形态)的函数 */
|
||||||
|
isExpandable?: IsExpandableFunction;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const services = useServices();
|
const services = useServices();
|
||||||
|
|||||||
@ -669,6 +669,9 @@ export interface TreeNodeData {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 判断组件树节点是否可展开(即是否要展示为拥有子节点的形态)的函数 */
|
||||||
|
export type IsExpandableFunction = (_data: TreeNodeData, _nodeStatusMap: Map<Id, LayerNodeStatus>) => boolean;
|
||||||
|
|
||||||
export type AsyncBeforeHook<Value extends Array<string>, C extends Record<Value[number], (...args: any) => any>> = {
|
export type AsyncBeforeHook<Value extends Array<string>, C extends Record<Value[number], (...args: any) => any>> = {
|
||||||
[K in Value[number]]?: (...args: Parameters<C[K]>) => Promise<Parameters<C[K]>> | Parameters<C[K]>;
|
[K in Value[number]]?: (...args: Parameters<C[K]>) => Promise<Parameters<C[K]>> | Parameters<C[K]>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { Id } from '@tmagic/core';
|
import type { Id } from '@tmagic/core';
|
||||||
import { getKeys } from '@tmagic/utils';
|
import { getKeys } from '@tmagic/utils';
|
||||||
|
|
||||||
import type { LayerNodeStatus } from '@editor/type';
|
import type { IsExpandableFunction, LayerNodeStatus } from '@editor/type';
|
||||||
|
|
||||||
export const updateStatus = (nodeStatusMap: Map<Id, LayerNodeStatus>, id: Id, status: Partial<LayerNodeStatus>) => {
|
export const updateStatus = (nodeStatusMap: Map<Id, LayerNodeStatus>, id: Id, status: Partial<LayerNodeStatus>) => {
|
||||||
const nodeStatus = nodeStatusMap.get(id);
|
const nodeStatus = nodeStatusMap.get(id);
|
||||||
@ -13,3 +13,7 @@ export const updateStatus = (nodeStatusMap: Map<Id, LayerNodeStatus>, id: Id, st
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 默认的组件树节点是否可展开的判断函数:当节点的子项中至少存在一个可见节点时认为可展开 */
|
||||||
|
export const defaultIsExpandable: IsExpandableFunction = (data, nodeStatusMap) =>
|
||||||
|
Array.isArray(data.items) && data.items.some((item) => nodeStatusMap.get(item.id)?.visible);
|
||||||
|
|||||||
139
packages/editor/tests/unit/utils/tree.spec.ts
Normal file
139
packages/editor/tests/unit/utils/tree.spec.ts
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
// @vitest-environment node
|
||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
|
import type { Id } from '@tmagic/core';
|
||||||
|
|
||||||
|
import type { LayerNodeStatus, TreeNodeData } from '@editor/type';
|
||||||
|
import { defaultIsExpandable, updateStatus } from '@editor/utils/tree';
|
||||||
|
|
||||||
|
const buildStatus = (overrides: Partial<LayerNodeStatus> = {}): LayerNodeStatus => ({
|
||||||
|
visible: true,
|
||||||
|
expand: false,
|
||||||
|
selected: false,
|
||||||
|
draggable: false,
|
||||||
|
...overrides,
|
||||||
|
});
|
||||||
|
|
||||||
|
const buildStatusMap = (entries: [Id, Partial<LayerNodeStatus>][]): Map<Id, LayerNodeStatus> => {
|
||||||
|
const map = new Map<Id, LayerNodeStatus>();
|
||||||
|
entries.forEach(([id, status]) => map.set(id, buildStatus(status)));
|
||||||
|
return map;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('defaultIsExpandable', () => {
|
||||||
|
test('节点没有 items 时返回 false', () => {
|
||||||
|
const data: TreeNodeData = { id: 'node_1' };
|
||||||
|
const statusMap = buildStatusMap([['node_1', { visible: true }]]);
|
||||||
|
|
||||||
|
expect(defaultIsExpandable(data, statusMap)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('items 为空数组时返回 false', () => {
|
||||||
|
const data: TreeNodeData = { id: 'node_1', items: [] };
|
||||||
|
const statusMap = buildStatusMap([['node_1', { visible: true }]]);
|
||||||
|
|
||||||
|
expect(defaultIsExpandable(data, statusMap)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('items 中存在至少一个可见子节点时返回 true', () => {
|
||||||
|
const data: TreeNodeData = {
|
||||||
|
id: 'parent_1',
|
||||||
|
items: [{ id: 'child_1' }, { id: 'child_2' }],
|
||||||
|
};
|
||||||
|
const statusMap = buildStatusMap([
|
||||||
|
['parent_1', { visible: true }],
|
||||||
|
['child_1', { visible: false }],
|
||||||
|
['child_2', { visible: true }],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(defaultIsExpandable(data, statusMap)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('所有子节点都不可见时返回 false(被搜索过滤的场景)', () => {
|
||||||
|
const data: TreeNodeData = {
|
||||||
|
id: 'parent_1',
|
||||||
|
items: [{ id: 'child_1' }, { id: 'child_2' }],
|
||||||
|
};
|
||||||
|
const statusMap = buildStatusMap([
|
||||||
|
['parent_1', { visible: true }],
|
||||||
|
['child_1', { visible: false }],
|
||||||
|
['child_2', { visible: false }],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(defaultIsExpandable(data, statusMap)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('子节点状态在 statusMap 中缺失时视为不可见', () => {
|
||||||
|
const data: TreeNodeData = {
|
||||||
|
id: 'parent_1',
|
||||||
|
items: [{ id: 'child_1' }],
|
||||||
|
};
|
||||||
|
const statusMap = buildStatusMap([['parent_1', { visible: true }]]);
|
||||||
|
|
||||||
|
expect(defaultIsExpandable(data, statusMap)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('items 不是数组时返回 false', () => {
|
||||||
|
const data: TreeNodeData = { id: 'node_1', items: undefined };
|
||||||
|
const statusMap = buildStatusMap([['node_1', { visible: true }]]);
|
||||||
|
|
||||||
|
expect(defaultIsExpandable(data, statusMap)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateStatus', () => {
|
||||||
|
test('更新已存在节点的部分状态字段', () => {
|
||||||
|
const statusMap = buildStatusMap([['node_1', { visible: true, expand: false, selected: false, draggable: false }]]);
|
||||||
|
|
||||||
|
updateStatus(statusMap, 'node_1', { expand: true, selected: true });
|
||||||
|
|
||||||
|
expect(statusMap.get('node_1')).toEqual({
|
||||||
|
visible: true,
|
||||||
|
expand: true,
|
||||||
|
selected: true,
|
||||||
|
draggable: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('节点不存在时静默返回,不抛错', () => {
|
||||||
|
const statusMap = buildStatusMap([]);
|
||||||
|
|
||||||
|
expect(() => updateStatus(statusMap, 'node_missing', { expand: true })).not.toThrow();
|
||||||
|
expect(statusMap.has('node_missing')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('忽略状态对象中值为 undefined 的字段', () => {
|
||||||
|
const statusMap = buildStatusMap([['node_1', { visible: true, expand: true }]]);
|
||||||
|
|
||||||
|
updateStatus(statusMap, 'node_1', { expand: undefined, selected: true });
|
||||||
|
|
||||||
|
const status = statusMap.get('node_1')!;
|
||||||
|
expect(status.expand).toBe(true);
|
||||||
|
expect(status.selected).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('将传入的非 boolean 值强制转换为 boolean', () => {
|
||||||
|
const statusMap = buildStatusMap([['node_1', { visible: false }]]);
|
||||||
|
|
||||||
|
updateStatus(statusMap, 'node_1', { visible: 1 as unknown as boolean });
|
||||||
|
|
||||||
|
expect(statusMap.get('node_1')?.visible).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user