fix(shortcuts): scope dynamic close listeners (#716)

This commit is contained in:
InfinityPacer
2026-08-25 12:11:47 +08:00
committed by GitHub
parent 99d306fa96
commit 62b2eae120
3 changed files with 42 additions and 3 deletions
+4 -1
View File
@@ -14,6 +14,8 @@ const props = withDefaults(
icon?: string
maxWidth?: string
modelValue?: boolean
/** 动态视图是否声明并可能触发 close 事件。 */
supportsClose?: boolean
subtitle?: string
title: string
view: Component
@@ -25,6 +27,7 @@ const props = withDefaults(
icon: 'mdi-cog',
maxWidth: '35rem',
modelValue: true,
supportsClose: false,
viewProps: () => ({}),
},
)
@@ -81,7 +84,7 @@ function closeDialog() {
</VCardItem>
<VDivider />
<VCardText :class="bodyClasses">
<Component :is="props.view" v-bind="props.viewProps" @close="closeDialog" />
<Component :is="props.view" v-bind="props.viewProps" v-on="props.supportsClose ? { close: closeDialog } : {}" />
</VCardText>
</VCard>
</VDialog>
@@ -2,20 +2,30 @@ import ShortcutToolDialog from '@/components/dialog/ShortcutToolDialog.vue'
import { screen } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import { renderWithProviders } from '@tests/support/render'
import { defineComponent, markRaw } from 'vue'
import { describe, expect, it } from 'vitest'
import { defineComponent, h, markRaw } from 'vue'
import { describe, expect, it, vi } from 'vitest'
const ToolView = defineComponent({
emits: ['close'],
template: '<button type="button" @click="$emit(\'close\')">关闭工具</button>',
})
const FragmentToolView = defineComponent({
setup(_, { attrs }) {
return () => [
h('span', { 'data-testid': 'close-listener-state' }, attrs.onClose ? '已绑定' : '未绑定'),
h('span', '普通工具内容'),
]
},
})
describe('ShortcutToolDialog', () => {
it('closes the outer dialog when the active tool requests it', async () => {
const user = userEvent.setup()
const result = await renderWithProviders(ShortcutToolDialog, {
props: {
modelValue: true,
supportsClose: true,
title: '测试工具',
view: markRaw(ToolView),
},
@@ -31,4 +41,26 @@ describe('ShortcutToolDialog', () => {
expect(result.emitted()['update:modelValue']).toEqual([[false]])
expect(result.emitted().close).toEqual([[]])
})
it('does not pass a close listener to an ordinary fragment view', async () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
await renderWithProviders(ShortcutToolDialog, {
props: {
modelValue: true,
title: '普通工具',
view: markRaw(FragmentToolView),
},
global: {
stubs: {
VDialogCloseBtn: true,
},
},
})
expect(screen.getByTestId('close-listener-state')).toHaveTextContent('未绑定')
expect(warn.mock.calls.some(([message]) => String(message).includes('Extraneous non-emits event listeners'))).toBe(
false,
)
})
})
+4
View File
@@ -31,6 +31,8 @@ export type ShortcutToolItem = PermissionProtectedItem & {
dialogSubtitle?: string
icon: string
maxWidth?: string
/** 动态视图是否声明并可能触发 close 事件。 */
supportsClose?: boolean
subtitle: string
title: string
titleText?: string
@@ -50,6 +52,7 @@ export function useShortcutTools() {
dialog: 'nameTest',
component: NameTestView,
maxWidth: '65rem',
supportsClose: true,
titleText: t('shortcut.recognition.title'),
},
{
@@ -143,6 +146,7 @@ export function useShortcutTools() {
icon: item.icon,
maxWidth: item.maxWidth ?? '35rem',
subtitle: item.dialogSubtitle,
supportsClose: item.supportsClose,
title: item.titleText ?? item.title,
view: item.component,
},