fix(quick-13): resolve lint issues in main source files

- Fix errcheck for db.Close() in testhelper.go
- Fix errcheck, nlreturn, wsl, gofumpt issues in genevents/main.go
- Fix gofumpt and wsl issues in library.go
This commit is contained in:
2026-03-05 14:07:50 -05:00
parent 97f256d67f
commit e1a95e65a9
12 changed files with 177 additions and 61 deletions
+16 -7
View File
@@ -72,7 +72,10 @@ func TestConfig_LoadSave_Roundtrip(t *testing.T) {
} }
if loaded.Theme.BackgroundShade != theme.BackgroundLight { if loaded.Theme.BackgroundShade != theme.BackgroundLight {
t.Errorf("Theme.BackgroundShade = %q, want %q", loaded.Theme.BackgroundShade, theme.BackgroundLight) t.Errorf(
"Theme.BackgroundShade = %q, want %q",
loaded.Theme.BackgroundShade, theme.BackgroundLight,
)
} }
// Verify tracklist. // Verify tracklist.
@@ -86,13 +89,19 @@ func TestConfig_LoadSave_Roundtrip(t *testing.T) {
} }
for i, want := range wantColumns { for i, want := range wantColumns {
if loaded.TrackList.Columns[i].ID != want { if loaded.TrackList.Columns[i].ID != want {
t.Errorf("TrackList.Columns[%d].ID = %q, want %q", i, loaded.TrackList.Columns[i].ID, want) t.Errorf(
"TrackList.Columns[%d].ID = %q, want %q",
i, loaded.TrackList.Columns[i].ID, want,
)
} }
} }
// Verify favorites. // Verify favorites.
if loaded.Favorites.IconStyle != favorites.IconStar { if loaded.Favorites.IconStyle != favorites.IconStar {
t.Errorf("Favorites.IconStyle = %q, want %q", loaded.Favorites.IconStyle, favorites.IconStar) t.Errorf(
"Favorites.IconStyle = %q, want %q",
loaded.Favorites.IconStyle, favorites.IconStar,
)
} }
if loaded.Favorites.PinDefault != false { if loaded.Favorites.PinDefault != false {
@@ -105,7 +114,10 @@ func TestConfig_LoadSave_Roundtrip(t *testing.T) {
} }
if loaded.Library.ScanConcurrency != library.ScanConcurrencySSD { if loaded.Library.ScanConcurrency != library.ScanConcurrencySSD {
t.Errorf("Library.ScanConcurrency = %q, want %q", loaded.Library.ScanConcurrency, library.ScanConcurrencySSD) t.Errorf(
"Library.ScanConcurrency = %q, want %q",
loaded.Library.ScanConcurrency, library.ScanConcurrencySSD,
)
} }
// Verify window. // Verify window.
@@ -206,9 +218,6 @@ func TestConfig_ApplyDefaults_NilSubConfigs(t *testing.T) {
if c.Favorites == nil { if c.Favorites == nil {
t.Error("Favorites should not be nil after applyDefaults") t.Error("Favorites should not be nil after applyDefaults")
} }
// Library is intentionally left nil by applyDefaults when it
// starts as nil (no library dir configured yet).
} }
// containsSubstring is a test helper for checking error messages. // containsSubstring is a test helper for checking error messages.
+62 -23
View File
@@ -44,28 +44,50 @@ func seedSearchData(t *testing.T, db *DB) {
intPtr := func(v int64) *int64 { return &v } intPtr := func(v int64) *int64 { return &v }
tracks := []track{ tracks := []track{
{1, "/music/queen/bohemian_rhapsody.mp3", "Bohemian Rhapsody", "Queen", "A Night at the Opera", intPtr(11), intPtr(1), 1975, "Rock", "Freddie Mercury", 354000, 0, 44100, 16, 2, 320000, 8500000}, {
{2, "/music/beyonce/halo.flac", "Halo", "Beyoncé", "Lemonade", intPtr(1), intPtr(1), 2008, "Pop", "Ryan Tedder", 261000, 1, 96000, 24, 2, 1411000, 42000000}, 1, "/music/queen/bohemian_rhapsody.mp3", "Bohemian Rhapsody", "Queen",
{3, "/music/acdc/back_in_black.mp3", "Back in Black", "AC/DC", "Back in Black", intPtr(1), intPtr(1), 1980, "Hard Rock", "Angus Young", 255000, 0, 44100, 16, 2, 320000, 6100000}, "A Night at the Opera", intPtr(11), intPtr(1), 1975, "Rock",
{4, "/music/pinkfloyd/comfortably_numb.flac", "Comfortably Numb", "Pink Floyd", "The Dark Side of the Moon", intPtr(6), intPtr(1), 1979, "Progressive Rock", "David Gilmour", 382000, 1, 96000, 24, 2, 1411000, 54000000}, "Freddie Mercury", 354000, 0, 44100, 16, 2, 320000, 8500000,
{5, "/music/queen/another_one_bites_the_dust.mp3", "Another One Bites the Dust", "Queen", "The Game", intPtr(3), intPtr(1), 1980, "Funk Rock", "John Deacon", 215000, 0, 44100, 16, 2, 320000, 5200000}, },
{6, "/music/acdc/thunderstruck.mp3", "Thunderstruck", "AC/DC", "The Razors Edge", intPtr(1), intPtr(1), 1990, "Hard Rock", "Angus Young", 292000, 0, 44100, 16, 2, 320000, 7000000}, {
{7, "/music/qotsa/queen_of_the_stone_age.mp3", "Queen of the Stone Age", "Queens of the Stone Age", "Rated R", intPtr(1), intPtr(1), 2000, "Stoner Rock", "Josh Homme", 310000, 0, 44100, 16, 2, 320000, 7400000}, 2, "/music/beyonce/halo.flac", "Halo", "Beyoncé", "Lemonade",
intPtr(1), intPtr(1), 2008, "Pop", "Ryan Tedder", 261000, 1,
96000, 24, 2, 1411000, 42000000,
},
{
3, "/music/acdc/back_in_black.mp3", "Back in Black", "AC/DC",
"Back in Black", intPtr(1), intPtr(1), 1980, "Hard Rock",
"Angus Young", 255000, 0, 44100, 16, 2, 320000, 6100000,
},
{
4, "/music/pinkfloyd/comfortably_numb.flac", "Comfortably Numb",
"Pink Floyd", "The Dark Side of the Moon", intPtr(6), intPtr(1),
1979, "Progressive Rock", "David Gilmour", 382000, 1, 96000, 24,
2, 1411000, 54000000,
},
{
5, "/music/queen/another_one_bites_the_dust.mp3",
"Another One Bites the Dust", "Queen", "The Game", intPtr(3),
intPtr(1), 1980, "Funk Rock", "John Deacon", 215000, 0, 44100,
16, 2, 320000, 5200000,
},
{
6, "/music/acdc/thunderstruck.mp3", "Thunderstruck", "AC/DC",
"The Razors Edge", intPtr(1), intPtr(1), 1990, "Hard Rock",
"Angus Young", 292000, 0, 44100, 16, 2, 320000, 7000000,
},
{
7, "/music/qotsa/queen_of_the_stone_age.mp3",
"Queen of the Stone Age", "Queens of the Stone Age", "Rated R",
intPtr(1), intPtr(1), 2000, "Stoner Rock", "Josh Homme", 310000,
0, 44100, 16, 2, 320000, 7400000,
},
} }
// Build unique sets. // Build unique sets.
type artistEntry struct {
id int64
text string
}
type albumEntry struct {
id int64
name string
}
artistMap := map[string]int64{} artistMap := map[string]int64{}
albumMap := map[string]int64{} albumMap := map[string]int64{}
var artistID, albumID int64 var artistID, albumID int64
for _, tr := range tracks { for _, tr := range tracks {
@@ -104,6 +126,7 @@ func seedSearchData(t *testing.T, db *DB) {
// Insert genres + recording_genres. // Insert genres + recording_genres.
genreMap := map[string]int64{} genreMap := map[string]int64{}
var genreID int64 var genreID int64
for _, tr := range tracks { for _, tr := range tracks {
@@ -131,8 +154,11 @@ func seedSearchData(t *testing.T, db *DB) {
// Insert recording. // Insert recording.
_, err := db.ExecContext( _, err := db.ExecContext(
"INSERT INTO recordings (id, name, artist_credit_id, track_number, disc_number, year, genre, composer) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", "INSERT INTO recordings (id, name, artist_credit_id, "+
tr.id, tr.title, acID, tr.trackNum, tr.discNum, tr.year, tr.genre, tr.composer, "track_number, disc_number, year, genre, composer) "+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
tr.id, tr.title, acID, tr.trackNum, tr.discNum,
tr.year, tr.genre, tr.composer,
) )
if err != nil { if err != nil {
t.Fatalf("insert recording %d %q: %v", tr.id, tr.title, err) t.Fatalf("insert recording %d %q: %v", tr.id, tr.title, err)
@@ -140,8 +166,12 @@ func seedSearchData(t *testing.T, db *DB) {
// Insert audio_files. // Insert audio_files.
_, err = db.ExecContext( _, err = db.ExecContext(
"INSERT INTO audio_files (id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "INSERT INTO audio_files (id, file_path, "+
tr.id, tr.filePath, tr.lenMs, tr.ftID, tr.id, tr.sr, tr.bd, tr.ch, tr.br, tr.fsize, "length_milliseconds, file_type_id, recording_id, "+
"sample_rate, bit_depth, channels, bitrate, file_size) "+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
tr.id, tr.filePath, tr.lenMs, tr.ftID, tr.id,
tr.sr, tr.bd, tr.ch, tr.br, tr.fsize,
) )
if err != nil { if err != nil {
t.Fatalf("insert audio_file %d: %v", tr.id, err) t.Fatalf("insert audio_file %d: %v", tr.id, err)
@@ -149,7 +179,9 @@ func seedSearchData(t *testing.T, db *DB) {
// Link recording to release_group. // Link recording to release_group.
_, err = db.ExecContext( _, err = db.ExecContext(
"INSERT INTO release_group_recordings (release_group_id, recording_id, track_number, disc_number) VALUES (?, ?, ?, ?)", "INSERT INTO release_group_recordings "+
"(release_group_id, recording_id, track_number, disc_number) "+
"VALUES (?, ?, ?, ?)",
rgID, tr.id, tr.trackNum, tr.discNum, rgID, tr.id, tr.trackNum, tr.discNum,
) )
if err != nil { if err != nil {
@@ -368,6 +400,7 @@ func TestSearchFTS_SpecialCharacters(t *testing.T) {
// Verify at least one AC/DC track is present. // Verify at least one AC/DC track is present.
found := false found := false
for _, r := range results { for _, r := range results {
if r.Artist == "AC/DC" { if r.Artist == "AC/DC" {
found = true found = true
@@ -432,6 +465,7 @@ func TestSearchFTS_Diacritics(t *testing.T) {
} }
found := false found := false
for _, r := range results { for _, r := range results {
if r.Artist == "Beyoncé" { if r.Artist == "Beyoncé" {
found = true found = true
@@ -489,6 +523,7 @@ func TestSearchFTSByFilename(t *testing.T) {
} }
found := false found := false
for _, r := range results { for _, r := range results {
if r.Title == "Bohemian Rhapsody" { if r.Title == "Bohemian Rhapsody" {
found = true found = true
@@ -620,7 +655,9 @@ func TestInsertAndDeleteSearchIndex(t *testing.T) {
} }
// Insert into search index. // Insert into search index.
if err := db.InsertSearchIndex(1, "/test/track.mp3", "Test Track", "Test Artist", "Test Album"); err != nil { if err := db.InsertSearchIndex(
1, "/test/track.mp3", "Test Track", "Test Artist", "Test Album",
); err != nil {
t.Fatalf("InsertSearchIndex: %v", err) t.Fatalf("InsertSearchIndex: %v", err)
} }
@@ -781,11 +818,13 @@ func TestMigrationsApplied(t *testing.T) {
if !rows.Next() { if !rows.Next() {
_ = rows.Close() _ = rows.Close()
t.Fatal("PRAGMA user_version: no row returned") t.Fatal("PRAGMA user_version: no row returned")
} }
if err := rows.Scan(&version); err != nil { if err := rows.Scan(&version); err != nil {
_ = rows.Close() _ = rows.Close()
t.Fatalf("scan user_version: %v", err) t.Fatalf("scan user_version: %v", err)
} }
+1 -1
View File
@@ -63,7 +63,7 @@ func NewTestDB(t *testing.T) *DB {
queries := sqlcgen.New(db) queries := sqlcgen.New(db)
t.Cleanup(func() { db.Close() }) t.Cleanup(func() { _ = db.Close() })
return &DB{ return &DB{
db: db, db: db,
+16 -3
View File
@@ -32,6 +32,7 @@ func main() {
if *output == "" || *output == "/dev/stdout" { if *output == "" || *output == "/dev/stdout" {
fmt.Print(ts) fmt.Print(ts)
return return
} }
@@ -57,6 +58,7 @@ type constEntry struct {
// groups in declaration order. // groups in declaration order.
func parseEvents(path string) ([]constGroup, error) { func parseEvents(path string) ([]constGroup, error) {
fset := token.NewFileSet() fset := token.NewFileSet()
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments) f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil { if err != nil {
return nil, fmt.Errorf("parse %s: %w", path, err) return nil, fmt.Errorf("parse %s: %w", path, err)
@@ -82,11 +84,14 @@ func parseEvents(path string) ([]constGroup, error) {
if !ok { if !ok {
continue continue
} }
for i, name := range vs.Names { for i, name := range vs.Names {
if i >= len(vs.Values) { if i >= len(vs.Values) {
continue continue
} }
bl, ok := vs.Values[i].(*ast.BasicLit) bl, ok := vs.Values[i].(*ast.BasicLit)
if !ok || bl.Kind != token.STRING { if !ok || bl.Kind != token.STRING {
continue continue
} }
@@ -110,6 +115,7 @@ func parseEvents(path string) ([]constGroup, error) {
func cleanComment(s string) string { func cleanComment(s string) string {
s = strings.TrimSpace(s) s = strings.TrimSpace(s)
s = strings.TrimSuffix(s, ".") s = strings.TrimSuffix(s, ".")
return s return s
} }
@@ -126,6 +132,7 @@ func generateTypeScript(groups []constGroup) string {
if g.Comment != "" { if g.Comment != "" {
b.WriteString(" // " + g.Comment + "\n") b.WriteString(" // " + g.Comment + "\n")
} }
for _, c := range g.Consts { for _, c := range g.Consts {
b.WriteString(fmt.Sprintf(" %s: %q,\n", c.Name, c.Value)) b.WriteString(fmt.Sprintf(" %s: %q,\n", c.Name, c.Value))
} }
@@ -146,20 +153,26 @@ func generateTypeScript(groups []constGroup) string {
// then renames it into place for atomic replacement. // then renames it into place for atomic replacement.
func writeAtomic(path, data string) error { func writeAtomic(path, data string) error {
dir := filepath.Dir(path) dir := filepath.Dir(path)
tmp, err := os.CreateTemp(dir, ".genevents-*.tmp") tmp, err := os.CreateTemp(dir, ".genevents-*.tmp")
if err != nil { if err != nil {
return err return err
} }
tmpName := tmp.Name() tmpName := tmp.Name()
if _, err := tmp.WriteString(data); err != nil { if _, err := tmp.WriteString(data); err != nil {
tmp.Close() _ = tmp.Close()
os.Remove(tmpName) _ = os.Remove(tmpName)
return err return err
} }
if err := tmp.Close(); err != nil { if err := tmp.Close(); err != nil {
os.Remove(tmpName) _ = os.Remove(tmpName)
return err return err
} }
return os.Rename(tmpName, path) return os.Rename(tmpName, path)
} }
+1
View File
@@ -31,6 +31,7 @@ func TestFavoritesConfig_Validate_InvalidIconStyle(t *testing.T) {
t.Parallel() t.Parallel()
c := &Config{IconStyle: "diamond"} c := &Config{IconStyle: "diamond"}
err := c.Validate() err := c.Validate()
if err == nil { if err == nil {
t.Fatal("Validate() expected error for unknown icon style, got nil") t.Fatal("Validate() expected error for unknown icon style, got nil")
+13 -8
View File
@@ -229,6 +229,7 @@ func (l *Library) Scan() (*ScanMetrics, error) {
"count", len(existingFiles), "count", len(existingFiles),
"library-directory", l.conf.DirectoryPath, "library-directory", l.conf.DirectoryPath,
) )
workChan := make(chan scanWork, 100) workChan := make(chan scanWork, 100)
resultChan := make(chan importResult, 100) resultChan := make(chan importResult, 100)
@@ -531,10 +532,14 @@ func (l *Library) Scan() (*ScanMetrics, error) {
// point so it is safe to close. // point so it is safe to close.
thumbStart := time.Now() thumbStart := time.Now()
runtime.EventsEmit(l.ctx, events.LibraryScanProgress, runtime.EventsEmit(l.ctx, events.LibraryScanProgress, ScanProgress{
ScanProgress{Phase: "thumbnails", Total: totalFiles, Phase: "thumbnails",
Processed: a + s + u, Added: a, Skipped: s, Updated: u}, Total: totalFiles,
) Processed: a + s + u,
Added: a,
Skipped: s,
Updated: u,
})
close(thumbChan) close(thumbChan)
thumbWg.Wait() thumbWg.Wait()
@@ -542,10 +547,10 @@ func (l *Library) Scan() (*ScanMetrics, error) {
metrics.ThumbnailWallClock = time.Since(thumbStart) metrics.ThumbnailWallClock = time.Since(thumbStart)
// --- Phase 5: orphan cleanup --- // --- Phase 5: orphan cleanup ---
runtime.EventsEmit(l.ctx, events.LibraryScanProgress, runtime.EventsEmit(l.ctx, events.LibraryScanProgress, ScanProgress{
ScanProgress{Phase: "orphans", Total: totalFiles, Phase: "orphans", Total: totalFiles,
Processed: a + s + u, Added: a, Skipped: s, Updated: u}, Processed: a + s + u, Added: a, Skipped: s, Updated: u,
) })
orphanStart := time.Now() orphanStart := time.Now()
+18 -5
View File
@@ -396,7 +396,10 @@ func TestCachedLinkArtist(t *testing.T) {
lib.cachedLinkArtist(q, cache, metrics, "Queen", ac.ID) lib.cachedLinkArtist(q, cache, metrics, "Queen", ac.ID)
if len(cache.linkedCredits) != 1 { if len(cache.linkedCredits) != 1 {
t.Errorf("linkedCredits after duplicate = %d, want 1 (should skip)", len(cache.linkedCredits)) t.Errorf(
"linkedCredits after duplicate = %d, want 1 (should skip)",
len(cache.linkedCredits),
)
} }
} }
@@ -536,6 +539,7 @@ func TestResolveReleaseGroup(t *testing.T) {
// Cache key is composite: "albumName\x00artistCreditID". // Cache key is composite: "albumName\x00artistCreditID".
cacheKey := fmt.Sprintf("%s\x00%d", "A Night at the Opera", ac.ID) cacheKey := fmt.Sprintf("%s\x00%d", "A Night at the Opera", ac.ID)
cachedRG := cache.releaseGroups[cacheKey] cachedRG := cache.releaseGroups[cacheKey]
if !cachedRG.CoverArtID.Valid { if !cachedRG.CoverArtID.Valid {
t.Error("expected CoverArtID to be set after update") t.Error("expected CoverArtID to be set after update")
} }
@@ -616,7 +620,9 @@ func TestOrphanDeletion(t *testing.T) {
} }
// Add FTS search index entry. // Add FTS search index entry.
if err := db.InsertSearchIndex(af.ID, "/music/test.mp3", "Test Song", "Test Artist", ""); err != nil { if err := db.InsertSearchIndex(
af.ID, "/music/test.mp3", "Test Song", "Test Artist", "",
); err != nil {
t.Fatalf("insert search index: %v", err) t.Fatalf("insert search index: %v", err)
} }
@@ -651,11 +657,12 @@ func TestOrphanDeletion(t *testing.T) {
// become stale but harmless (they reference a non-existent // become stale but harmless (they reference a non-existent
// audio_file ID, so JOINs return no results). // audio_file ID, so JOINs return no results).
// ClearSearchIndex (used during full rescan) handles bulk cleanup. // ClearSearchIndex (used during full rescan) handles bulk cleanup.
// DeleteSearchIndex on contentless FTS5 is expected to error.
// Not a fatal error — documents the contentless FTS5 limitation.
err = db.DeleteSearchIndex(af.ID) err = db.DeleteSearchIndex(af.ID)
if err == nil { if err == nil {
t.Log("DeleteSearchIndex succeeded (unexpected for contentless FTS5)") t.Log("DeleteSearchIndex succeeded (unexpected for contentless FTS5)")
} }
// Not a fatal error — documents the contentless FTS5 limitation.
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -705,7 +712,10 @@ func TestEntityCache_EmptyFields(t *testing.T) {
} }
if albumACID.Int64 != trackAC.ID { if albumACID.Int64 != trackAC.ID {
t.Errorf("empty AlbumArtist should reuse track credit: got %d, want %d", albumACID.Int64, trackAC.ID) t.Errorf(
"empty AlbumArtist should reuse track credit: got %d, want %d",
albumACID.Int64, trackAC.ID,
)
} }
// resolveAlbumArtistCredit when AlbumArtist matches Artist also reuses. // resolveAlbumArtistCredit when AlbumArtist matches Artist also reuses.
@@ -716,6 +726,9 @@ func TestEntityCache_EmptyFields(t *testing.T) {
sameACID := lib.resolveAlbumArtistCredit(q, cache, metrics, sameTags, trackAC.ID) sameACID := lib.resolveAlbumArtistCredit(q, cache, metrics, sameTags, trackAC.ID)
if sameACID.Int64 != trackAC.ID { if sameACID.Int64 != trackAC.ID {
t.Errorf("matching AlbumArtist should reuse track credit: got %d, want %d", sameACID.Int64, trackAC.ID) t.Errorf(
"matching AlbumArtist should reuse track credit: got %d, want %d",
sameACID.Int64, trackAC.ID,
)
} }
} }
+10 -4
View File
@@ -126,8 +126,11 @@ func TestUserVolume_ToVolume_Roundtrip(t *testing.T) {
diff := int(roundtripped) - int(i) diff := int(roundtripped) - int(i)
if diff < -1 || diff > 1 { if diff < -1 || diff > 1 {
t.Errorf("Roundtrip UserVolume(%d) -> Volume(%f) -> UserVolume(%d): drift %d exceeds ±1", t.Errorf(
i, vol, roundtripped, diff) "Roundtrip UserVolume(%d) -> Volume(%f) -> UserVolume(%d): "+
"drift %d exceeds ±1",
i, vol, roundtripped, diff,
)
} }
} }
@@ -138,8 +141,11 @@ func TestUserVolume_ToVolume_Roundtrip(t *testing.T) {
roundtripped := vol.ToUserVolume() roundtripped := vol.ToUserVolume()
if roundtripped != uv { if roundtripped != uv {
t.Errorf("Exact roundtrip UserVolume(%d) -> Volume(%f) -> UserVolume(%d): want exact match", t.Errorf(
uv, vol, roundtripped) "Exact roundtrip UserVolume(%d) -> Volume(%f) -> "+
"UserVolume(%d): want exact match",
uv, vol, roundtripped,
)
} }
} }
} }
+17 -4
View File
@@ -14,7 +14,7 @@ func newTestQueueDirect(tracks int, currentIndex int) *Queue {
} }
q.tracks = make([]Track, tracks) q.tracks = make([]Track, tracks)
for i := 0; i < tracks; i++ { for i := range tracks {
q.tracks[i] = Track{FilePath: "/test/track.mp3", Position: int64(i)} q.tracks[i] = Track{FilePath: "/test/track.mp3", Position: int64(i)}
} }
@@ -135,11 +135,15 @@ func TestGenerateShuffleOrder_Properties(t *testing.T) {
// Property 2: current track is at shuffleOrder[0]. // Property 2: current track is at shuffleOrder[0].
if q.shuffleOrder[0] != tc.currentIdx { if q.shuffleOrder[0] != tc.currentIdx {
t.Errorf("shuffleOrder[0]: got %d, want %d (currentIndex)", 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). // Property 3: all indices present (no duplicates, no missing).
seen := make(map[int]bool, tc.trackCount) seen := make(map[int]bool, tc.trackCount)
for _, idx := range q.shuffleOrder { for _, idx := range q.shuffleOrder {
if idx < 0 || idx >= tc.trackCount { if idx < 0 || idx >= tc.trackCount {
t.Errorf("shuffleOrder contains out-of-range index: %d", idx) t.Errorf("shuffleOrder contains out-of-range index: %d", idx)
@@ -153,7 +157,10 @@ func TestGenerateShuffleOrder_Properties(t *testing.T) {
} }
if len(seen) != tc.trackCount { if len(seen) != tc.trackCount {
t.Errorf("unique indices in shuffleOrder: got %d, want %d", len(seen), tc.trackCount) t.Errorf(
"unique indices in shuffleOrder: got %d, want %d",
len(seen), tc.trackCount,
)
} }
}) })
} }
@@ -177,6 +184,7 @@ func TestNextIndex_ShuffleMode(t *testing.T) {
// Advance to index 4 and get next. // Advance to index 4 and get next.
q.currentIndex = 4 q.currentIndex = 4
got = q.nextIndex() got = q.nextIndex()
if got != 0 { if got != 0 {
t.Errorf("nextIndex (shuffle, pos 2): got %d, want 0", got) t.Errorf("nextIndex (shuffle, pos 2): got %d, want 0", got)
} }
@@ -184,6 +192,7 @@ func TestNextIndex_ShuffleMode(t *testing.T) {
// At the end of shuffle order with RepeatOff. // At the end of shuffle order with RepeatOff.
q.currentIndex = 1 // last in shuffleOrder q.currentIndex = 1 // last in shuffleOrder
got = q.nextIndex() got = q.nextIndex()
if got != -1 { if got != -1 {
t.Errorf("nextIndex (shuffle, end, repeatOff): got %d, want -1", got) t.Errorf("nextIndex (shuffle, end, repeatOff): got %d, want -1", got)
} }
@@ -191,7 +200,11 @@ func TestNextIndex_ShuffleMode(t *testing.T) {
// At the end of shuffle order with RepeatAll. // At the end of shuffle order with RepeatAll.
q.repeatMode = RepeatAll q.repeatMode = RepeatAll
got = q.nextIndex() got = q.nextIndex()
if got != 2 { if got != 2 {
t.Errorf("nextIndex (shuffle, end, repeatAll): got %d, want 2 (wraps to shuffleOrder[0])", got) t.Errorf(
"nextIndex (shuffle, end, repeatAll): got %d, want 2 "+
"(wraps to shuffleOrder[0])", got,
)
} }
} }
+12 -3
View File
@@ -35,7 +35,10 @@ func TestSaveState_RestoreState_Roundtrip(t *testing.T) {
// Each track's FilePath, Title, Artist. // Each track's FilePath, Title, Artist.
for i := range s1.Tracks { for i := range s1.Tracks {
if s2.Tracks[i].FilePath != s1.Tracks[i].FilePath { if s2.Tracks[i].FilePath != s1.Tracks[i].FilePath {
t.Errorf("track[%d] FilePath: got %q, want %q", i, s2.Tracks[i].FilePath, s1.Tracks[i].FilePath) t.Errorf(
"track[%d] FilePath: got %q, want %q",
i, s2.Tracks[i].FilePath, s1.Tracks[i].FilePath,
)
} }
if s2.Tracks[i].Title != s1.Tracks[i].Title { if s2.Tracks[i].Title != s1.Tracks[i].Title {
@@ -43,7 +46,10 @@ func TestSaveState_RestoreState_Roundtrip(t *testing.T) {
} }
if s2.Tracks[i].Artist != s1.Tracks[i].Artist { if s2.Tracks[i].Artist != s1.Tracks[i].Artist {
t.Errorf("track[%d] Artist: got %q, want %q", i, s2.Tracks[i].Artist, s1.Tracks[i].Artist) t.Errorf(
"track[%d] Artist: got %q, want %q",
i, s2.Tracks[i].Artist, s1.Tracks[i].Artist,
)
} }
} }
@@ -71,7 +77,10 @@ func TestSaveState_RestoreState_Roundtrip(t *testing.T) {
} else { } else {
for i := range q.shuffleOrder { for i := range q.shuffleOrder {
if q2.shuffleOrder[i] != q.shuffleOrder[i] { if q2.shuffleOrder[i] != q.shuffleOrder[i] {
t.Errorf("shuffleOrder[%d]: got %d, want %d", i, q2.shuffleOrder[i], q.shuffleOrder[i]) t.Errorf(
"shuffleOrder[%d]: got %d, want %d",
i, q2.shuffleOrder[i], q.shuffleOrder[i],
)
} }
} }
} }
+9 -3
View File
@@ -16,6 +16,7 @@ type mockTrackLoader struct {
func (m *mockTrackLoader) LoadFile(filePath string) error { func (m *mockTrackLoader) LoadFile(filePath string) error {
m.loadedFile = filePath m.loadedFile = filePath
return nil return nil
} }
@@ -53,7 +54,7 @@ func seedAudioFiles(t *testing.T, db *database.DB, count int) []string {
paths := make([]string, count) paths := make([]string, count)
for i := 0; i < count; i++ { for i := range count {
recID := i + 1 recID := i + 1
afID := i + 1 afID := i + 1
fp := fmt.Sprintf("/test/track%d.mp3", i+1) fp := fmt.Sprintf("/test/track%d.mp3", i+1)
@@ -68,7 +69,9 @@ func seedAudioFiles(t *testing.T, db *database.DB, count int) []string {
} }
_, err = db.ExecContext( _, err = db.ExecContext(
"INSERT OR IGNORE INTO audio_files (id, file_path, length_milliseconds, file_type_id, recording_id) VALUES (?, ?, 180000, 0, ?)", "INSERT OR IGNORE INTO audio_files (id, file_path, "+
"length_milliseconds, file_type_id, recording_id) "+
"VALUES (?, ?, 180000, 0, ?)",
afID, fp, recID, afID, fp, recID,
) )
if err != nil { if err != nil {
@@ -292,7 +295,10 @@ func TestRemoveTrack_RemoveCurrentTrack(t *testing.T) {
// After removing currentIndex=2, index should be clamped to valid range. // After removing currentIndex=2, index should be clamped to valid range.
if state.CurrentIndex < 0 || state.CurrentIndex >= len(state.Tracks) { if state.CurrentIndex < 0 || state.CurrentIndex >= len(state.Tracks) {
t.Errorf("currentIndex out of range: got %d, track count %d", state.CurrentIndex, len(state.Tracks)) t.Errorf(
"currentIndex out of range: got %d, track count %d",
state.CurrentIndex, len(state.Tracks),
)
} }
} }
+2
View File
@@ -49,6 +49,7 @@ func TestThemeConfig_Validate_InvalidHexColor(t *testing.T) {
t.Parallel() t.Parallel()
c := &Config{AccentColor: tt.color, BackgroundShade: BackgroundDark} c := &Config{AccentColor: tt.color, BackgroundShade: BackgroundDark}
err := c.Validate() err := c.Validate()
if err == nil { if err == nil {
t.Error("Validate() expected error for invalid hex color, got nil") t.Error("Validate() expected error for invalid hex color, got nil")
@@ -61,6 +62,7 @@ func TestThemeConfig_Validate_InvalidBackgroundShade(t *testing.T) {
t.Parallel() t.Parallel()
c := &Config{AccentColor: "#ffd43b", BackgroundShade: "neon"} c := &Config{AccentColor: "#ffd43b", BackgroundShade: "neon"}
err := c.Validate() err := c.Validate()
if err == nil { if err == nil {
t.Fatal("Validate() expected error for unknown shade, got nil") t.Fatal("Validate() expected error for unknown shade, got nil")