import 'package:flutter_test/flutter_test.dart'; import 'package:timbre/library/offline_library.dart'; import 'package:timbre/subsonic/models.dart'; /// Builds a downloaded-track [Song] with just the fields the offline /// reconstruction reads. Everything else defaults to null. Song _song( String id, { String? title, String? album, String? albumId, String? artist, String? artistId, String? coverArt, int? track, int? discNumber, int? year, String? genre, }) => Song( id: id, title: title, album: album, albumId: albumId, artist: artist, artistId: artistId, coverArt: coverArt, track: track, discNumber: discNumber, year: year, genre: genre, ); void main() { group('offline_library — album reconstruction', () { test('groups songs by albumId and synthesizes album metadata', () { final songs = [ _song('1', title: 'A', album: 'Rumours', albumId: 'alb1', artist: 'Fleetwood Mac', artistId: 'art1', coverArt: 'cov1', track: 2, year: 1977, genre: 'Rock'), _song('2', title: 'B', album: 'Rumours', albumId: 'alb1', artist: 'Fleetwood Mac', artistId: 'art1', coverArt: 'cov1', track: 1, year: 1977, genre: 'Rock'), ]; final albums = albumsFromSongs(songs); expect(albums, hasLength(1)); final a = albums.single; expect(a.id, 'alb1'); expect(a.name, 'Rumours'); expect(a.artist, 'Fleetwood Mac'); expect(a.artistId, 'art1'); expect(a.coverArt, 'cov1'); expect(a.year, 1977); expect(a.genre, 'Rock'); expect(a.songCount, 2); }); test('orders an album by disc then track, nulls last', () { final songs = [ _song('1', album: 'X', albumId: 'x', title: 'no-track'), _song('2', album: 'X', albumId: 'x', title: 'd1t2', discNumber: 1, track: 2), _song('3', album: 'X', albumId: 'x', title: 'd2t1', discNumber: 2, track: 1), _song('4', album: 'X', albumId: 'x', title: 'd1t1', discNumber: 1, track: 1), ]; final ids = albumsFromSongs(songs).single.songs.map((s) => s.title).toList(); expect(ids, ['d1t1', 'd1t2', 'd2t1', 'no-track']); }); test('falls back to album name as key and id when albumId is missing', () { final songs = [ _song('1', album: 'Untitled Sessions', title: 'A'), _song('2', album: 'Untitled Sessions', title: 'B'), ]; final albums = albumsFromSongs(songs); expect(albums, hasLength(1)); expect(albums.single.id, 'Untitled Sessions'); expect(albums.single.songCount, 2); }); test('skips songs with no album identity', () { final songs = [ _song('1', title: 'orphan'), _song('2', album: 'Real', albumId: 'r', title: 'kept'), ]; final albums = albumsFromSongs(songs); expect(albums, hasLength(1)); expect(albums.single.id, 'r'); }); test('sorts albums by name case-insensitively', () { final songs = [ _song('1', album: 'zebra', albumId: 'z'), _song('2', album: 'Apple', albumId: 'a'), _song('3', album: 'mango', albumId: 'm'), ]; final names = albumsFromSongs(songs).map((a) => a.name).toList(); expect(names, ['Apple', 'mango', 'zebra']); }); }); group('offline_library — artist reconstruction', () { test('groups by artistId and nests albums', () { final songs = [ _song('1', album: 'One', albumId: 'a1', artist: 'Radiohead', artistId: 'r', coverArt: 'c1'), _song('2', album: 'Two', albumId: 'a2', artist: 'Radiohead', artistId: 'r', coverArt: 'c2'), ]; final artists = artistsFromSongs(songs); expect(artists, hasLength(1)); final a = artists.single; expect(a.id, 'r'); expect(a.name, 'Radiohead'); expect(a.albumCount, 2); expect(a.albums.map((al) => al.id), containsAll(['a1', 'a2'])); expect(a.coverArt, isNotNull); }); test('falls back to artist name as key and id', () { final songs = [_song('1', album: 'X', albumId: 'x', artist: 'Nameless Band')]; final artists = artistsFromSongs(songs); expect(artists.single.id, 'Nameless Band'); }); }); group('offline_library — keyed lookups', () { final songs = [ _song('1', album: 'One', albumId: 'a1', artist: 'Band', artistId: 'b1'), _song('2', album: 'Two', albumId: 'a2', artist: 'Band', artistId: 'b1'), ]; test('albumFromSongs returns the matching album or null', () { expect(albumFromSongs(songs, 'a2')?.name, 'Two'); expect(albumFromSongs(songs, 'nope'), isNull); }); test('artistFromSongs returns the matching artist or null', () { final artist = artistFromSongs(songs, 'b1'); expect(artist?.name, 'Band'); expect(artist?.albumCount, 2); expect(artistFromSongs(songs, 'nope'), isNull); }); }); test('empty input yields empty output', () { expect(albumsFromSongs(const []), isEmpty); expect(artistsFromSongs(const []), isEmpty); expect(albumFromSongs(const [], 'x'), isNull); expect(artistFromSongs(const [], 'x'), isNull); }); }