2026-02-08 12:05:39 +08:00
|
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
|
|
|
|
import { setActivePinia, createPinia } from 'pinia'
|
|
|
|
|
|
|
|
|
|
|
|
// Mock i18n
|
|
|
|
|
|
vi.mock('@/i18n', () => ({
|
|
|
|
|
|
i18n: {
|
|
|
|
|
|
global: {
|
|
|
|
|
|
t: (key: string) => key,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
// Mock app store
|
|
|
|
|
|
const mockShowSuccess = vi.fn()
|
|
|
|
|
|
const mockShowError = vi.fn()
|
|
|
|
|
|
|
|
|
|
|
|
vi.mock('@/stores/app', () => ({
|
|
|
|
|
|
useAppStore: () => ({
|
|
|
|
|
|
showSuccess: mockShowSuccess,
|
|
|
|
|
|
showError: mockShowError,
|
|
|
|
|
|
}),
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
import { useClipboard } from '@/composables/useClipboard'
|
|
|
|
|
|
|
|
|
|
|
|
describe('useClipboard', () => {
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
|
setActivePinia(createPinia())
|
|
|
|
|
|
vi.useFakeTimers()
|
|
|
|
|
|
vi.clearAllMocks()
|
|
|
|
|
|
|
|
|
|
|
|
// 默认模拟安全上下文 + Clipboard API
|
|
|
|
|
|
Object.defineProperty(window, 'isSecureContext', { value: true, writable: true })
|
|
|
|
|
|
Object.defineProperty(navigator, 'clipboard', {
|
|
|
|
|
|
value: {
|
|
|
|
|
|
writeText: vi.fn().mockResolvedValue(undefined),
|
|
|
|
|
|
},
|
|
|
|
|
|
writable: true,
|
|
|
|
|
|
configurable: true,
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
|
vi.useRealTimers()
|
|
|
|
|
|
// 恢复 execCommand
|
|
|
|
|
|
if ('execCommand' in document) {
|
|
|
|
|
|
delete (document as any).execCommand
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('复制成功后 copied 变为 true', async () => {
|
|
|
|
|
|
const { copied, copyToClipboard } = useClipboard()
|
|
|
|
|
|
|
|
|
|
|
|
expect(copied.value).toBe(false)
|
|
|
|
|
|
|
|
|
|
|
|
await copyToClipboard('hello')
|
|
|
|
|
|
|
|
|
|
|
|
expect(copied.value).toBe(true)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('copied 在 2 秒后自动恢复为 false', async () => {
|
|
|
|
|
|
const { copied, copyToClipboard } = useClipboard()
|
|
|
|
|
|
|
|
|
|
|
|
await copyToClipboard('hello')
|
|
|
|
|
|
expect(copied.value).toBe(true)
|
|
|
|
|
|
|
|
|
|
|
|
vi.advanceTimersByTime(2000)
|
|
|
|
|
|
|
|
|
|
|
|
expect(copied.value).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('复制成功时调用 showSuccess', async () => {
|
|
|
|
|
|
const { copyToClipboard } = useClipboard()
|
|
|
|
|
|
|
|
|
|
|
|
await copyToClipboard('hello', '已复制')
|
|
|
|
|
|
|
|
|
|
|
|
expect(mockShowSuccess).toHaveBeenCalledWith('已复制')
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('无自定义消息时使用 i18n 默认消息', async () => {
|
|
|
|
|
|
const { copyToClipboard } = useClipboard()
|
|
|
|
|
|
|
|
|
|
|
|
await copyToClipboard('hello')
|
|
|
|
|
|
|
|
|
|
|
|
expect(mockShowSuccess).toHaveBeenCalledWith('common.copiedToClipboard')
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('空文本返回 false 且不复制', async () => {
|
|
|
|
|
|
const { copyToClipboard, copied } = useClipboard()
|
|
|
|
|
|
|
|
|
|
|
|
const result = await copyToClipboard('')
|
|
|
|
|
|
|
|
|
|
|
|
expect(result).toBe(false)
|
|
|
|
|
|
expect(copied.value).toBe(false)
|
|
|
|
|
|
expect(navigator.clipboard.writeText).not.toHaveBeenCalled()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('Clipboard API 失败时降级到 fallback', async () => {
|
2026-02-10 00:37:56 +08:00
|
|
|
|
const writeTextMock = navigator.clipboard.writeText as any
|
|
|
|
|
|
writeTextMock.mockRejectedValue(new Error('API failed'))
|
2026-02-08 12:05:39 +08:00
|
|
|
|
|
|
|
|
|
|
// jsdom 没有 execCommand,手动定义
|
2026-02-10 00:37:56 +08:00
|
|
|
|
const documentAny = document as any
|
|
|
|
|
|
documentAny.execCommand = vi.fn().mockReturnValue(true)
|
2026-02-08 12:05:39 +08:00
|
|
|
|
|
|
|
|
|
|
const { copyToClipboard, copied } = useClipboard()
|
|
|
|
|
|
const result = await copyToClipboard('fallback text')
|
|
|
|
|
|
|
|
|
|
|
|
expect(result).toBe(true)
|
|
|
|
|
|
expect(copied.value).toBe(true)
|
|
|
|
|
|
expect(document.execCommand).toHaveBeenCalledWith('copy')
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('非安全上下文使用 fallback', async () => {
|
|
|
|
|
|
Object.defineProperty(window, 'isSecureContext', { value: false, writable: true })
|
|
|
|
|
|
|
2026-02-10 00:37:56 +08:00
|
|
|
|
const documentAny = document as any
|
|
|
|
|
|
documentAny.execCommand = vi.fn().mockReturnValue(true)
|
2026-02-08 12:05:39 +08:00
|
|
|
|
|
|
|
|
|
|
const { copyToClipboard, copied } = useClipboard()
|
|
|
|
|
|
const result = await copyToClipboard('insecure context text')
|
|
|
|
|
|
|
|
|
|
|
|
expect(result).toBe(true)
|
|
|
|
|
|
expect(copied.value).toBe(true)
|
|
|
|
|
|
expect(navigator.clipboard.writeText).not.toHaveBeenCalled()
|
|
|
|
|
|
expect(document.execCommand).toHaveBeenCalledWith('copy')
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('所有复制方式均失败时调用 showError', async () => {
|
2026-02-10 00:37:56 +08:00
|
|
|
|
const writeTextMock = navigator.clipboard.writeText as any
|
|
|
|
|
|
writeTextMock.mockRejectedValue(new Error('fail'))
|
|
|
|
|
|
|
|
|
|
|
|
const documentAny = document as any
|
|
|
|
|
|
documentAny.execCommand = vi.fn().mockReturnValue(false)
|
2026-02-08 12:05:39 +08:00
|
|
|
|
|
|
|
|
|
|
const { copyToClipboard, copied } = useClipboard()
|
|
|
|
|
|
const result = await copyToClipboard('text')
|
|
|
|
|
|
|
|
|
|
|
|
expect(result).toBe(false)
|
|
|
|
|
|
expect(copied.value).toBe(false)
|
|
|
|
|
|
expect(mockShowError).toHaveBeenCalled()
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|