mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-11-05 17:40:54 +00:00
fix(editor): 数据源与代码块删除确认改用 Popconfirm
This commit is contained in:
parent
8e4391972b
commit
c2272b12d8
@ -20,12 +20,29 @@
|
||||
</template>
|
||||
|
||||
<template #tree-node-tool="{ data }">
|
||||
<TMagicTooltip v-if="data.type === 'code'" effect="dark" :content="editable ? '编辑' : '查看'" placement="bottom">
|
||||
<Icon :icon="editable ? Edit : View" class="edit-icon" @click.stop="editCode(`${data.key}`)"></Icon>
|
||||
</TMagicTooltip>
|
||||
<TMagicTooltip v-if="data.type === 'code' && editable" effect="dark" content="删除" placement="bottom">
|
||||
<Icon :icon="Close" class="edit-icon" @click.stop="deleteCode(`${data.key}`, { historySource: 'tree' })"></Icon>
|
||||
<TMagicTooltip v-if="data.type === 'code'" effect="dark" :content="editable ? '编辑' : '查看'" placement="left">
|
||||
<TMagicButton
|
||||
link
|
||||
:icon="editable ? Edit : View"
|
||||
class="edit-icon"
|
||||
@click.stop="editCode(`${data.key}`)"
|
||||
></TMagicButton>
|
||||
</TMagicTooltip>
|
||||
<TMagicPopconfirm
|
||||
v-if="data.type === 'code' && editable"
|
||||
title="确定删除该代码块吗?"
|
||||
placement="right"
|
||||
:teleported="true"
|
||||
@confirm="deleteCode(`${data.key}`, { historySource: 'tree' })"
|
||||
>
|
||||
<template #reference>
|
||||
<span class="delete-icon-wrapper" @click.stop="deleteClickHandler(`${data.key}`)">
|
||||
<TMagicTooltip effect="dark" content="删除" placement="right">
|
||||
<TMagicButton link :icon="Close" class="edit-icon"></TMagicButton>
|
||||
</TMagicTooltip>
|
||||
</span>
|
||||
</template>
|
||||
</TMagicPopconfirm>
|
||||
<slot name="code-block-panel-tool" :id="data.key" :data="data"></slot>
|
||||
</template>
|
||||
</Tree>
|
||||
@ -37,9 +54,8 @@ import { Close, Edit, View } from '@element-plus/icons-vue';
|
||||
|
||||
import type { Id, MNode } from '@tmagic/core';
|
||||
import { DepTargetType } from '@tmagic/core';
|
||||
import { tMagicMessage, tMagicMessageBox, TMagicTooltip } from '@tmagic/design';
|
||||
import { TMagicButton, tMagicMessage, TMagicPopconfirm, TMagicTooltip } from '@tmagic/design';
|
||||
|
||||
import Icon from '@editor/components/Icon.vue';
|
||||
import Tree from '@editor/components/Tree.vue';
|
||||
import { useFilter } from '@editor/hooks/use-filter';
|
||||
import { useNodeStatus } from '@editor/hooks/use-node-status';
|
||||
@ -142,29 +158,38 @@ const editCode = (id: string) => {
|
||||
emit('edit', id);
|
||||
};
|
||||
|
||||
const deleteCode = async (id: string, { historySource }: { historySource?: HistoryOpSource } = {}) => {
|
||||
/** 返回不能删除的原因,可删除时返回 undefined */
|
||||
const getDeleteErrorType = (id: string): CodeDeleteErrorType | undefined => {
|
||||
const currentCode = codeList.value.find((codeItem) => codeItem.id === id);
|
||||
const existBinds = Boolean(currentCode?.items?.length);
|
||||
const undeleteableList = codeBlockService.getUndeletableList() || [];
|
||||
if (!existBinds && !undeleteableList.includes(id)) {
|
||||
await tMagicMessageBox.confirm('确定删除该代码块吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
if (currentCode?.items?.length) {
|
||||
return CodeDeleteErrorType.BIND;
|
||||
}
|
||||
|
||||
// 无绑定关系,且不在不可删除列表中
|
||||
emit('remove', id, { historySource });
|
||||
} else {
|
||||
return (codeBlockService.getUndeletableList() || []).includes(id) ? CodeDeleteErrorType.UNDELETEABLE : undefined;
|
||||
};
|
||||
|
||||
/** 删除确认由 TMagicPopconfirm 完成,这里只做校验与删除 */
|
||||
const deleteCode = (id: string, { historySource }: { historySource?: HistoryOpSource } = {}) => {
|
||||
const errorType = getDeleteErrorType(id);
|
||||
|
||||
if (errorType) {
|
||||
if (typeof props.customError === 'function') {
|
||||
props.customError(id, existBinds ? CodeDeleteErrorType.BIND : CodeDeleteErrorType.UNDELETEABLE);
|
||||
props.customError(id, errorType);
|
||||
} else if (errorType === CodeDeleteErrorType.BIND) {
|
||||
tMagicMessage.error('代码块存在绑定关系,不可删除');
|
||||
} else {
|
||||
if (existBinds) {
|
||||
tMagicMessage.error('代码块存在绑定关系,不可删除');
|
||||
} else {
|
||||
tMagicMessage.error('代码块不可删除');
|
||||
}
|
||||
tMagicMessage.error('代码块不可删除');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
emit('remove', id, { historySource });
|
||||
};
|
||||
|
||||
/** 不可删除的代码块点击后直接说明原因,无需等待确认 */
|
||||
const deleteClickHandler = (id: string) => {
|
||||
if (getDeleteErrorType(id)) {
|
||||
deleteCode(id, { historySource: 'tree' });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -19,12 +19,29 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #tree-node-tool="{ data }">
|
||||
<TMagicTooltip v-if="data.type === 'ds'" effect="dark" :content="editable ? '编辑' : '查看'" placement="bottom">
|
||||
<Icon :icon="editable ? Edit : View" class="edit-icon" @click.stop="editHandler(`${data.key}`)"></Icon>
|
||||
</TMagicTooltip>
|
||||
<TMagicTooltip v-if="data.type === 'ds' && editable" effect="dark" content="删除" placement="bottom">
|
||||
<Icon :icon="Close" class="edit-icon" @click.stop="removeHandler(`${data.key}`)"></Icon>
|
||||
<TMagicTooltip v-if="data.type === 'ds'" effect="dark" :content="editable ? '编辑' : '查看'" placement="left">
|
||||
<TMagicButton
|
||||
link
|
||||
:icon="editable ? Edit : View"
|
||||
class="edit-icon"
|
||||
@click.stop="editHandler(`${data.key}`)"
|
||||
></TMagicButton>
|
||||
</TMagicTooltip>
|
||||
<TMagicPopconfirm
|
||||
v-if="data.type === 'ds' && editable"
|
||||
title="确定删除?"
|
||||
placement="right"
|
||||
:teleported="true"
|
||||
@confirm="removeHandler(`${data.key}`)"
|
||||
>
|
||||
<template #reference>
|
||||
<span class="delete-icon-wrapper" @click.stop>
|
||||
<TMagicTooltip effect="dark" content="删除" placement="right">
|
||||
<TMagicButton link :icon="Close" class="edit-icon"></TMagicButton>
|
||||
</TMagicTooltip>
|
||||
</span>
|
||||
</template>
|
||||
</TMagicPopconfirm>
|
||||
<slot name="data-source-panel-tool" :data="data"></slot>
|
||||
</template>
|
||||
</Tree>
|
||||
@ -35,9 +52,8 @@ import { computed } from 'vue';
|
||||
import { Close, Edit, View } from '@element-plus/icons-vue';
|
||||
|
||||
import { DepData, DepTargetType, Id, MNode } from '@tmagic/core';
|
||||
import { TMagicTooltip } from '@tmagic/design';
|
||||
import { TMagicButton, TMagicPopconfirm, TMagicTooltip } from '@tmagic/design';
|
||||
|
||||
import Icon from '@editor/components/Icon.vue';
|
||||
import Tree from '@editor/components/Tree.vue';
|
||||
import { useFilter } from '@editor/hooks/use-filter';
|
||||
import { useNodeStatus } from '@editor/hooks/use-node-status';
|
||||
@ -156,7 +172,7 @@ const editHandler = (id: string) => {
|
||||
emit('edit', id);
|
||||
};
|
||||
|
||||
const removeHandler = async (id: string) => {
|
||||
const removeHandler = (id: string) => {
|
||||
emit('remove', id);
|
||||
};
|
||||
|
||||
|
||||
@ -50,7 +50,7 @@
|
||||
import { computed, inject, ref, useTemplateRef, watch } from 'vue';
|
||||
import { mergeWith } from 'lodash-es';
|
||||
|
||||
import { tMagicMessageBox, TMagicScrollbar } from '@tmagic/design';
|
||||
import { TMagicScrollbar } from '@tmagic/design';
|
||||
|
||||
import ContentMenu from '@editor/components/ContentMenu.vue';
|
||||
import SearchInput from '@editor/components/SearchInput.vue';
|
||||
@ -133,13 +133,8 @@ const addHandler = (type: string) => {
|
||||
editDialog.value.show();
|
||||
};
|
||||
|
||||
const removeHandler = async (id: string) => {
|
||||
await tMagicMessageBox.confirm('确定删除?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
|
||||
/** 删除确认由 DataSourceList 中的 TMagicPopconfirm 完成,这里只负责执行删除 */
|
||||
const removeHandler = (id: string) => {
|
||||
dataSourceService.remove(id, { historySource: 'tree-contextmenu' });
|
||||
};
|
||||
|
||||
|
||||
@ -76,9 +76,21 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
// Element Plus 的 link 按钮左右 padding 是 2px,TDesign 的 text 变体默认
|
||||
// 为 calc(var(--td-comp-paddingLR-l) - 1px)(15px),会把工具栏图标按钮撑宽。
|
||||
.t-button--variant-text {
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.tmagic-design-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.delete-icon-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,6 +50,21 @@ vi.mock('@tmagic/design', () => ({
|
||||
return () => h('div', { class: 'fake-tooltip' }, slots.default?.());
|
||||
},
|
||||
}),
|
||||
TMagicPopconfirm: defineComponent({
|
||||
name: 'TMagicPopconfirm',
|
||||
props: ['title', 'placement', 'width'],
|
||||
emits: ['confirm', 'cancel'],
|
||||
setup(_p, { slots }) {
|
||||
return () => h('div', { class: 'fake-popconfirm' }, slots.reference?.());
|
||||
},
|
||||
}),
|
||||
TMagicButton: defineComponent({
|
||||
name: 'TMagicButton',
|
||||
props: ['icon', 'link'],
|
||||
setup(_p, { slots, attrs }) {
|
||||
return () => h('button', { ...attrs, class: 'edit-icon' }, slots.default?.());
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@editor/components/Icon.vue', () => ({
|
||||
@ -90,6 +105,7 @@ beforeEach(() => {
|
||||
codeBlockService.getCodeDsl.mockReturnValue({ c1: { name: 'C1' } });
|
||||
codeBlockService.getEditStatus.mockReturnValue(true);
|
||||
codeBlockService.getUndeletableList.mockReturnValue([]);
|
||||
depService.getTarget.mockReturnValue(null);
|
||||
editorService.get.mockImplementation((k: string) => {
|
||||
if (k === 'root') return { items: [{ id: 'p1', name: 'page1' }] };
|
||||
if (k === 'stage') return { select: vi.fn() };
|
||||
@ -109,12 +125,20 @@ describe('CodeBlockList', () => {
|
||||
expect(wrapper.emitted('edit')?.[0]?.[0]).toBe('c1');
|
||||
});
|
||||
|
||||
test('删除按钮 弹窗确认后 emit remove', async () => {
|
||||
test('删除图标由 Popconfirm 包裹,确认后 emit remove', async () => {
|
||||
const wrapper = mount(CodeBlockList);
|
||||
const popconfirm = wrapper.findComponent({ name: 'TMagicPopconfirm' });
|
||||
expect(popconfirm.exists()).toBe(true);
|
||||
expect(popconfirm.props('title')).toBe('确定删除该代码块吗?');
|
||||
expect(popconfirm.props('placement')).toBe('right');
|
||||
|
||||
await wrapper.findAll('.edit-icon')[1].trigger('click');
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
expect(messageBoxConfirm).toHaveBeenCalled();
|
||||
expect(wrapper.emitted('remove')).toBeFalsy();
|
||||
expect(messageBoxConfirm).not.toHaveBeenCalled();
|
||||
|
||||
popconfirm.vm.$emit('confirm');
|
||||
expect(wrapper.emitted('remove')?.[0]?.[0]).toBe('c1');
|
||||
expect(wrapper.emitted('remove')?.[0]?.[1]).toEqual({ historySource: 'tree' });
|
||||
});
|
||||
|
||||
test('点击 Tree 节点选中组件', async () => {
|
||||
@ -136,21 +160,53 @@ describe('CodeBlockList', () => {
|
||||
expect(wrapper.emitted('node-contextmenu')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('删除按钮: 在不可删除列表 提示错误', async () => {
|
||||
test('删除按钮: 在不可删除列表 点击时提示错误', async () => {
|
||||
codeBlockService.getUndeletableList.mockReturnValue(['c1']);
|
||||
const wrapper = mount(CodeBlockList);
|
||||
await wrapper.findAll('.edit-icon')[1].trigger('click');
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
expect(wrapper.emitted('remove')).toBeFalsy();
|
||||
expect(messageError).toHaveBeenCalledWith('代码块不可删除');
|
||||
});
|
||||
|
||||
test('Popconfirm 确认时仍校验不可删除列表', () => {
|
||||
codeBlockService.getUndeletableList.mockReturnValue(['c1']);
|
||||
const wrapper = mount(CodeBlockList);
|
||||
wrapper.findComponent({ name: 'TMagicPopconfirm' }).vm.$emit('confirm');
|
||||
expect(wrapper.emitted('remove')).toBeFalsy();
|
||||
expect(messageError).toHaveBeenCalledWith('代码块不可删除');
|
||||
});
|
||||
|
||||
test('删除按钮: 存在绑定关系 提示错误', async () => {
|
||||
depService.getTarget.mockReturnValue({
|
||||
id: 'c1',
|
||||
deps: { n1: { name: 'N1', keys: ['k'], data: { pageId: 'p1' } } },
|
||||
} as any);
|
||||
const wrapper = mount(CodeBlockList);
|
||||
await wrapper.findAll('.edit-icon')[1].trigger('click');
|
||||
expect(wrapper.emitted('remove')).toBeFalsy();
|
||||
expect(messageError).toHaveBeenCalledWith('代码块存在绑定关系,不可删除');
|
||||
});
|
||||
|
||||
test('customError 函数被调用', async () => {
|
||||
codeBlockService.getUndeletableList.mockReturnValue(['c1']);
|
||||
const customError = vi.fn();
|
||||
const wrapper = mount(CodeBlockList, { props: { customError } as any });
|
||||
await wrapper.findAll('.edit-icon')[1].trigger('click');
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
expect(customError).toHaveBeenCalledWith('c1', 'undeleteable');
|
||||
});
|
||||
|
||||
test('customError 在 Popconfirm 确认时被调用', () => {
|
||||
codeBlockService.getUndeletableList.mockReturnValue(['c1']);
|
||||
const customError = vi.fn();
|
||||
const wrapper = mount(CodeBlockList, { props: { customError } as any });
|
||||
wrapper.findComponent({ name: 'TMagicPopconfirm' }).vm.$emit('confirm');
|
||||
expect(customError).toHaveBeenCalledWith('c1', 'undeleteable');
|
||||
});
|
||||
|
||||
test('editable=false 时不显示删除图标和 Popconfirm', () => {
|
||||
codeBlockService.getEditStatus.mockReturnValue(false);
|
||||
const wrapper = mount(CodeBlockList);
|
||||
expect(wrapper.findAll('.edit-icon').length).toBe(1);
|
||||
expect(wrapper.findComponent({ name: 'TMagicPopconfirm' }).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@ -29,7 +29,9 @@ vi.mock('@editor/hooks/use-node-status', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('@editor/hooks/use-filter', () => ({
|
||||
useFilter: () => ({ filterTextChangeHandler: vi.fn() }),
|
||||
useFilter: (list: any, _nodeStatusMap: any, filterNode: any) => ({
|
||||
filterTextChangeHandler: (value: string) => list.value.forEach((item: any) => filterNode(value, item)),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@editor/components/Tree.vue', () => ({
|
||||
@ -74,10 +76,26 @@ vi.mock('@tmagic/design', () => ({
|
||||
return () => h('div', { class: 'fake-tooltip' }, slots.default?.());
|
||||
},
|
||||
}),
|
||||
TMagicPopconfirm: defineComponent({
|
||||
name: 'TMagicPopconfirm',
|
||||
props: ['title', 'placement', 'width'],
|
||||
emits: ['confirm', 'cancel'],
|
||||
setup(_p, { slots }) {
|
||||
return () => h('div', { class: 'fake-popconfirm' }, slots.reference?.());
|
||||
},
|
||||
}),
|
||||
TMagicButton: defineComponent({
|
||||
name: 'TMagicButton',
|
||||
props: ['icon', 'link'],
|
||||
setup(_p, { slots, attrs }) {
|
||||
return () => h('button', { ...attrs, class: 'edit-icon' }, slots.default?.());
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
depService.getTargets.mockReturnValue({});
|
||||
dataSourceService.get.mockImplementation((k: string) => {
|
||||
if (k === 'editable') return true;
|
||||
if (k === 'dataSources') return [{ id: 'd1', title: 'D1', methods: [] }];
|
||||
@ -123,14 +141,21 @@ describe('DataSourceList', () => {
|
||||
expect(wrapper.emitted('edit')?.[0]?.[0]).toBe('d1');
|
||||
});
|
||||
|
||||
test('点击删除图标 emit remove (editable=true)', async () => {
|
||||
test('删除图标由 Popconfirm 包裹,确认后才 emit remove', async () => {
|
||||
const wrapper = mount(DataSourceList);
|
||||
const icons = wrapper.findAll('.edit-icon');
|
||||
await icons[1].trigger('click');
|
||||
const popconfirm = wrapper.findComponent({ name: 'TMagicPopconfirm' });
|
||||
expect(popconfirm.exists()).toBe(true);
|
||||
expect(popconfirm.props('title')).toBe('确定删除?');
|
||||
expect(popconfirm.props('placement')).toBe('right');
|
||||
|
||||
await wrapper.findAll('.edit-icon')[1].trigger('click');
|
||||
expect(wrapper.emitted('remove')).toBeFalsy();
|
||||
|
||||
popconfirm.vm.$emit('confirm');
|
||||
expect(wrapper.emitted('remove')?.[0]?.[0]).toBe('d1');
|
||||
});
|
||||
|
||||
test('editable=false 时不显示删除图标', () => {
|
||||
test('editable=false 时不显示删除图标和 Popconfirm', () => {
|
||||
dataSourceService.get.mockImplementation((k: string) => {
|
||||
if (k === 'editable') return false;
|
||||
if (k === 'dataSources') return [{ id: 'd1', title: 'D1' }];
|
||||
@ -138,6 +163,7 @@ describe('DataSourceList', () => {
|
||||
});
|
||||
const wrapper = mount(DataSourceList);
|
||||
expect(wrapper.findAll('.edit-icon').length).toBe(1);
|
||||
expect(wrapper.findComponent({ name: 'TMagicPopconfirm' }).exists()).toBe(false);
|
||||
});
|
||||
|
||||
test('list 计算: dataSources 为空时为空数组', () => {
|
||||
@ -149,4 +175,32 @@ describe('DataSourceList', () => {
|
||||
const wrapper = mount(DataSourceList);
|
||||
expect(wrapper.findComponent({ name: 'TreeStub' }).props('data').length).toBe(0);
|
||||
});
|
||||
|
||||
test('list 计算: 合并依赖节点并按页面分类', () => {
|
||||
depService.getTargets.mockReturnValue({
|
||||
d1: { deps: { n1: { name: 'N1', keys: ['k1'], data: { pageId: 'p1' } } } },
|
||||
} as any);
|
||||
editorService.get.mockImplementation((k: string) => {
|
||||
if (k === 'root')
|
||||
return {
|
||||
items: [
|
||||
{ id: 'p1', name: 'page1' },
|
||||
{ id: 'p2', name: 'page2' },
|
||||
],
|
||||
};
|
||||
return null;
|
||||
});
|
||||
|
||||
const wrapper = mount(DataSourceList);
|
||||
const list = wrapper.findComponent({ name: 'TreeStub' }).props('data') as any[];
|
||||
expect(list[0].items).toHaveLength(1);
|
||||
expect(list[0].items[0].items.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('filter 空关键字与名称匹配', () => {
|
||||
const wrapper = mount(DataSourceList);
|
||||
(wrapper.vm as any).filter('');
|
||||
(wrapper.vm as any).filter('D1');
|
||||
expect(wrapper.findComponent({ name: 'TreeStub' }).props('data')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@ -187,8 +187,8 @@ describe('DataSourceListPanel', () => {
|
||||
await wrapper.find('.edit-btn').trigger('click');
|
||||
expect(editHandler).toHaveBeenCalledWith('d1');
|
||||
await wrapper.find('.remove-btn').trigger('click');
|
||||
expect(messageBoxConfirm).toHaveBeenCalled();
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
// 确认已下沉到 DataSourceList 的 Popconfirm,面板不再弹出 MessageBox
|
||||
expect(messageBoxConfirm).not.toHaveBeenCalled();
|
||||
expect(dataSourceService.remove).toHaveBeenCalledWith('d1', { historySource: 'tree-contextmenu' });
|
||||
await wrapper.find('.ctx-btn').trigger('click');
|
||||
expect(nodeContentMenuHandler).toHaveBeenCalled();
|
||||
@ -217,4 +217,32 @@ describe('DataSourceListPanel', () => {
|
||||
expect(events).toContain('edit-data-source');
|
||||
expect(events).toContain('remove-data-source');
|
||||
});
|
||||
|
||||
test('eventBus remove-data-source 执行删除', () => {
|
||||
mountIt();
|
||||
const handler = eventBusOn.mock.calls.find((c: any[]) => c[0] === 'remove-data-source')?.[1];
|
||||
handler?.('d2');
|
||||
expect(dataSourceService.remove).toHaveBeenCalledWith('d2', { historySource: 'tree-contextmenu' });
|
||||
});
|
||||
|
||||
test('eventBus 编辑事件重置定位信息并打开编辑', () => {
|
||||
mountIt();
|
||||
const handlerOf = (name: string) => eventBusOn.mock.calls.find((c: any[]) => c[0] === name)?.[1];
|
||||
|
||||
handlerOf('edit-data-source')?.('d1');
|
||||
handlerOf('edit-data-source-method')?.('d2', 'm1');
|
||||
handlerOf('edit-data-source-field')?.('d3', ['f1']);
|
||||
|
||||
expect(editHandler).toHaveBeenCalledWith('d1');
|
||||
expect(editHandler).toHaveBeenCalledWith('d2');
|
||||
expect(editHandler).toHaveBeenCalledWith('d3');
|
||||
});
|
||||
|
||||
test('addHandler 合并表单配置中的数组值', async () => {
|
||||
editDialog.value = { show: editDialogShow };
|
||||
dataSourceService.getFormValue.mockReturnValueOnce({ id: 'fv', type: 'http', fields: [{ name: 'a' }] });
|
||||
const wrapper = mountIt();
|
||||
await wrapper.find('.add-btn').trigger('click');
|
||||
expect(dataSourceValues.value.fields).toEqual([{ name: 'a' }]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||
*
|
||||
* Copyright (C) 2025 Tencent.
|
||||
*/
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
import { setDesignConfig } from '@tmagic/design';
|
||||
|
||||
import CodeBlockList from '@editor/layouts/sidebar/code-block/CodeBlockList.vue';
|
||||
import DataSourceList from '@editor/layouts/sidebar/data-source/DataSourceList.vue';
|
||||
|
||||
import elementPlusAdapter from '../../../../../element-plus-adapter/src/index';
|
||||
|
||||
setDesignConfig(elementPlusAdapter);
|
||||
|
||||
const editorService = {
|
||||
get: vi.fn((key: string) => (key === 'root' ? { items: [{ id: 'p1', name: 'page1' }] } : null)),
|
||||
select: vi.fn(),
|
||||
};
|
||||
const depService = {
|
||||
getTargets: vi.fn(() => ({})),
|
||||
getTarget: vi.fn(() => null),
|
||||
};
|
||||
const dataSourceService = {
|
||||
get: vi.fn((key: string) => {
|
||||
if (key === 'editable') return true;
|
||||
if (key === 'dataSources') return [{ id: 'd1', title: 'D1' }];
|
||||
return null;
|
||||
}),
|
||||
};
|
||||
const codeBlockService = {
|
||||
getCodeDsl: vi.fn(() => ({ c1: { name: 'C1' } })),
|
||||
getCodeContentById: vi.fn(),
|
||||
getEditStatus: vi.fn(() => true),
|
||||
getUndeletableList: vi.fn(() => []),
|
||||
};
|
||||
|
||||
vi.mock('@editor/hooks/use-services', () => ({
|
||||
useServices: () => ({ editorService, depService, dataSourceService, codeBlockService }),
|
||||
}));
|
||||
|
||||
/** 点击 Popconfirm 的确认按钮(Element Plus 默认文案为 Yes) */
|
||||
const clickConfirm = async () => {
|
||||
await vi.waitFor(() => {
|
||||
expect(document.body.textContent).toContain('Yes');
|
||||
});
|
||||
const confirmBtn = Array.from(document.querySelectorAll('button')).find(
|
||||
(btn) => btn.textContent?.trim().toLowerCase() === 'yes',
|
||||
);
|
||||
confirmBtn?.click();
|
||||
};
|
||||
|
||||
/** 浮层需挂载到 body,且 placement 真正作用到 popper 上 */
|
||||
const expectRightPlacement = async (wrapper: { element: Element }) => {
|
||||
await vi.waitFor(() => {
|
||||
expect(document.querySelector('.el-popper')?.getAttribute('data-popper-placement')).toBe('right');
|
||||
});
|
||||
const popper = document.querySelector('.el-popper');
|
||||
expect(popper).toBeTruthy();
|
||||
expect(wrapper.element.contains(popper!)).toBe(false);
|
||||
};
|
||||
|
||||
describe('侧栏删除 Popconfirm(真实 element-plus)', () => {
|
||||
test('数据源:点击删除图标弹出 Popconfirm,确认后 emit remove', async () => {
|
||||
const wrapper = mount(DataSourceList, { attachTo: document.body });
|
||||
|
||||
await wrapper.findAll('.edit-icon')[1].trigger('click');
|
||||
await vi.waitFor(() => {
|
||||
expect(document.body.textContent).toContain('确定删除?');
|
||||
});
|
||||
await expectRightPlacement(wrapper);
|
||||
expect(wrapper.emitted('remove')).toBeFalsy();
|
||||
|
||||
await clickConfirm();
|
||||
await vi.waitFor(() => {
|
||||
expect(wrapper.emitted('remove')?.[0]?.[0]).toBe('d1');
|
||||
});
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('代码块:点击删除图标弹出 Popconfirm,确认后 emit remove', async () => {
|
||||
const wrapper = mount(CodeBlockList, { attachTo: document.body });
|
||||
|
||||
await wrapper.findAll('.edit-icon')[1].trigger('click');
|
||||
await vi.waitFor(() => {
|
||||
expect(document.body.textContent).toContain('确定删除该代码块吗?');
|
||||
});
|
||||
await expectRightPlacement(wrapper);
|
||||
expect(wrapper.emitted('remove')).toBeFalsy();
|
||||
|
||||
await clickConfirm();
|
||||
await vi.waitFor(() => {
|
||||
expect(wrapper.emitted('remove')?.[0]?.[0]).toBe('c1');
|
||||
});
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user