mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
131 lines
4.8 KiB
TypeScript
131 lines
4.8 KiB
TypeScript
/**
|
|
* IPC dedupers — each helper collapses repeat calls within a time-bounded
|
|
* window. The interesting behaviour is the engine-mode contribution to the
|
|
* replay-gain dedupe key (so changing the LUFS target re-fires even when
|
|
* the cached dB stays the same) and the null-aware number formatter.
|
|
*/
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const { authState, invokeMock } = vi.hoisted(() => ({
|
|
authState: {
|
|
normalizationEngine: 'off' as 'off' | 'replaygain' | 'loudness',
|
|
loudnessTargetLufs: -14,
|
|
loudnessPreAnalysisAttenuationDb: 0,
|
|
},
|
|
invokeMock: vi.fn(async (_cmd: string, _args?: Record<string, unknown>) => undefined),
|
|
}));
|
|
|
|
vi.mock('@tauri-apps/api/core', () => ({ invoke: invokeMock }));
|
|
vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => authState } }));
|
|
vi.mock('@/lib/audio/loudnessPreAnalysisSlider', () => ({
|
|
effectiveLoudnessPreAnalysisAttenuationDb: (attenuation: number) => attenuation,
|
|
}));
|
|
|
|
import {
|
|
_resetNormalizationIpcDedupeForTest,
|
|
invokeAudioSetNormalizationDeduped,
|
|
invokeAudioUpdateReplayGainDeduped,
|
|
} from '@/features/playback/store/normalizationIpcDedupe';
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(new Date('2026-05-12T12:00:00Z'));
|
|
invokeMock.mockClear();
|
|
authState.normalizationEngine = 'off';
|
|
authState.loudnessTargetLufs = -14;
|
|
authState.loudnessPreAnalysisAttenuationDb = 0;
|
|
});
|
|
|
|
afterEach(() => {
|
|
_resetNormalizationIpcDedupeForTest();
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
describe('invokeAudioSetNormalizationDeduped', () => {
|
|
const payload = { engine: 'loudness', targetLufs: -14, preAnalysisAttenuationDb: 0 };
|
|
|
|
it('passes the first call through', () => {
|
|
invokeAudioSetNormalizationDeduped(payload);
|
|
expect(invokeMock).toHaveBeenCalledTimes(1);
|
|
expect(invokeMock).toHaveBeenCalledWith('audio_set_normalization', payload);
|
|
});
|
|
|
|
it('skips a repeat with the same payload inside the 450 ms window', () => {
|
|
invokeAudioSetNormalizationDeduped(payload);
|
|
vi.advanceTimersByTime(449);
|
|
invokeAudioSetNormalizationDeduped(payload);
|
|
expect(invokeMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('fires again once the 450 ms window elapses', () => {
|
|
invokeAudioSetNormalizationDeduped(payload);
|
|
vi.advanceTimersByTime(450);
|
|
invokeAudioSetNormalizationDeduped(payload);
|
|
expect(invokeMock).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('fires when any payload field changes within the window', () => {
|
|
invokeAudioSetNormalizationDeduped(payload);
|
|
invokeAudioSetNormalizationDeduped({ ...payload, targetLufs: -10 });
|
|
invokeAudioSetNormalizationDeduped({ ...payload, engine: 'replaygain' });
|
|
invokeAudioSetNormalizationDeduped({ ...payload, preAnalysisAttenuationDb: -2 });
|
|
expect(invokeMock).toHaveBeenCalledTimes(4);
|
|
});
|
|
});
|
|
|
|
describe('invokeAudioUpdateReplayGainDeduped', () => {
|
|
const payload = {
|
|
volume: 0.8,
|
|
replayGainDb: -6,
|
|
replayGainPeak: 0.9,
|
|
loudnessGainDb: -3,
|
|
preGainDb: 0,
|
|
fallbackDb: -6,
|
|
};
|
|
|
|
it('passes the first call through', () => {
|
|
invokeAudioUpdateReplayGainDeduped(payload);
|
|
expect(invokeMock).toHaveBeenCalledWith('audio_update_replay_gain', payload);
|
|
});
|
|
|
|
it('skips a repeat with the same payload inside the 250 ms window', () => {
|
|
invokeAudioUpdateReplayGainDeduped(payload);
|
|
vi.advanceTimersByTime(249);
|
|
invokeAudioUpdateReplayGainDeduped(payload);
|
|
expect(invokeMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('fires again once the 250 ms window elapses', () => {
|
|
invokeAudioUpdateReplayGainDeduped(payload);
|
|
vi.advanceTimersByTime(250);
|
|
invokeAudioUpdateReplayGainDeduped(payload);
|
|
expect(invokeMock).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('re-fires when the LUFS target changes even if the dB payload stays the same', () => {
|
|
authState.normalizationEngine = 'loudness';
|
|
authState.loudnessTargetLufs = -14;
|
|
invokeAudioUpdateReplayGainDeduped(payload);
|
|
authState.loudnessTargetLufs = -10;
|
|
invokeAudioUpdateReplayGainDeduped(payload);
|
|
expect(invokeMock).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('treats null and non-finite gain values as the dedupe-string "null"', () => {
|
|
invokeAudioUpdateReplayGainDeduped({ ...payload, replayGainDb: null });
|
|
invokeAudioUpdateReplayGainDeduped({ ...payload, replayGainDb: Number.NaN });
|
|
// Same dedupe-key (both serialize to "null") + same window → second call dropped.
|
|
expect(invokeMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('_resetNormalizationIpcDedupeForTest', () => {
|
|
it('lets the next call through again after a reset', () => {
|
|
const payload = { engine: 'off', targetLufs: -14, preAnalysisAttenuationDb: 0 };
|
|
invokeAudioSetNormalizationDeduped(payload);
|
|
_resetNormalizationIpcDedupeForTest();
|
|
invokeAudioSetNormalizationDeduped(payload);
|
|
expect(invokeMock).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|