mirror of
https://github.com/tabler/tabler.git
synced 2026-10-12 05:06:55 +04:00
212 lines
6.8 KiB
TypeScript
212 lines
6.8 KiB
TypeScript
import { describe, it, expect, beforeAll, beforeEach, afterEach, vi } from 'vitest'
|
|
import { clearFixture, getFixture } from '../helpers/fixture'
|
|
import Clipboard from '../../src/clipboard'
|
|
|
|
const MARKUP = `
|
|
<div>
|
|
<input id="api-key" value="sk_live_1234">
|
|
<button type="button" data-bs-toggle="clipboard" data-bs-target="#api-key">
|
|
<span class="clipboard-label">Copy</span>
|
|
<span class="clipboard-feedback">Copied</span>
|
|
</button>
|
|
</div>`
|
|
|
|
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 = '<div><code id="snippet"> npm install @tabler/core </code><button data-bs-toggle="clipboard" data-bs-target="#snippet"></button></div>'
|
|
|
|
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 = '<div><button data-bs-toggle="clipboard" data-bs-target="#missing"></button></div>'
|
|
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 = '<div><button data-bs-toggle="clipboard" data-bs-text="x"><svg class="clipboard-label"></svg><svg class="clipboard-feedback"></svg><span class="clipboard-feedback">Copied</span></button></div>'
|
|
|
|
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 = '<button type="button" data-bs-toggle="clipboard" data-bs-text="007"></button>'
|
|
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 = '<button type="button" data-bs-toggle="clipboard" data-bs-text=\'{"a":1}\'></button>'
|
|
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 = '<div id="addr">Line 1<br>Line 2 <span style="display:none">(copy)</span></div><button type="button" data-bs-toggle="clipboard" data-bs-target="#addr"></button>'
|
|
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 = '<input id="key:1" value="x"><button type="button" data-bs-toggle="clipboard" data-bs-target="#key:1"></button>'
|
|
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)
|
|
})
|
|
})
|
|
})
|