diff --git a/lib/localization/localization.dart b/lib/localization/localization.dart index 811fb145a..09537c1de 100644 --- a/lib/localization/localization.dart +++ b/lib/localization/localization.dart @@ -65,8 +65,6 @@ extension AppLocalizationsExtension on AppLocalizations { return artist; case SongSortFeature.album: return album; - default: - throw UnimplementedError(); } case ContentType.album: switch (feature as AlbumSortFeature) { @@ -78,8 +76,6 @@ extension AppLocalizationsExtension on AppLocalizations { return year; case AlbumSortFeature.numberOfSongs: return numberOfTracks; - default: - throw UnimplementedError(); } case ContentType.playlist: switch (feature as PlaylistSortFeature) { @@ -89,8 +85,6 @@ extension AppLocalizationsExtension on AppLocalizations { return dateAdded; case PlaylistSortFeature.name: return title; - default: - throw UnimplementedError(); } case ContentType.artist: switch (feature as ArtistSortFeature) { @@ -100,8 +94,6 @@ extension AppLocalizationsExtension on AppLocalizations { return numberOfAlbums; case ArtistSortFeature.numberOfTracks: return numberOfTracks; - default: - throw UnimplementedError(); } } } diff --git a/lib/logic/models/album.dart b/lib/logic/models/album.dart index 036168b19..abb543c0c 100644 --- a/lib/logic/models/album.dart +++ b/lib/logic/models/album.dart @@ -37,8 +37,8 @@ class Album extends PersistentQueue implements PlatformAlbum { bool get playable => true; /// Gets album normalized year. - int get year { - return lastYear == null || lastYear! < 1000 ? clock.now().year : lastYear!; + int? get year { + return lastYear ?? firstYear; } /// Returns string in format `album name • year`. @@ -46,11 +46,6 @@ class Album extends PersistentQueue implements PlatformAlbum { return ContentUtils.appendYearWithDot(album, year); } - /// Returns string in format `Album • year`. - String albumDotName(AppLocalizations l10n) { - return ContentUtils.appendYearWithDot(l10n.album, year); - } - /// Returns the album artist. Artist? getArtist() => artistId == null ? null : ContentControl.instance.state.artists.firstWhereOrNull((el) => el.id == artistId); diff --git a/lib/logic/models/sort.dart b/lib/logic/models/sort.dart index 99b2c4558..f9ed7a35f 100644 --- a/lib/logic/models/sort.dart +++ b/lib/logic/models/sort.dart @@ -1,15 +1,19 @@ -import 'package:enum_to_string/enum_to_string.dart'; import 'package:equatable/equatable.dart'; import 'package:sweyer/sweyer.dart'; -/// Interface for other sort feature enums. -abstract class SortFeature extends Enum { - const SortFeature._(String value) : super(value); +/// The order of a sort operation. +enum SortOrder { + /// Ascending sort order ("lower" values first, "higher" values last). + ascending, + + /// Descending sort order ("higher" values first, "lower" values last). + descending, +} +/// Interface for other sort feature enums. +abstract class SortFeature { /// Returns sort feature values for a given content. static List> getValuesForContent(ContentType contentType) { - // TODO: Remove ContentType cast, see https://github.com/dart-lang/language/issues/2315 - // ignore: unnecessary_cast switch (contentType as ContentType) { case ContentType.song: return SongSortFeature.values as List>; @@ -22,120 +26,112 @@ abstract class SortFeature extends Enum { } } - /// Whether the default order is ASC. - bool get defaultOrderAscending; + /// An identifier for this feature. + String get id; + + /// The default sorting order of the feature. + SortOrder get defaultSortingOrder; } /// Features to sort by a [Song] list. /// /// Each feature also has the default sort order - ASC or DESC. /// When user changes the [SongSortFeature] the new default sort order is applied. -class SongSortFeature extends SortFeature { - const SongSortFeature._(String value) : super._(value); - - @override - bool get defaultOrderAscending => this != dateModified && this != dateAdded; - - static List get values => const [dateModified, dateAdded, title, artist, album]; - +enum SongSortFeature implements SortFeature { /// Sort by the [Song.dateModified]. - /// Default sort order is DESC. - static const dateModified = SongSortFeature._('dateModified'); + dateModified(SortOrder.descending), /// Sort by the [Song.dateAdded]. - /// Default sort order is DESC. - static const dateAdded = SongSortFeature._('dateAdded'); + dateAdded(SortOrder.descending), /// Sort by the [Song.title]. - /// Default sort order is ASC. - static const title = SongSortFeature._('title'); + title(SortOrder.ascending), /// Sort by the [Song.artist]. - /// Default sort order is ASC. - static const artist = SongSortFeature._('artist'); + artist(SortOrder.ascending), /// Sort by the [Song.album]. - /// Default sort order is ASC. - static const album = SongSortFeature._('album'); + album(SortOrder.ascending); + + const SongSortFeature(this.defaultSortingOrder); + + @override + String get id => name; + + @override + final SortOrder defaultSortingOrder; } /// Features to sort by a [Album] list. /// /// Each feature also has the default sort order - ASC or DESC. /// When user changes the [AlbumSortFeature] the new default sort order is applied. -class AlbumSortFeature extends SortFeature { - const AlbumSortFeature._(String value) : super._(value); - - @override - bool get defaultOrderAscending => this != year; - - static List get values => const [title, artist, year, numberOfSongs]; - +enum AlbumSortFeature implements SortFeature { /// Sort by the [Album.album]. - /// Default sort order is ASC. - static const title = AlbumSortFeature._('title'); + title(SortOrder.ascending), /// Sort by the [Album.artist]. - /// Default sort order is ASC. - static const artist = AlbumSortFeature._('artist'); + artist(SortOrder.ascending), /// Sort by the [Album.lastYear]. - /// Default sort order is DESC. - static const year = AlbumSortFeature._('year'); + year(SortOrder.descending), /// Sort by the [Album.numberOfSongs]. - /// Default sort order is ASC. - static const numberOfSongs = AlbumSortFeature._('numberOfSongs'); + numberOfSongs(SortOrder.ascending); + + const AlbumSortFeature(this.defaultSortingOrder); + + @override + String get id => name; + + @override + final SortOrder defaultSortingOrder; } /// Features to sort by a [Playlist] list. /// /// Each feature also has the default sort order - ASC or DESC. /// When user changes the [PlaylistSortFeature] the new default sort order is applied. -class PlaylistSortFeature extends SortFeature { - const PlaylistSortFeature._(String value) : super._(value); - - @override - bool get defaultOrderAscending => this != dateModified && this != dateAdded; - - static List get values => const [dateAdded, dateModified, name]; - +enum PlaylistSortFeature implements SortFeature { /// Sort by the [Playlist.dateModified]. - /// Default sort order is DESC. - static const dateModified = PlaylistSortFeature._('dateModified'); + dateModified(SortOrder.descending), /// Sort by the [Playlist.dateAdded]. - /// Default sort order is DESC. - static const dateAdded = PlaylistSortFeature._('dateAdded'); + dateAdded(SortOrder.descending), /// Sort by the [Playlist.name]. - /// Default sort order is ASC. - static const name = PlaylistSortFeature._('name'); + name(SortOrder.ascending); + + const PlaylistSortFeature(this.defaultSortingOrder); + + @override + String get id => this.name; + + @override + final SortOrder defaultSortingOrder; } /// Features to sort by a [Artist] list. /// /// Each feature also has the default sort order - ASC or DESC. /// When user changes the [ArtistSortFeature] the new default sort order is applied. -class ArtistSortFeature extends SortFeature { - const ArtistSortFeature._(String value) : super._(value); - - @override - bool get defaultOrderAscending => true; - - static List get values => const [name, numberOfAlbums, numberOfTracks]; - +enum ArtistSortFeature implements SortFeature { /// Sort by the [Artist.artist]. - /// Default sort order is ASC. - static const name = ArtistSortFeature._('name'); + name(SortOrder.ascending), /// Sort by the [Artist.numberOfAlbums]. - /// Default sort order is ASC. - static const numberOfAlbums = ArtistSortFeature._('numberOfAlbums'); + numberOfAlbums(SortOrder.ascending), /// Sort by the [Artist.numberOfTracks]. - /// Default sort order is ASC. - static const numberOfTracks = ArtistSortFeature._('numberOfTracks'); + numberOfTracks(SortOrder.ascending); + + const ArtistSortFeature(this.defaultSortingOrder); + + @override + String get id => this.name; + + @override + final SortOrder defaultSortingOrder; } abstract class Sort extends Equatable { @@ -143,7 +139,7 @@ abstract class Sort extends Equatable { required this.feature, required this.orderAscending, }); - Sort.defaultOrder(this.feature) : orderAscending = feature.defaultOrderAscending; + Sort.defaultOrder(this.feature) : orderAscending = feature.defaultSortingOrder == SortOrder.ascending; final SortFeature feature; final bool orderAscending; @@ -151,29 +147,26 @@ abstract class Sort extends Equatable { @override List get props => [feature, orderAscending]; - Sort copyWith({SortFeature? feature, bool? orderAscending}); - Sort get withDefaultOrder; + Sort copyWith({SortFeature? feature, bool? orderAscending}); + Sort get withDefaultOrder => copyWith(orderAscending: feature.defaultSortingOrder == SortOrder.ascending); Comparator get comparator; Map toMap() => { - 'feature': feature.value, + 'feature': feature.id, 'orderAscending': orderAscending, }; } class SongSort extends Sort { const SongSort({ - required SongSortFeature feature, - bool orderAscending = true, - }) : super(feature: feature, orderAscending: orderAscending); + required SongSortFeature super.feature, + super.orderAscending = true, + }); SongSort.defaultOrder(feature) : super.defaultOrder(feature); factory SongSort.fromMap(Map map) => SongSort( - feature: EnumToString.fromString( - SongSortFeature.values, - map['feature'], - )!, + feature: SongSortFeature.values.byName(map['feature']), orderAscending: map['orderAscending'], ); @@ -188,11 +181,6 @@ class SongSort extends Sort { ); } - @override - SongSort get withDefaultOrder { - return copyWith(orderAscending: feature.defaultOrderAscending); - } - int _fallbackTitle(Song a, Song b) { return a.title.toLowerCase().compareTo(b.title.toLowerCase()); } @@ -268,19 +256,10 @@ class AlbumSort extends Sort { AlbumSort.defaultOrder(feature) : super.defaultOrder(feature); factory AlbumSort.fromMap(Map map) => AlbumSort( - feature: EnumToString.fromString( - AlbumSortFeature.values, - map['feature'], - )!, + feature: AlbumSortFeature.values.byName(map['feature']), orderAscending: map['orderAscending'], ); - @override - Map toMap() => { - 'feature': feature.value, - 'orderAscending': orderAscending, - }; - @override AlbumSort copyWith({ covariant AlbumSortFeature? feature, @@ -292,13 +271,8 @@ class AlbumSort extends Sort { ); } - @override - AlbumSort get withDefaultOrder { - return copyWith(orderAscending: feature.defaultOrderAscending); - } - int _fallbackYear(Album a, Album b) { - return a.year.compareTo(b.year); + return (a.year ?? 0).compareTo((b.year ?? 0)); // TODO: Decide how to sort null values } int _fallbackTitle(Album a, Album b) { @@ -329,7 +303,7 @@ class AlbumSort extends Sort { break; case AlbumSortFeature.year: c = (a, b) { - final compare = a.year.compareTo(b.year); + final compare = (a.year ?? 0).compareTo(b.year ?? 0); // TODO: Decide how to sort null values if (compare == 0) { return _fallbackTitle(a, b); } @@ -363,19 +337,10 @@ class PlaylistSort extends Sort { PlaylistSort.defaultOrder(feature) : super.defaultOrder(feature); factory PlaylistSort.fromMap(Map map) => PlaylistSort( - feature: EnumToString.fromString( - PlaylistSortFeature.values, - map['feature'], - )!, + feature: PlaylistSortFeature.values.byName(map['feature']), orderAscending: map['orderAscending'], ); - @override - Map toMap() => { - 'feature': feature.value, - 'orderAscending': orderAscending, - }; - @override PlaylistSort copyWith({ covariant PlaylistSortFeature? feature, @@ -387,11 +352,6 @@ class PlaylistSort extends Sort { ); } - @override - PlaylistSort get withDefaultOrder { - return copyWith(orderAscending: feature.defaultOrderAscending); - } - int _fallbackName(Playlist a, Playlist b) { return a.name.toLowerCase().compareTo(b.name.toLowerCase()); } @@ -449,19 +409,10 @@ class ArtistSort extends Sort { ArtistSort.defaultOrder(feature) : super.defaultOrder(feature); factory ArtistSort.fromMap(Map map) => ArtistSort( - feature: EnumToString.fromString( - ArtistSortFeature.values, - map['feature'], - )!, + feature: ArtistSortFeature.values.byName(map['feature']), orderAscending: map['orderAscending'], ); - @override - Map toMap() => { - 'feature': feature.value, - 'orderAscending': orderAscending, - }; - @override ArtistSort copyWith({ covariant ArtistSortFeature? feature, @@ -473,11 +424,6 @@ class ArtistSort extends Sort { ); } - @override - ArtistSort get withDefaultOrder { - return copyWith(orderAscending: feature.defaultOrderAscending); - } - int _fallbackName(Artist a, Artist b) { return a.artist.toLowerCase().compareTo(b.artist.toLowerCase()); } diff --git a/lib/logic/player/content.dart b/lib/logic/player/content.dart index ebbfbddf2..6fca5538d 100644 --- a/lib/logic/player/content.dart +++ b/lib/logic/player/content.dart @@ -822,18 +822,8 @@ class ContentUtils { static const String dot = '•'; /// Joins list with the [dot]. - static String joinDot(List list) { - if (list.isEmpty) { - return ''; - } - var result = list.first; - for (int i = 1; i < list.length; i++) { - final string = list[i].toString(); - if (string.isNotEmpty) { - result += ' $dot $string'; - } - } - return result; + static String joinDot(List list) { + return list.where((item) => item != null && item.toString().isNotEmpty).join(' $dot '); } /// Returns a default icon for the playlist art. @@ -855,7 +845,10 @@ class ContentUtils { } /// Appends dot and year to [string]. - static String appendYearWithDot(String string, int year) { + static String appendYearWithDot(String string, int? year) { + if (year == null) { + return string; + } return '$string $dot $year'; } diff --git a/lib/widgets/content_list_view/list_header.dart b/lib/widgets/content_list_view/list_header.dart index a2a7a4b47..b91e04cb7 100644 --- a/lib/widgets/content_list_view/list_header.dart +++ b/lib/widgets/content_list_view/list_header.dart @@ -108,7 +108,7 @@ class ContentListHeader extends StatelessWidget { void _handleTap(BuildContext context) { final l10n = getl10n(context); final sort = getSort(); - ShowFunctions.instance.showRadio( + ShowFunctions.instance.showRadio>( context: context, title: l10n.sort, items: SortFeature.getValuesForContent(contentType), diff --git a/lib/widgets/content_list_view/persistent_queue_tile.dart b/lib/widgets/content_list_view/persistent_queue_tile.dart index 9add0ae7f..86ff2d745 100644 --- a/lib/widgets/content_list_view/persistent_queue_tile.dart +++ b/lib/widgets/content_list_view/persistent_queue_tile.dart @@ -231,7 +231,7 @@ class _PersistentQueueTileState if (queue is Album) { children.add(ArtistWidget( artist: queue.artist, - trailingText: queue.year.toString(), + trailingText: queue.year?.toString(), textStyle: _subtitleTheme(widget.queue.type, theme), )); } else if (queue is Playlist) { diff --git a/test/logic/models/sort_test.dart b/test/logic/models/sort_test.dart new file mode 100644 index 000000000..61c1b4a2d --- /dev/null +++ b/test/logic/models/sort_test.dart @@ -0,0 +1,185 @@ +import '../../test.dart'; + +void main() { + group('Song sorting', () { + late List songs; + setUp(() { + songs = [ + songWith(id: 0, dateModified: 0, dateAdded: 0, title: '', artist: '', album: ''), + songWith(id: 1, dateModified: 1, dateAdded: 0, title: '', artist: '', album: ''), + songWith(id: 2, dateModified: -1, dateAdded: 0, title: '', artist: '', album: ''), + songWith(id: 3, dateModified: 0, dateAdded: 1, title: '', artist: '', album: ''), + songWith(id: 4, dateModified: 0, dateAdded: -1, title: '', artist: '', album: ''), + songWith(id: 5, dateModified: 0, dateAdded: 0, title: 'A', artist: '', album: ''), + songWith(id: 6, dateModified: 0, dateAdded: 0, title: 'AA', artist: '', album: ''), + songWith(id: 7, dateModified: 0, dateAdded: 0, title: 'a', artist: '', album: ''), + songWith(id: 8, dateModified: 0, dateAdded: 0, title: 'z', artist: '', album: ''), + songWith(id: 9, dateModified: 0, dateAdded: 0, title: '', artist: 'A', album: ''), + songWith(id: 10, dateModified: 0, dateAdded: 0, title: '', artist: 'AA', album: ''), + songWith(id: 11, dateModified: 0, dateAdded: 0, title: '', artist: 'a', album: ''), + songWith(id: 12, dateModified: 0, dateAdded: 0, title: '', artist: 'z', album: ''), + songWith(id: 13, dateModified: 0, dateAdded: 0, title: '', artist: '', album: 'A'), + songWith(id: 14, dateModified: 0, dateAdded: 0, title: '', artist: '', album: 'AA'), + songWith(id: 15, dateModified: 0, dateAdded: 0, title: '', artist: '', album: 'a'), + songWith(id: 16, dateModified: 0, dateAdded: 0, title: '', artist: '', album: 'z'), + ]; + }); + final Map>> testCases = { + SortOrder.ascending: { + SongSortFeature.dateModified: [2, 0, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 5, 7, 6, 8, 1], + SongSortFeature.dateAdded: [4, 0, 1, 2, 9, 10, 11, 12, 13, 14, 15, 16, 5, 7, 6, 8, 3], + SongSortFeature.title: [2, 0, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 1, 5, 7, 6, 8], + SongSortFeature.artist: [0, 1, 2, 3, 4, 13, 14, 15, 16, 5, 7, 6, 8, 9, 11, 10, 12], + SongSortFeature.album: [0, 1, 2, 3, 4, 9, 10, 11, 12, 5, 7, 6, 8, 13, 15, 14, 16], + }, + SortOrder.descending: { + SongSortFeature.dateModified: [1, 8, 6, 5, 7, 0, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 2], + SongSortFeature.dateAdded: [3, 8, 6, 5, 7, 0, 1, 2, 9, 10, 11, 12, 13, 14, 15, 16, 4], + SongSortFeature.title: [8, 6, 5, 7, 1, 0, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 2], + SongSortFeature.artist: [12, 10, 9, 11, 8, 6, 5, 7, 0, 1, 2, 3, 4, 13, 14, 15, 16], + SongSortFeature.album: [16, 14, 13, 15, 8, 6, 5, 7, 0, 1, 2, 3, 4, 9, 10, 11, 12], + }, + }; + for (final MapEntry(key: sortOrder, value: featureTestCases) in testCases.entries) { + for (final MapEntry(key: feature, value: expectedContentList) in featureTestCases.entries) { + test('Sorts songs by ${feature.name} ${sortOrder.name}', () async { + expect( + songs.toList() + ..sort(SongSort(feature: feature, orderAscending: sortOrder == SortOrder.ascending).comparator), + expectedContentList.map((i) => songs[i]).toList(), + ); + }); + } + } + }); + + group('Album sorting', () { + late List albums; + setUp(() { + albums = [ + albumWith(id: 0, album: '', artist: '', lastYear: 0, firstYear: 0, numberOfSongs: 0), + albumWith(id: 1, album: 'A', artist: '', lastYear: 0, firstYear: 0, numberOfSongs: 0), + albumWith(id: 2, album: 'AA', artist: '', lastYear: 0, firstYear: 0, numberOfSongs: 0), + albumWith(id: 3, album: 'a', artist: '', lastYear: 0, firstYear: 0, numberOfSongs: 0), + albumWith(id: 4, album: 'z', artist: '', lastYear: 0, firstYear: 0, numberOfSongs: 0), + albumWith(id: 5, album: '', artist: 'A', lastYear: 0, firstYear: 0, numberOfSongs: 0), + albumWith(id: 6, album: '', artist: 'AA', lastYear: 0, firstYear: 0, numberOfSongs: 0), + albumWith(id: 7, album: '', artist: 'a', lastYear: 0, firstYear: 0, numberOfSongs: 0), + albumWith(id: 8, album: '', artist: 'z', lastYear: 0, firstYear: 0, numberOfSongs: 0), + albumWith(id: 9, album: '', artist: '', lastYear: 1, firstYear: 0, numberOfSongs: 0), + albumWith(id: 10, album: '', artist: '', lastYear: -1, firstYear: 0, numberOfSongs: 0), + albumWith(id: 11, album: '', artist: '', lastYear: null, firstYear: 0, numberOfSongs: 0), + albumWith(id: 12, album: '', artist: '', lastYear: null, firstYear: 1, numberOfSongs: 0), + albumWith(id: 13, album: '', artist: '', lastYear: null, firstYear: -1, numberOfSongs: 0), + albumWith(id: 14, album: '', artist: '', lastYear: null, firstYear: null, numberOfSongs: 0), + albumWith(id: 15, album: '', artist: '', lastYear: 0, firstYear: 0, numberOfSongs: 1), + albumWith(id: 16, album: '', artist: '', lastYear: 0, firstYear: 0, numberOfSongs: -1), + ]; + }); + final Map>> testCases = { + SortOrder.ascending: { + AlbumSortFeature.title: [10, 13, 0, 5, 6, 7, 8, 11, 14, 15, 16, 9, 12, 1, 3, 2, 4], + AlbumSortFeature.artist: [10, 13, 0, 1, 2, 3, 4, 11, 14, 15, 16, 9, 12, 5, 7, 6, 8], + AlbumSortFeature.year: [10, 13, 0, 5, 6, 7, 8, 11, 14, 15, 16, 1, 3, 2, 4, 9, 12], + AlbumSortFeature.numberOfSongs: [16, 10, 13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 14, 9, 12, 15], + }, + SortOrder.descending: { + AlbumSortFeature.title: [4, 2, 1, 3, 9, 12, 0, 5, 6, 7, 8, 11, 14, 15, 16, 10, 13], + AlbumSortFeature.artist: [8, 6, 5, 7, 9, 12, 0, 1, 2, 3, 4, 11, 14, 15, 16, 10, 13], + AlbumSortFeature.year: [9, 12, 4, 2, 1, 3, 0, 5, 6, 7, 8, 11, 14, 15, 16, 10, 13], + AlbumSortFeature.numberOfSongs: [15, 9, 12, 0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 14, 10, 13, 16], + }, + }; + for (final MapEntry(key: sortOrder, value: featureTestCases) in testCases.entries) { + for (final MapEntry(key: feature, value: expectedContentList) in featureTestCases.entries) { + test('Sorts albums by ${feature.name} ${sortOrder.name}', () async { + expect( + albums.toList() + ..sort(AlbumSort(feature: feature, orderAscending: sortOrder == SortOrder.ascending).comparator), + expectedContentList.map((i) => albums[i]).toList(), + ); + }); + } + } + }); + + group('Playlist sorting', () { + late List playlists; + setUp(() { + playlists = [ + playlistWith(id: 0, name: '', dateModified: 0, dateAdded: 0), + playlistWith(id: 1, name: 'A', dateModified: 0, dateAdded: 0), + playlistWith(id: 2, name: 'AA', dateModified: 0, dateAdded: 0), + playlistWith(id: 3, name: 'a', dateModified: 0, dateAdded: 0), + playlistWith(id: 4, name: 'z', dateModified: 0, dateAdded: 0), + playlistWith(id: 5, name: '', dateModified: 1, dateAdded: 0), + playlistWith(id: 6, name: '', dateModified: -1, dateAdded: 0), + playlistWith(id: 7, name: '', dateModified: 0, dateAdded: 1), + playlistWith(id: 8, name: '', dateModified: 0, dateAdded: -1), + ]; + }); + final Map>> testCases = { + SortOrder.ascending: { + PlaylistSortFeature.dateModified: [6, 0, 7, 8, 1, 3, 2, 4, 5], + PlaylistSortFeature.dateAdded: [8, 0, 5, 6, 1, 3, 2, 4, 7], + PlaylistSortFeature.name: [6, 0, 7, 8, 5, 1, 3, 2, 4], + }, + SortOrder.descending: { + PlaylistSortFeature.dateModified: [5, 4, 2, 1, 3, 0, 7, 8, 6], + PlaylistSortFeature.dateAdded: [7, 4, 2, 1, 3, 0, 5, 6, 8], + PlaylistSortFeature.name: [4, 2, 1, 3, 5, 0, 7, 8, 6], + }, + }; + for (final MapEntry(key: sortOrder, value: featureTestCases) in testCases.entries) { + for (final MapEntry(key: feature, value: expectedContentList) in featureTestCases.entries) { + test('Sorts playlists by ${feature.name} ${sortOrder.name}', () async { + expect( + playlists.toList() + ..sort(PlaylistSort(feature: feature, orderAscending: sortOrder == SortOrder.ascending).comparator), + expectedContentList.map((i) => playlists[i]).toList(), + ); + }); + } + } + }); + + group('Artist sorting', () { + late List artists; + setUp(() { + artists = [ + artistWith(id: 0, artist: '', numberOfAlbums: 0, numberOfTracks: 0), + artistWith(id: 1, artist: 'A', numberOfAlbums: 0, numberOfTracks: 0), + artistWith(id: 2, artist: 'AA', numberOfAlbums: 0, numberOfTracks: 0), + artistWith(id: 3, artist: 'a', numberOfAlbums: 0, numberOfTracks: 0), + artistWith(id: 4, artist: 'z', numberOfAlbums: 0, numberOfTracks: 0), + artistWith(id: 5, artist: '', numberOfAlbums: 1, numberOfTracks: 0), + artistWith(id: 6, artist: '', numberOfAlbums: -1, numberOfTracks: 0), + artistWith(id: 7, artist: '', numberOfAlbums: 0, numberOfTracks: 1), + artistWith(id: 8, artist: '', numberOfAlbums: 0, numberOfTracks: -1), + ]; + }); + final Map>> testCases = { + SortOrder.ascending: { + ArtistSortFeature.name: [8, 0, 5, 6, 7, 1, 3, 2, 4], + ArtistSortFeature.numberOfAlbums: [6, 0, 7, 8, 1, 3, 2, 4, 5], + ArtistSortFeature.numberOfTracks: [8, 0, 5, 6, 1, 3, 2, 4, 7], + }, + SortOrder.descending: { + ArtistSortFeature.name: [4, 2, 1, 3, 7, 0, 5, 6, 8], + ArtistSortFeature.numberOfAlbums: [5, 4, 2, 1, 3, 0, 7, 8, 6], + ArtistSortFeature.numberOfTracks: [7, 4, 2, 1, 3, 0, 5, 6, 8], + }, + }; + for (final MapEntry(key: sortOrder, value: featureTestCases) in testCases.entries) { + for (final MapEntry(key: feature, value: expectedContentList) in featureTestCases.entries) { + test('Sorts artists by ${feature.name} ${sortOrder.name}', () async { + expect( + artists.toList() + ..sort(ArtistSort(feature: feature, orderAscending: sortOrder == SortOrder.ascending).comparator), + expectedContentList.map((i) => artists[i]).toList(), + ); + }); + } + } + }); +}