refactor(player): E.12 — extract skip-to-1★ helper (#575)

`applySkipStarOnManualNext` — the helper that records a manual `next()`
into the per-track skip counter and auto-rates the track 1★ once the
configured threshold crosses — moves into `src/store/skipStarRating.ts`.
File-private with one call site; no caller-side changes outside
playerStore's own import.

10 focused tests pin each early-return branch (manual=false, null
track, threshold not crossed, recordSkipStarManualAdvance returning
null, already-rated via override / queue / passed-track) plus the
happy path that calls setRating(1) + the state update for the queue,
currentTrack, and override map. Promise rejections are verified to
be swallowed.

playerStore 3291 → 3263 LOC.
This commit is contained in:
Frank Stellmacher
2026-05-12 14:22:38 +02:00
committed by GitHub
parent ddead24678
commit 85df3e42c1
3 changed files with 170 additions and 29 deletions
+37
View File
@@ -0,0 +1,37 @@
import { setRating } from '../api/subsonic';
import { useAuthStore } from './authStore';
import { usePlayerStore, type Track } from './playerStore';
/**
* Skip → 1★ behaviour: every user-initiated `next()` on an unrated track
* counts in `authStore.skipStarManualSkipCountsByKey` (persisted). Once the
* configured threshold is crossed, the track is auto-rated 1★ — both on the
* Subsonic server and in local Zustand state (queue + currentTrack + the
* override map that QueuePanel reads).
*
* Natural track end (incl. gapless advance) does NOT count; it clears the
* threshold counter elsewhere. Already-rated tracks are skipped silently.
*/
export function applySkipStarOnManualNext(skippedTrack: Track | null, manual: boolean): void {
if (!manual || !skippedTrack) return;
const id = skippedTrack.id;
const adv = useAuthStore.getState().recordSkipStarManualAdvance(id);
if (!adv?.crossedThreshold) return;
const live = usePlayerStore.getState();
const fromQueue = live.queue.find(t => t.id === id);
const cur =
live.userRatingOverrides[id] ??
fromQueue?.userRating ??
skippedTrack.userRating ??
0;
if (cur >= 1) return;
setRating(id, 1)
.then(() => {
usePlayerStore.setState(s => ({
queue: s.queue.map(t => (t.id === id ? { ...t, userRating: 1 } : t)),
currentTrack: s.currentTrack?.id === id ? { ...s.currentTrack, userRating: 1 } : s.currentTrack,
userRatingOverrides: { ...s.userRatingOverrides, [id]: 1 },
}));
})
.catch(() => {});
}