import { describe, it, expect, beforeAll, beforeEach, afterEach, vi } from 'vitest'
import { clearFixture, getFixture } from '../helpers/fixture'
import Clipboard from '../../src/clipboard'
const MARKUP = `
`
describe('Clipboard', () => {
let fixtureEl: HTMLElement
let written: string[]
beforeAll(() => {
fixtureEl = getFixture()
})
beforeEach(() => {
written = []
// The real clipboard needs a user gesture and a secure context.
vi.stubGlobal('navigator', {
clipboard: {
writeText: (text: string) => {
written.push(text)
return Promise.resolve()
},
},
})
fixtureEl.innerHTML = MARKUP
})
afterEach(() => {
vi.unstubAllGlobals()
vi.useRealTimers()
clearFixture()
})
const trigger = (): HTMLElement => fixtureEl.querySelector('button')!
const label = (): HTMLElement => fixtureEl.querySelector('.clipboard-label')!
const hidden = (el: Element): boolean => el.hasAttribute('hidden')
const feedback = (): HTMLElement => fixtureEl.querySelector('.clipboard-feedback')!
describe('NAME', () => {
it('should return plugin name', () => {
expect(Clipboard.NAME).toBe('clipboard')
})
})
describe('constructor', () => {
it('should hide the feedback and mark it as a status', () => {
new Clipboard(trigger())
expect(hidden(feedback())).toBe(true)
expect(feedback().getAttribute('role')).toBe('status')
expect(hidden(label())).toBe(false)
})
})
describe('copy', () => {
it('should copy the value of the target field and show the copied state', async () => {
const spy = vi.fn()
trigger().addEventListener('copied.bs.clipboard', spy)
const instance = new Clipboard(trigger(), { delay: 0 })
await instance.copy()
expect(written).toEqual(['sk_live_1234'])
expect(trigger().classList.contains('copied')).toBe(true)
expect(hidden(label())).toBe(true)
expect(hidden(feedback())).toBe(false)
expect(spy).toHaveBeenCalledTimes(1)
})
it('should read the text of a target that is not a field', async () => {
fixtureEl.innerHTML = ' npm install @tabler/core
'
await new Clipboard(trigger()).copy()
expect(written).toEqual(['npm install @tabler/core'])
})
it('should prefer a literal text over the target', async () => {
const instance = new Clipboard(trigger(), { text: 'literal' })
await instance.copy()
expect(written).toEqual(['literal'])
})
it('should copy on click', async () => {
new Clipboard(trigger())
trigger().click()
await vi.waitFor(() => expect(written).toEqual(['sk_live_1234']))
})
it('should go back to the label after the delay', async () => {
const instance = new Clipboard(trigger(), { delay: 50 })
await instance.copy()
expect(hidden(feedback())).toBe(false)
await vi.waitFor(() => expect(hidden(feedback())).toBe(true))
expect(trigger().classList.contains('copied')).toBe(false)
expect(hidden(label())).toBe(false)
})
})
describe('failure', () => {
it('should fire error.bs.clipboard when there is nothing to copy', async () => {
fixtureEl.innerHTML = ''
const spy = vi.fn()
trigger().addEventListener('error.bs.clipboard', spy)
await new Clipboard(trigger()).copy()
expect(written).toEqual([])
expect(spy).toHaveBeenCalledTimes(1)
})
it('should fire error.bs.clipboard when the browser refuses', async () => {
vi.stubGlobal('navigator', { clipboard: { writeText: () => Promise.reject(new Error('denied')) } })
const spy = vi.fn()
trigger().addEventListener('error.bs.clipboard', spy)
await new Clipboard(trigger()).copy()
expect(spy).toHaveBeenCalledTimes(1)
expect(trigger().classList.contains('copied')).toBe(false)
})
})
describe('svg children', () => {
it('should hide an icon with the attribute, not the DOM property', () => {
fixtureEl.innerHTML = ''
new Clipboard(trigger())
const icons = fixtureEl.querySelectorAll('.clipboard-feedback')
expect(icons.length).toBe(2)
for (const icon of icons) {
expect(icon.hasAttribute('hidden')).toBe(true)
}
})
})
describe('dispose', () => {
it('should stop the timer and remove the instance', async () => {
const instance = new Clipboard(trigger(), { delay: 50 })
await instance.copy()
instance.dispose()
expect(Clipboard.getInstance(trigger())).toBeNull()
expect(hidden(feedback())).toBe(false)
})
})
describe('text sources', () => {
it('copies data-bs-text as written', async () => {
fixtureEl.innerHTML = ''
const instance = new Clipboard(trigger())
await instance.copy()
expect(written).toEqual(['007'])
})
it('copies data-bs-text that looks like JSON as written', async () => {
fixtureEl.innerHTML = ''
const instance = new Clipboard(trigger())
await instance.copy()
expect(written).toEqual(['{"a":1}'])
})
it('skips hidden text and keeps line breaks from an element target', async () => {
fixtureEl.innerHTML = 'Line 1
Line 2 (copy)
'
const instance = new Clipboard(trigger())
await instance.copy()
expect(written).toEqual(['Line 1\nLine 2'])
})
it('finds a target whose id needs escaping', async () => {
fixtureEl.innerHTML = ''
const instance = new Clipboard(trigger())
await instance.copy()
expect(written).toEqual(['x'])
})
})
describe('reset', () => {
it('ends the copied state kept by delay 0', async () => {
const instance = new Clipboard(trigger(), { delay: 0 })
await instance.copy()
expect(hidden(feedback())).toBe(false)
instance.reset()
expect(hidden(feedback())).toBe(true)
expect(hidden(label())).toBe(false)
})
})
})