156 lines
5.6 KiB
TypeScript
156 lines
5.6 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import {
|
|
CLIPBOARD_TEXT_MEASURE_YIELD_CODE_UNITS,
|
|
CLIPBOARD_TEXT_TOO_LARGE_ERROR,
|
|
CLIPBOARD_TEXT_WRITE_TOO_LARGE_ERROR,
|
|
assertClipboardTextWriteWithinLimit,
|
|
assertClipboardTextWriteWithinLimitWithYield,
|
|
assertClipboardTextWithinLimit,
|
|
assertClipboardTextWithinLimitWithYield,
|
|
getClipboardTextByteLength,
|
|
isClipboardTextByteLengthOverLimit,
|
|
isClipboardTextByteLengthOverLimitWithYield,
|
|
isClipboardTextTooLargeError,
|
|
isClipboardTextWriteTooLargeError,
|
|
measureClipboardTextByteLength,
|
|
measureClipboardTextByteLengthWithYield
|
|
} from './clipboard-text'
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
describe('clipboard text limits', () => {
|
|
it('measures UTF-8 bytes instead of UTF-16 code units', () => {
|
|
expect(getClipboardTextByteLength('a😀')).toBe(5)
|
|
})
|
|
|
|
it('can stop measuring once a byte limit is exceeded', () => {
|
|
const measurement = measureClipboardTextByteLength('😀'.repeat(100), {
|
|
stopAfterBytes: 5
|
|
})
|
|
|
|
expect(measurement).toEqual({ byteLength: 8, exceededLimit: true })
|
|
expect(measurement.byteLength).toBeLessThan(getClipboardTextByteLength('😀'.repeat(100)))
|
|
})
|
|
|
|
it('detects text over a byte limit without requiring full measurement', () => {
|
|
expect(isClipboardTextByteLengthOverLimit('😀'.repeat(100), 5)).toBe(true)
|
|
expect(isClipboardTextByteLengthOverLimit('éé', 4)).toBe(false)
|
|
})
|
|
|
|
it('yields while measuring large accepted clipboard text', async () => {
|
|
const yieldToEventLoop = vi.fn(async () => {})
|
|
|
|
const measurement = await measureClipboardTextByteLengthWithYield('x'.repeat(32), {
|
|
stopAfterBytes: 64,
|
|
yieldAfterCodeUnits: 8,
|
|
yieldToEventLoop
|
|
})
|
|
|
|
expect(measurement).toEqual({ byteLength: 32, exceededLimit: false })
|
|
expect(yieldToEventLoop).toHaveBeenCalled()
|
|
})
|
|
|
|
it('uses the default 256k code-unit yield cadence for accepted large clipboard text', async () => {
|
|
const yieldToEventLoop = vi.fn(async () => {})
|
|
|
|
const measurement = await measureClipboardTextByteLengthWithYield(
|
|
'x'.repeat(CLIPBOARD_TEXT_MEASURE_YIELD_CODE_UNITS * 2 + 1),
|
|
{
|
|
yieldToEventLoop
|
|
}
|
|
)
|
|
|
|
expect(measurement.exceededLimit).toBe(false)
|
|
expect(yieldToEventLoop).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('yields while checking multibyte clipboard limits before rejecting', async () => {
|
|
const yieldToEventLoop = vi.fn(async () => {})
|
|
|
|
await expect(
|
|
isClipboardTextByteLengthOverLimitWithYield('é'.repeat(16), 31, {
|
|
yieldAfterCodeUnits: 4,
|
|
yieldToEventLoop
|
|
})
|
|
).resolves.toBe(true)
|
|
|
|
expect(yieldToEventLoop).toHaveBeenCalled()
|
|
})
|
|
|
|
it('rejects text whose string length already exceeds the byte limit without scanning', () => {
|
|
const codePointAt = vi.spyOn(String.prototype, 'codePointAt')
|
|
|
|
expect(isClipboardTextByteLengthOverLimit('x'.repeat(6), 5)).toBe(true)
|
|
expect(() => assertClipboardTextWithinLimit('x'.repeat(6), { maxBytes: 5 })).toThrow(
|
|
CLIPBOARD_TEXT_TOO_LARGE_ERROR
|
|
)
|
|
expect(() => assertClipboardTextWriteWithinLimit('x'.repeat(6), { maxBytes: 5 })).toThrow(
|
|
CLIPBOARD_TEXT_WRITE_TOO_LARGE_ERROR
|
|
)
|
|
expect(codePointAt).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('lets each clipboard consumer override the shared default byte limits', async () => {
|
|
expect(assertClipboardTextWithinLimit('abc', { maxBytes: 3 })).toBe('abc')
|
|
expect(() => assertClipboardTextWithinLimit('abc', { maxBytes: 2 })).toThrow(
|
|
CLIPBOARD_TEXT_TOO_LARGE_ERROR
|
|
)
|
|
expect(assertClipboardTextWriteWithinLimit('copy', { maxBytes: 4 })).toBe('copy')
|
|
expect(() => assertClipboardTextWriteWithinLimit('copy', { maxBytes: 3 })).toThrow(
|
|
CLIPBOARD_TEXT_WRITE_TOO_LARGE_ERROR
|
|
)
|
|
|
|
await expect(assertClipboardTextWithinLimitWithYield('async', { maxBytes: 5 })).resolves.toBe(
|
|
'async'
|
|
)
|
|
await expect(
|
|
assertClipboardTextWriteWithinLimitWithYield('async', { maxBytes: 4 })
|
|
).rejects.toThrow(CLIPBOARD_TEXT_WRITE_TOO_LARGE_ERROR)
|
|
})
|
|
|
|
it('rejects oversized text with a metadata-only error', () => {
|
|
expect(() => assertClipboardTextWithinLimit('secret-token-value', { maxBytes: 4 })).toThrow(
|
|
CLIPBOARD_TEXT_TOO_LARGE_ERROR
|
|
)
|
|
|
|
try {
|
|
assertClipboardTextWithinLimit('secret-token-value', { maxBytes: 4 })
|
|
} catch (error) {
|
|
expect(isClipboardTextTooLargeError(error)).toBe(true)
|
|
expect(String(error)).not.toContain('secret-token-value')
|
|
}
|
|
})
|
|
|
|
it('rejects oversized async clipboard reads and writes with metadata-only errors', async () => {
|
|
await expect(
|
|
assertClipboardTextWithinLimitWithYield('secret-token-value', { maxBytes: 4 })
|
|
).rejects.toThrow(CLIPBOARD_TEXT_TOO_LARGE_ERROR)
|
|
await expect(
|
|
assertClipboardTextWriteWithinLimitWithYield('copied-secret-token-value', { maxBytes: 4 })
|
|
).rejects.toThrow(CLIPBOARD_TEXT_WRITE_TOO_LARGE_ERROR)
|
|
|
|
try {
|
|
await assertClipboardTextWithinLimitWithYield('secret-token-value', { maxBytes: 4 })
|
|
} catch (error) {
|
|
expect(isClipboardTextTooLargeError(error)).toBe(true)
|
|
expect(String(error)).not.toContain('secret-token-value')
|
|
}
|
|
})
|
|
|
|
it('rejects oversized clipboard writes with a metadata-only error', () => {
|
|
expect(() =>
|
|
assertClipboardTextWriteWithinLimit('copied-secret-token-value', { maxBytes: 4 })
|
|
).toThrow(CLIPBOARD_TEXT_WRITE_TOO_LARGE_ERROR)
|
|
|
|
try {
|
|
assertClipboardTextWriteWithinLimit('copied-secret-token-value', { maxBytes: 4 })
|
|
} catch (error) {
|
|
expect(isClipboardTextWriteTooLargeError(error)).toBe(true)
|
|
expect(isClipboardTextTooLargeError(error)).toBe(false)
|
|
expect(String(error)).not.toContain('copied-secret-token-value')
|
|
}
|
|
})
|
|
})
|