File size: 4,878 Bytes
8a428e5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { defineComponent, h } from 'vue'
import { mount } from '@vue/test-utils'
import { useWakeLock } from './useWakeLock'
// useWakeLock() calls onUnmounted() internally, which requires a genuine
// component instance (an effectScope alone is not enough). Mounting a tiny
// host component — and unmounting it after each test — is what actually
// exercises (and cleans up after) the composable's lifecycle hook, and
// avoids leaking `document` listeners between tests.
function mountWakeLock() {
let exposed
const Host = defineComponent({
setup() {
exposed = useWakeLock()
return () => h('div')
},
})
const wrapper = mount(Host)
return { wrapper, ...exposed }
}
describe('useWakeLock', () => {
let originalWakeLock
let wrapper
beforeEach(() => {
originalWakeLock = navigator.wakeLock
})
afterEach(() => {
wrapper?.unmount()
wrapper = undefined
if (originalWakeLock === undefined) delete navigator.wakeLock
else Object.defineProperty(navigator, 'wakeLock', { value: originalWakeLock, configurable: true })
vi.restoreAllMocks()
})
it('does nothing when the Wake Lock API is not supported', async () => {
delete navigator.wakeLock
const result = mountWakeLock()
wrapper = result.wrapper
await expect(result.acquireWakeLock()).resolves.toBeUndefined()
await expect(result.releaseWakeLock()).resolves.toBeUndefined()
})
it('acquires and releases a wake lock when supported', async () => {
const release = vi.fn().mockResolvedValue(undefined)
const mockLock = { addEventListener: vi.fn(), release }
Object.defineProperty(navigator, 'wakeLock', {
value: { request: vi.fn().mockResolvedValue(mockLock) },
configurable: true,
})
const result = mountWakeLock()
wrapper = result.wrapper
await result.acquireWakeLock()
expect(navigator.wakeLock.request).toHaveBeenCalledWith('screen')
await result.releaseWakeLock()
expect(release).toHaveBeenCalled()
})
it('does not request a second lock while one is already held', async () => {
const mockLock = { addEventListener: vi.fn(), release: vi.fn().mockResolvedValue(undefined) }
const request = vi.fn().mockResolvedValue(mockLock)
Object.defineProperty(navigator, 'wakeLock', { value: { request }, configurable: true })
const result = mountWakeLock()
wrapper = result.wrapper
await result.acquireWakeLock()
await result.acquireWakeLock()
expect(request).toHaveBeenCalledTimes(1)
await result.releaseWakeLock()
})
it('silently ignores a rejected wake lock request', async () => {
Object.defineProperty(navigator, 'wakeLock', {
value: { request: vi.fn().mockRejectedValue(new Error('denied')) },
configurable: true,
})
const result = mountWakeLock()
wrapper = result.wrapper
await expect(result.acquireWakeLock()).resolves.toBeUndefined()
await result.releaseWakeLock()
})
it('releaseWakeLock is a no-op when no lock is held', async () => {
delete navigator.wakeLock
const result = mountWakeLock()
wrapper = result.wrapper
await expect(result.releaseWakeLock()).resolves.toBeUndefined()
})
it('re-acquires the lock when the tab becomes visible again after wanting one', async () => {
const mockLock = { addEventListener: vi.fn(), release: vi.fn().mockResolvedValue(undefined) }
const request = vi.fn().mockResolvedValue(mockLock)
Object.defineProperty(navigator, 'wakeLock', { value: { request }, configurable: true })
const result = mountWakeLock()
wrapper = result.wrapper
await result.acquireWakeLock()
// Simulate the browser auto-releasing the lock on tab-hide.
const releaseCallback = mockLock.addEventListener.mock.calls[0][1]
releaseCallback()
Object.defineProperty(document, 'visibilityState', { value: 'visible', configurable: true })
document.dispatchEvent(new Event('visibilitychange'))
// Allow the async re-acquire to run.
await Promise.resolve()
await Promise.resolve()
expect(request).toHaveBeenCalledTimes(2)
await result.releaseWakeLock()
})
it('cleans up the visibility listener and releases the lock on unmount', async () => {
const release = vi.fn().mockResolvedValue(undefined)
const mockLock = { addEventListener: vi.fn(), release }
Object.defineProperty(navigator, 'wakeLock', {
value: { request: vi.fn().mockResolvedValue(mockLock) },
configurable: true,
})
const removeSpy = vi.spyOn(document, 'removeEventListener')
const result = mountWakeLock()
await result.acquireWakeLock()
result.wrapper.unmount()
wrapper = undefined
expect(removeSpy).toHaveBeenCalledWith('visibilitychange', expect.any(Function))
expect(release).toHaveBeenCalled()
})
})
|