Files
workspace/code/fms/fms-vue/tests/components/modal-confirm.spec.js
T
2026-09-14 22:30:13 +08:00

109 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Modal.confirm 命令式确认对话框:开关时机与 onOk / onCancel 语义。
* 覆盖「确定后关闭」「onOk reject 保持打开」「取消」「点遮罩静默关闭(不触发 onCancel)」
* 「destroy 立即卸载」几种路径。
*/
import { afterEach, describe, expect, it, vi } from 'vitest'
import { flushPromises } from '@vue/test-utils'
import { nextTick } from 'vue'
import { confirm } from '@/components/ui/modal/modal-confirm.js'
/** Modal 用 Teleport 挂到 body,只能从 document 上找面板 */
const panel = () => document.querySelector('.fms-modal')
/** footer 按钮顺序:[取消, 确定] */
const footerButtons = () => [...document.querySelectorAll('.fms-modal-footer button')]
/**
* 关窗要等 Transition 的两帧 rAF 与 after-leave,时序不确定:
* 轮询到条件成立或超时为止。
*/
async function waitFor(predicate, timeout = 500) {
const start = Date.now()
while (Date.now() - start < timeout) {
await flushPromises()
await nextTick()
if (predicate()) return true
await new Promise((resolve) => setTimeout(resolve, 20))
}
return false
}
let active = null
afterEach(async () => {
active?.destroy()
active = null
await waitFor(() => !panel())
})
describe('Modal.confirm', () => {
it('确定:onOk resolve 后对话框关闭', async () => {
const onOk = vi.fn()
active = confirm({ title: '确认', content: '删除后不可恢复', onOk })
expect(panel()).toBeTruthy()
expect(document.body.textContent).toContain('删除后不可恢复')
footerButtons().at(-1).click()
expect(await waitFor(() => !panel())).toBe(true)
expect(onOk).toHaveBeenCalledTimes(1)
})
it('确定:onOk reject 时保持打开,等待重试或取消', async () => {
const onOk = vi.fn(() => Promise.reject(new Error('删除失败')))
active = confirm({ title: '确认', content: '内容', onOk })
footerButtons().at(-1).click()
// 等一段确定的时间,确认对话框没有进入关闭流程
await new Promise((resolve) => setTimeout(resolve, 100))
await flushPromises()
expect(onOk).toHaveBeenCalledTimes(1)
expect(panel()).toBeTruthy()
})
it('取消:onCancel 被调用并关闭', async () => {
const onCancel = vi.fn()
active = confirm({ title: '确认', content: '内容', onCancel })
footerButtons()[0].click()
expect(await waitFor(() => !panel())).toBe(true)
expect(onCancel).toHaveBeenCalledTimes(1)
})
it('点遮罩空白:关闭对话框但不触发 onCancel(静默关闭)', async () => {
const onCancel = vi.fn()
active = confirm({ title: '确认', content: '内容', onCancel })
const wrap = document.querySelector('.fms-modal-wrap')
expect(wrap).toBeTruthy()
// 点击 wrap 自身(面板外空白区域):target === currentTarget 才命中遮罩关闭分支。
// 遮罩关闭走 Modal.requestClose → emit('cancel'),但 confirm 未绑 @cancel,
// 故只由 v-model:open 关窗,不应触发调用方 onCancel。
wrap.click()
expect(await waitFor(() => !panel())).toBe(true)
expect(onCancel).not.toHaveBeenCalled()
})
it('okButtonProps.danger 映射为危险态确定按钮', () => {
active = confirm({ title: '确认', content: '内容', okButtonProps: { danger: true } })
expect(footerButtons().at(-1).className).toContain('button-danger')
})
it('destroy 立即卸载容器与遮罩', async () => {
active = confirm({ title: '确认', content: '内容' })
expect(panel()).toBeTruthy()
active.destroy()
active = null
await waitFor(() => !panel())
expect(panel()).toBeFalsy()
expect(document.querySelector('.fms-modal-wrap')).toBeFalsy()
})
})