Files
Psychotoxical-psysonic/src/store/gaplessPreloadState.test.ts
T
Frank Stellmacher 3a99be4daa refactor(player): E.16 — extract gapless-preload coordination state (#579)
Three coordinating mutables move into `src/store/gaplessPreloadState.ts`
behind a thin API:

  - `gaplessPreloadingId` / `bytePreloadingId` — guards so the runtime
    doesn't fire a chain-/byte-preload twice for the same id
  - `lastGaplessSwitchTime` — timestamp used by the 500–600 ms
    ghost-IPC suppression guards in `handleAudioEnded` and `playTrack`

Public API: `get…` / `set…` accessors plus `clearPreloadingIds`
(atomic clear of both guards — collapses three `= null; = null;`
duplications) and `markGaplessSwitch` (`Date.now()` stamp). The
mutables are no longer reachable from outside the module.

13 focused tests pin the get/set round-trips, independence between
the two guards, the atomic clear, and the timestamp progression.

playerStore 3208 → 3207 LOC (small delta because the `= null; = null;`
inlines compress to single `clearPreloadingIds()` calls but the
module itself adds the accessor functions to the import list).
2026-05-12 14:53:37 +02:00

92 lines
2.5 KiB
TypeScript

/**
* Three mutables that coordinate the gapless preloader. Most of the surface
* is straight get/set — the interesting bit is `clearPreloadingIds` (atomic
* clear of both) and `markGaplessSwitch` (timestamp side effect).
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
_resetGaplessPreloadStateForTest,
clearPreloadingIds,
getBytePreloadingId,
getGaplessPreloadingId,
getLastGaplessSwitchTime,
markGaplessSwitch,
setBytePreloadingId,
setGaplessPreloadingId,
} from './gaplessPreloadState';
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-05-12T12:00:00Z'));
});
afterEach(() => {
_resetGaplessPreloadStateForTest();
vi.useRealTimers();
});
describe('initial state', () => {
it('is null / 0 for unread accessors', () => {
expect(getGaplessPreloadingId()).toBeNull();
expect(getBytePreloadingId()).toBeNull();
expect(getLastGaplessSwitchTime()).toBe(0);
});
});
describe('preloading-id accessors', () => {
it('round-trips through the gapless guard', () => {
setGaplessPreloadingId('t1');
expect(getGaplessPreloadingId()).toBe('t1');
});
it('round-trips through the byte guard', () => {
setBytePreloadingId('t2');
expect(getBytePreloadingId()).toBe('t2');
});
it('keeps the two guards independent', () => {
setGaplessPreloadingId('a');
setBytePreloadingId('b');
expect(getGaplessPreloadingId()).toBe('a');
expect(getBytePreloadingId()).toBe('b');
});
it('accepts null to clear a guard', () => {
setGaplessPreloadingId('a');
setGaplessPreloadingId(null);
expect(getGaplessPreloadingId()).toBeNull();
});
});
describe('clearPreloadingIds', () => {
it('clears both guards atomically', () => {
setGaplessPreloadingId('a');
setBytePreloadingId('b');
clearPreloadingIds();
expect(getGaplessPreloadingId()).toBeNull();
expect(getBytePreloadingId()).toBeNull();
});
it('does not touch the gapless-switch timestamp', () => {
markGaplessSwitch();
const before = getLastGaplessSwitchTime();
clearPreloadingIds();
expect(getLastGaplessSwitchTime()).toBe(before);
});
});
describe('markGaplessSwitch', () => {
it('stamps Date.now()', () => {
markGaplessSwitch();
expect(getLastGaplessSwitchTime()).toBe(Date.now());
});
it('overwrites on a later call', () => {
markGaplessSwitch();
const first = getLastGaplessSwitchTime();
vi.advanceTimersByTime(700);
markGaplessSwitch();
expect(getLastGaplessSwitchTime()).toBeGreaterThan(first);
});
});