@rosskevin/ifvisible
Version:
Cross-browser, lightweight way to check if user is looking at the page or interacting with it. (wrapper around HTML5 visibility api)
341 lines (288 loc) • 9.03 kB
text/typescript
import { vi } from 'vitest'
import { FireableEvent, Status } from '../EventBus.js'
import { IfVisible } from '../IfVisible.js'
function expectIt(ifv: IfVisible, status: Status) {
expect(ifv.getStatus()).toEqual(status)
}
function expectActive(ifv: IfVisible) {
expectIt(ifv, 'active')
}
function expectIdle(ifv: IfVisible) {
expectIt(ifv, 'idle')
}
function expectHidden(ifv: IfVisible) {
expectIt(ifv, 'hidden')
}
describe('IfVisible', () => {
let ifv: IfVisible
beforeEach(() => {
vi.useFakeTimers()
vi.setSystemTime(new Date('2022-06-16'))
ifv = new IfVisible(window, document)
})
afterEach(() => {
vi.useRealTimers()
})
describe('when instantiating', () => {
it('is `active`', () => {
expectActive(ifv)
})
it('is `idle` after initial idleTime (30)', () => {
expectActive(ifv)
// 10 total (of 30)
vi.advanceTimersByTime(10000)
expectActive(ifv)
// 30 total (of 30)
vi.advanceTimersByTime(20000)
expectIdle(ifv)
})
})
describe('methods', () => {
describe('idle', () => {
it('setup is initally `active`', () => {
expectActive(ifv)
})
it('is `idle`', () => {
ifv.idle()
expectIdle(ifv)
})
it(`fires status change 'idle'`, () => {
let newStatus: Status | undefined
ifv.on('statusChanged', (data) => (newStatus = data?.status))
ifv.idle()
expect(newStatus).toEqual('idle')
})
it(`fires 'idle'`, () => {
const spy = vi.fn()
ifv.on('idle', spy)
ifv.idle()
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(1)
})
})
describe('blur', () => {
it('setup is initally `active`', () => {
expectActive(ifv)
})
it('is `hidden`', () => {
ifv.blur()
expectHidden(ifv)
})
it(`fires status change 'hidden'`, () => {
let newStatus: Status | undefined
ifv.on('statusChanged', (data) => (newStatus = data?.status))
ifv.blur()
expect(newStatus).toEqual('hidden')
})
it(`fires 'blur'`, () => {
const spy = vi.fn()
ifv.on('blur', spy)
ifv.blur()
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(1)
})
})
describe('focus', () => {
beforeEach(() => {
expectActive(ifv)
vi.advanceTimersByTime(30000)
expectIdle(ifv)
})
it('setup is initally `idle`', () => {
expectIdle(ifv)
})
it('is `active`', () => {
ifv.focus()
expectActive(ifv)
})
it(`fires status change 'active'`, () => {
let newStatus: Status | undefined
ifv.on('statusChanged', (data) => (newStatus = data?.status))
ifv.focus()
expect(newStatus).toEqual('active')
})
;['focus', 'wakeup'].forEach((name) => {
it(`fires '${name}'`, () => {
const spy = vi.fn()
ifv.on(name as FireableEvent, spy)
ifv.focus()
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(1)
})
})
})
describe('wakeup', () => {
beforeEach(() => {
expectActive(ifv)
vi.advanceTimersByTime(30000)
expectIdle(ifv)
})
it('setup is initally `idle`', () => {
expectIdle(ifv)
})
it('is `active`', () => {
ifv.wakeup()
expectActive(ifv)
})
it(`fires status change 'active'`, () => {
let newStatus: Status | undefined
ifv.on('statusChanged', (data) => (newStatus = data?.status))
ifv.wakeup()
expect(newStatus).toEqual('active')
})
it(`fires 'wakeup'`, () => {
const spy = vi.fn()
ifv.on('wakeup', spy)
ifv.wakeup()
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(1)
})
})
describe('now', () => {
it('when active is true', () => {
expectActive(ifv)
expect(ifv.now()).toEqual(true)
expect(ifv.now('active')).toEqual(true)
expect(ifv.now('hidden')).toEqual(false)
expect(ifv.now('idle')).toEqual(false)
})
it('when idle is false', () => {
ifv.idle()
expect(ifv.now()).toEqual(false)
expect(ifv.now('active')).toEqual(false)
expect(ifv.now('hidden')).toEqual(false)
expect(ifv.now('idle')).toEqual(true)
})
it('when hidden is false', () => {
ifv.blur()
expect(ifv.now()).toEqual(false)
expect(ifv.now('active')).toEqual(false)
expect(ifv.now('hidden')).toEqual(true)
expect(ifv.now('idle')).toEqual(false)
})
})
describe('onEvery', () => {
it(`fires callback repeatedly when 'active'`, () => {
const spy = vi.fn()
ifv.onEvery(0.5, spy)
expectActive(ifv)
expect(spy).not.toHaveBeenCalled()
// 1 total (of 3)
vi.advanceTimersByTime(1000)
expectActive(ifv)
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(2)
// 2 total (of 3)
vi.advanceTimersByTime(1000)
expectActive(ifv)
expect(spy).toHaveBeenCalledTimes(4)
// 3 total (of 3)
vi.advanceTimersByTime(1000)
expectActive(ifv)
expect(spy).toHaveBeenCalledTimes(6)
})
it(`does not continue to fire callback when 'hidden' after blur()`, () => {
const spy = vi.fn()
ifv.onEvery(0.5, spy)
expectActive(ifv)
expect(spy).not.toHaveBeenCalled()
// 1 total (of 3)
vi.advanceTimersByTime(1000)
expectActive(ifv)
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(2)
// blur it and check
ifv.blur()
expectHidden(ifv)
expect(spy).toHaveBeenCalledTimes(2)
// advance time and check again to be sure
expectHidden(ifv)
expect(spy).toHaveBeenCalledTimes(2)
})
it(`does not continue to fire callback when 'idle' after idle()`, () => {
const spy = vi.fn()
ifv.onEvery(0.5, spy)
expectActive(ifv)
expect(spy).not.toHaveBeenCalled()
// 1 total (of 3)
vi.advanceTimersByTime(1000)
expectActive(ifv)
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(2)
// idle it and check
ifv.idle()
expectIdle(ifv)
expect(spy).toHaveBeenCalledTimes(2)
// advance time and check again to be sure
expectIdle(ifv)
expect(spy).toHaveBeenCalledTimes(2)
})
it(`stops firing when 'idle' after timeout`, () => {
const spy = vi.fn()
ifv.onEvery(1, spy)
expectActive(ifv)
expect(spy).not.toHaveBeenCalled()
// 1 total (of 60)
vi.advanceTimersByTime(1000)
expectActive(ifv)
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(1)
// 60 total (of 60) and 30s default timeout
vi.advanceTimersByTime(59000)
expectIdle(ifv)
expect(spy).toHaveBeenCalledTimes(30 - 1) // it's always -1, not sure why based on original code, but not a big deal to me at least
})
it(`restarts firing when waking after event`, () => {
const spy = vi.fn()
ifv.onEvery(1, spy)
expectActive(ifv)
expect(spy).not.toHaveBeenCalled()
// 1 total (of 60)
vi.advanceTimersByTime(30000)
expectIdle(ifv)
expect(spy).toHaveBeenCalledTimes(30 - 1) // it's always -1, not sure why based on original code, but not a big deal to me at least
//
// now, let's wake this up and check to see it resumes
//
expectIdle(ifv)
document.dispatchEvent(new window.Event('mousemove'))
expectActive(ifv)
expect(spy).toHaveBeenCalledTimes(30 - 1) // same as above, we haven't moved time.
// see if it reinitiates
vi.advanceTimersByTime(1000)
expectActive(ifv)
expect(spy).toHaveBeenCalledTimes(30)
})
})
})
describe('DOM events', () => {
beforeEach(() => {
expectActive(ifv)
vi.advanceTimersByTime(30000)
expectIdle(ifv)
})
it('setup is initally `idle`', () => {
expectIdle(ifv)
})
describe('document', () => {
// all doc events
;['mousemove', 'mousedown', 'keyup', 'touchstart'].forEach((name) => {
it(`is active after ${name} event`, () => {
expectIdle(ifv)
document.dispatchEvent(new window.Event(name))
expectActive(ifv)
})
})
})
describe('window', () => {
// all doc events
;['scroll'].forEach((name) => {
it(`is active after ${name} event`, () => {
expectIdle(ifv)
window.dispatchEvent(new window.Event(name))
expectActive(ifv)
})
})
})
})
})