Files
Psychotoxical-psysonic/src/store/seekDebounce.test.ts
T
Frank Stellmacher 6355946610 refactor(player): E.23 — extract engine state + seek debounce cluster (#587)
Two thematic cuts in one PR:

  - `src/store/engineState.ts` — `isAudioPaused` (warm-vs-cold-resume
    decision flag) + `playGeneration` (monotonically bumped guard
    counter used by long-running async callbacks to detect concurrent
    playTrack and bail). Get/set accessors + `bumpPlayGeneration()`
    that returns the new value (mirrors the original `++playGeneration`
    pattern).
  - `src/store/seekDebounce.ts` — seek-slider debounce timer behind
    `armSeekDebounce(delayMs, onFire)` / `clearSeekDebounce()` /
    `isSeekDebouncePending()`. Collapses the recurring inline
    `if (seekDebounce) { clearTimeout(...); seekDebounce = null; }`
    triple into single calls.

Sed-driven bulk rewrite for the >60 direct-access sites preserved
semantics verbatim (one JSDoc reference to `isAudioPaused` kept as
comment text). 14 tests across the two modules.

playerStore 2869 → 2867 LOC net (small delta — the imports are bigger
than the savings, but the mutables are no longer reachable from
outside their owning modules).
2026-05-12 17:14:37 +02:00

57 lines
1.5 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
_resetSeekDebounceForTest,
armSeekDebounce,
clearSeekDebounce,
isSeekDebouncePending,
} from './seekDebounce';
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
_resetSeekDebounceForTest();
vi.useRealTimers();
});
describe('seekDebounce', () => {
it('starts not pending', () => {
expect(isSeekDebouncePending()).toBe(false);
});
it('armSeekDebounce flips pending true and fires the callback after the delay', () => {
const cb = vi.fn();
armSeekDebounce(100, cb);
expect(isSeekDebouncePending()).toBe(true);
vi.advanceTimersByTime(99);
expect(cb).not.toHaveBeenCalled();
vi.advanceTimersByTime(1);
expect(cb).toHaveBeenCalledTimes(1);
expect(isSeekDebouncePending()).toBe(false);
});
it('arming again before fire replaces the callback', () => {
const first = vi.fn();
const second = vi.fn();
armSeekDebounce(100, first);
armSeekDebounce(100, second);
vi.advanceTimersByTime(100);
expect(first).not.toHaveBeenCalled();
expect(second).toHaveBeenCalledTimes(1);
});
it('clearSeekDebounce cancels a pending fire', () => {
const cb = vi.fn();
armSeekDebounce(100, cb);
clearSeekDebounce();
expect(isSeekDebouncePending()).toBe(false);
vi.advanceTimersByTime(1000);
expect(cb).not.toHaveBeenCalled();
});
it('clearSeekDebounce is a no-op when nothing is pending', () => {
expect(() => clearSeekDebounce()).not.toThrow();
});
});