import type { Ticker } from 'pixi.js'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { BackgroundBeat, beatDue, STALL_MS } from '../../src/viewport/backgroundBeat'; const page = { hidden: false }; /** A ticker that runs its listeners on `update`, as Pixi's does; `frame` is one the page drew itself. */ function fakeTicker() { const listeners = new Set<() => void>(); const ticker = { updates: 0, add: (fn: () => void) => listeners.add(fn), remove: (fn: () => void) => listeners.delete(fn), update: () => { ticker.updates += 1; for (const fn of listeners) fn(); }, frame: () => { for (const fn of listeners) fn(); }, }; return ticker; } describe('beatDue', () => { it('ticks a hidden page, and a page that has drawn no frame of its own for a while', () => { expect(beatDue(true, 0)).toBe(true); expect(beatDue(false, 16)).toBe(false); expect(beatDue(false, STALL_MS)).toBe(false); expect(beatDue(false, STALL_MS + 1)).toBe(true); }); }); describe('BackgroundBeat', () => { beforeEach(() => { vi.useFakeTimers({ toFake: ['setInterval', 'clearInterval', 'performance'] }); page.hidden = false; vi.stubGlobal('document', { get hidden() { return page.hidden; }, }); }); afterEach(() => { vi.useRealTimers(); vi.unstubAllGlobals(); }); it('stands aside while the page draws, and ticks at the rate asked for once it is hidden', () => { const ticker = fakeTicker(); const beat = new BackgroundBeat(ticker as unknown as Ticker); beat.start(60); for (let i = 0; i < 30; i++) { vi.advanceTimersByTime(16); ticker.frame(); } expect(ticker.updates).toBe(0); page.hidden = true; vi.advanceTimersByTime(1600); expect(ticker.updates).toBe(96); expect(beat.ticks).toBe(96); beat.stop(); vi.advanceTimersByTime(1600); expect(ticker.updates).toBe(96); }); it('takes over a page that stopped drawing without saying it is hidden, until it draws again', () => { const ticker = fakeTicker(); const beat = new BackgroundBeat(ticker as unknown as Ticker); beat.start(30); vi.advanceTimersByTime(STALL_MS - 10); expect(ticker.updates).toBe(0); // Its own ticks do not count as the page's frames: it goes on at every beat. vi.advanceTimersByTime(1000); expect(ticker.updates).toBe(30); const given = ticker.updates; ticker.frame(); vi.advanceTimersByTime(200); expect(ticker.updates).toBe(given); beat.stop(); }); });