offline updates and playhead fix
This commit is contained in:
parent
7a199fe4df
commit
6663330260
14 changed files with 1673 additions and 545 deletions
173
test/offline_library_test.dart
Normal file
173
test/offline_library_test.dart
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
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);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue