package queue import ( "log/slog" "testing" ) // newTestQueueDirect creates a Queue with direct field manipulation // (no DB needed) for pure navigation logic tests. func newTestQueueDirect(tracks int, currentIndex int) *Queue { q := &Queue{ logger: slog.Default(), repeatMode: RepeatOff, } q.tracks = make([]Track, tracks) for i := range tracks { q.tracks[i] = Track{FilePath: "/test/track.mp3", Position: int64(i)} } q.currentIndex = currentIndex return q } func TestNextIndex_NormalMode_AdvancesToNextTrack(t *testing.T) { t.Parallel() q := newTestQueueDirect(5, 2) got := q.nextIndex() if got != 3 { t.Errorf("nextIndex: got %d, want 3", got) } } func TestNextIndex_NormalMode_EndOfQueue_RepeatOff(t *testing.T) { t.Parallel() q := newTestQueueDirect(5, 4) got := q.nextIndex() if got != -1 { t.Errorf("nextIndex at end (repeatOff): got %d, want -1", got) } } func TestNextIndex_NormalMode_EndOfQueue_RepeatAll(t *testing.T) { t.Parallel() q := newTestQueueDirect(5, 4) q.repeatMode = RepeatAll got := q.nextIndex() if got != 0 { t.Errorf("nextIndex at end (repeatAll): got %d, want 0", got) } } func TestNextIndex_RepeatOne(t *testing.T) { t.Parallel() // Note: RepeatOne is handled in the Next() method, not nextIndex(). // nextIndex() with RepeatOne still advances normally — the repeat-one // logic replays the current track before calling nextIndex(). // This test verifies nextIndex advances in the RepeatOne case. q := newTestQueueDirect(5, 2) q.repeatMode = RepeatOne got := q.nextIndex() // nextIndex itself doesn't handle RepeatOne — it just advances. if got != 3 { t.Errorf("nextIndex (repeatOne): got %d, want 3", got) } } func TestPreviousIndex_NormalMode_GoesBack(t *testing.T) { t.Parallel() q := newTestQueueDirect(5, 3) got := q.previousIndex() if got != 2 { t.Errorf("previousIndex: got %d, want 2", got) } } func TestPreviousIndex_AtStart_RepeatOff(t *testing.T) { t.Parallel() q := newTestQueueDirect(5, 0) got := q.previousIndex() if got != -1 { t.Errorf("previousIndex at start (repeatOff): got %d, want -1", got) } } func TestPreviousIndex_AtStart_RepeatAll(t *testing.T) { t.Parallel() q := newTestQueueDirect(5, 0) q.repeatMode = RepeatAll got := q.previousIndex() if got != 4 { t.Errorf("previousIndex at start (repeatAll): got %d, want 4", got) } } func TestGenerateShuffleOrder_Properties(t *testing.T) { t.Parallel() testCases := []struct { name string trackCount int currentIdx int }{ {"single track", 1, 0}, {"five tracks", 5, 2}, {"twenty tracks", 20, 10}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Parallel() q := newTestQueueDirect(tc.trackCount, tc.currentIdx) q.generateShuffleOrder() // Property 1: length matches track count. if got := len(q.shuffleOrder); got != tc.trackCount { t.Errorf("shuffleOrder length: got %d, want %d", got, tc.trackCount) } // Property 2: current track is at shuffleOrder[0]. if q.shuffleOrder[0] != tc.currentIdx { t.Errorf( "shuffleOrder[0]: got %d, want %d (currentIndex)", q.shuffleOrder[0], tc.currentIdx, ) } // Property 3: all indices present (no duplicates, no missing). seen := make(map[int]bool, tc.trackCount) for _, idx := range q.shuffleOrder { if idx < 0 || idx >= tc.trackCount { t.Errorf("shuffleOrder contains out-of-range index: %d", idx) } if seen[idx] { t.Errorf("shuffleOrder contains duplicate index: %d", idx) } seen[idx] = true } if len(seen) != tc.trackCount { t.Errorf( "unique indices in shuffleOrder: got %d, want %d", len(seen), tc.trackCount, ) } }) } } func TestNextIndex_ShuffleMode(t *testing.T) { t.Parallel() q := newTestQueueDirect(5, 2) q.shuffleMode = true // Set a known shuffle order: [2, 4, 0, 3, 1] // Current index is 2, which is at shuffleOrder[0]. q.shuffleOrder = []int{2, 4, 0, 3, 1} // Next in shuffle order should be shuffleOrder[1] = 4. got := q.nextIndex() if got != 4 { t.Errorf("nextIndex (shuffle): got %d, want 4", got) } // Advance to index 4 and get next. q.currentIndex = 4 got = q.nextIndex() if got != 0 { t.Errorf("nextIndex (shuffle, pos 2): got %d, want 0", got) } // At the end of shuffle order with RepeatOff. q.currentIndex = 1 // last in shuffleOrder got = q.nextIndex() if got != -1 { t.Errorf("nextIndex (shuffle, end, repeatOff): got %d, want -1", got) } // At the end of shuffle order with RepeatAll. q.repeatMode = RepeatAll got = q.nextIndex() if got != 2 { t.Errorf( "nextIndex (shuffle, end, repeatAll): got %d, want 2 "+ "(wraps to shuffleOrder[0])", got, ) } }