diff --git a/.vscode/launch.json b/.vscode/launch.json index c7b4fd2..ea45bd1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -23,6 +23,7 @@ "args": [ "--flavor", "dev", + "--coverage", ], }, { diff --git a/src/lib/domain/modules/dynamic_queue.dart b/src/lib/domain/modules/dynamic_queue.dart index a894811..bc03c81 100644 --- a/src/lib/domain/modules/dynamic_queue.dart +++ b/src/lib/domain/modules/dynamic_queue.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:collection/collection.dart'; import 'package:flutter_fimber/flutter_fimber.dart'; import 'package:rxdart/rxdart.dart'; @@ -62,25 +63,25 @@ class DynamicQueue implements ManagedQueueInfo { ) { _log.d('init'); - _availableSongs = availableSongs; // for every item in queue, take the corresponding object from availableSongs - _queue = queue - .map( - (qi) => availableSongs.firstWhere( - (e) => e == qi, - orElse: () { - _log.d('Not found in available songs: $qi'); - return qi as QueueItemModel; - }, - ), - ) - .toList(); + // discard elements from queue that are not in availableSongs (thi should not happen) + final List tmpQueue = []; + for (final qi in queue) { + final avSong = availableSongs.firstWhereOrNull((e) => e == qi); + if (avSong != null) tmpQueue.add(avSong); + else _log.w('Item not found in availableSongs: $qi'); + } + _availableSongs = availableSongs; + _queue = tmpQueue; _playableSubject.add(playable); _availableSongsSubject.add(_availableSongs); _queueSubject.add(_queue); } + /// Generates a queue from the given [songs], depending the containing [playable] and [shuffleMode]. + /// + /// Returns the index of the starting song. Future generateQueue( List songs, Playable playable, diff --git a/src/lib/test_helpers.dart b/src/lib/test_helpers.dart new file mode 100644 index 0000000..3c0d0d0 --- /dev/null +++ b/src/lib/test_helpers.dart @@ -0,0 +1,35 @@ +import 'dart:math'; + +import 'package:mucke/domain/entities/song.dart'; + +String toVarName(String text) { + final cleanedText = text.replaceAll(RegExp(r'[^A-Za-z ]'), '').trim(); + final split = cleanedText.split(' '); + String camelCase = split[0].toLowerCase(); + for (int i = 1; i < min(split.length, 3); i++) { + camelCase += split[i].substring(0, 1).toUpperCase() + split[i].substring(1); + } + return camelCase; +} + +extension ToCodeExtension on Song { + String toCode() => """final ${toVarName(album)}$trackNumber = Song( + album: '$album', + albumId: $albumId, + artist: '$artist', + blockLevel: $blockLevel, + duration: const Duration(milliseconds: ${duration.inMilliseconds}), + path: '$path', + title: '$title', + likeCount: $likeCount, + playCount: $playCount, + discNumber: $discNumber, + next: $next, + previous: $previous, + timeAdded: DateTime.fromMillisecondsSinceEpoch(${timeAdded.millisecondsSinceEpoch}), + trackNumber: $trackNumber, + albumArtPath: '$albumArtPath', + color: const $color, + year: $year, + );"""; +} \ No newline at end of file diff --git a/src/test/data/beartooth_aggressive.dart b/src/test/data/beartooth_aggressive.dart new file mode 100644 index 0000000..d82d6a1 --- /dev/null +++ b/src/test/data/beartooth_aggressive.dart @@ -0,0 +1,261 @@ +import 'dart:ui'; + +import 'package:mucke/domain/entities/album.dart'; +import 'package:mucke/domain/entities/song.dart'; + +const aggressive = Album(id: 187, title: 'Aggressive', artist: 'Beartooth'); + +final aggressive1 = Song( + album: 'Aggressive', + albumId: 187, + artist: 'Beartooth', + blockLevel: 0, + duration: const Duration(milliseconds: 247234), + path: '/storage/emulated/0/Music/Beartooth/Aggressive/01 - Aggressive.mp3', + title: 'Aggressive', + likeCount: 1, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 1, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/187', + color: const Color(0xfff3f4f4), + year: 2016, +); + +final aggressive2 = Song( + album: 'Aggressive', + albumId: 187, + artist: 'Beartooth', + blockLevel: 0, + duration: const Duration(milliseconds: 210470), + path: '/storage/emulated/0/Music/Beartooth/Aggressive/02 - Hated.mp3', + title: 'Hated', + likeCount: 1, + playCount: 2, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 2, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/187', + color: const Color(0xfff3f4f4), + year: 2016, +); + +final aggressive3 = Song( + album: 'Aggressive', + albumId: 187, + artist: 'Beartooth', + blockLevel: 0, + duration: const Duration(milliseconds: 238290), + path: '/storage/emulated/0/Music/Beartooth/Aggressive/03 - Loser.mp3', + title: 'Loser', + likeCount: 1, + playCount: 1, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 3, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/187', + color: const Color(0xfff3f4f4), + year: 2016, +); + +final aggressive4 = Song( + album: 'Aggressive', + albumId: 187, + artist: 'Beartooth', + blockLevel: 0, + duration: const Duration(milliseconds: 203970), + path: '/storage/emulated/0/Music/Beartooth/Aggressive/04 - Fair Weather Friend.mp3', + title: 'Fair Weather Friend', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 4, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/187', + color: const Color(0xfff3f4f4), + year: 2016, +); + +final aggressive5 = Song( + album: 'Aggressive', + albumId: 187, + artist: 'Beartooth', + blockLevel: 0, + duration: const Duration(milliseconds: 183872), + path: '/storage/emulated/0/Music/Beartooth/Aggressive/05 - Burnout.mp3', + title: 'Burnout', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 5, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/187', + color: const Color(0xfff3f4f4), + year: 2016, +); + +final aggressive6 = Song( + album: 'Aggressive', + albumId: 187, + artist: 'Beartooth', + blockLevel: 0, + duration: const Duration(milliseconds: 194688), + path: '/storage/emulated/0/Music/Beartooth/Aggressive/06 - Sick of Me.mp3', + title: 'Sick of Me', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 6, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/187', + color: const Color(0xfff3f4f4), + year: 2016, +); + +final aggressive7 = Song( + album: 'Aggressive', + albumId: 187, + artist: 'Beartooth', + blockLevel: 0, + duration: const Duration(milliseconds: 215436), + path: '/storage/emulated/0/Music/Beartooth/Aggressive/07 - Censored.mp3', + title: 'Censored', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 7, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/187', + color: const Color(0xfff3f4f4), + year: 2016, +); + +final aggressive8 = Song( + album: 'Aggressive', + albumId: 187, + artist: 'Beartooth', + blockLevel: 0, + duration: const Duration(milliseconds: 138060), + path: '/storage/emulated/0/Music/Beartooth/Aggressive/08 - Always Dead.mp3', + title: 'Always Dead', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 8, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/187', + color: const Color(0xfff3f4f4), + year: 2016, +); + +final aggressive9 = Song( + album: 'Aggressive', + albumId: 187, + artist: 'Beartooth', + blockLevel: 0, + duration: const Duration(milliseconds: 196326), + path: '/storage/emulated/0/Music/Beartooth/Aggressive/09 - However You Want It Said.mp3', + title: 'However You Want It Said', + likeCount: 0, + playCount: 1, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 9, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/187', + color: const Color(0xfff3f4f4), + year: 2016, +); + +final aggressive10 = Song( + album: 'Aggressive', + albumId: 187, + artist: 'Beartooth', + blockLevel: 0, + duration: const Duration(milliseconds: 207350), + path: '/storage/emulated/0/Music/Beartooth/Aggressive/10 - Find a Way.mp3', + title: 'Find a Way', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 10, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/187', + color: const Color(0xfff3f4f4), + year: 2016, +); + +final aggressive11 = Song( + album: 'Aggressive', + albumId: 187, + artist: 'Beartooth', + blockLevel: 0, + duration: const Duration(milliseconds: 223418), + path: '/storage/emulated/0/Music/Beartooth/Aggressive/11 - Rock Is Dead.mp3', + title: 'Rock Is Dead', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 11, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/187', + color: const Color(0xfff3f4f4), + year: 2016, +); + +final aggressive12 = Song( + album: 'Aggressive', + albumId: 187, + artist: 'Beartooth', + blockLevel: 0, + duration: const Duration(milliseconds: 131248), + path: '/storage/emulated/0/Music/Beartooth/Aggressive/12 - King of Anything.mp3', + title: 'King of Anything', + likeCount: 1, + playCount: 1, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 12, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/187', + color: const Color(0xfff3f4f4), + year: 2016, +); + +final aggressiveSongs = [ + aggressive1, + aggressive2, + aggressive3, + aggressive4, + aggressive5, + aggressive6, + aggressive7, + aggressive8, + aggressive9, + aggressive10, + aggressive11, + aggressive12, +]; \ No newline at end of file diff --git a/src/test/data/in_flames_clayman.dart b/src/test/data/in_flames_clayman.dart new file mode 100644 index 0000000..271ef27 --- /dev/null +++ b/src/test/data/in_flames_clayman.dart @@ -0,0 +1,324 @@ +import 'dart:ui'; + +import 'package:mucke/domain/entities/album.dart'; +import 'package:mucke/domain/entities/song.dart'; + +const clayman = Album(id: 253, title: 'Clayman', artist: 'In Flames'); + +final clayman1 = Song( + album: 'Clayman', + albumId: 253, + artist: 'In Flames', + blockLevel: 0, + duration: const Duration(milliseconds: 280826), + path: '/storage/emulated/0/Music/In Flames/Clayman/01 - Bullet Ride.mp3', + title: 'Bullet Ride', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 1, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/253', + color: const Color(0xffcd6815), + year: 2000, +); + +final clayman2 = Song( + album: 'Clayman', + albumId: 253, + artist: 'In Flames', + blockLevel: 0, + duration: const Duration(milliseconds: 247182), + path: '/storage/emulated/0/Music/In Flames/Clayman/02 - Pinball Map.mp3', + title: 'Pinball Map', + likeCount: 1, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 2, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/253', + color: const Color(0xffcd6815), + year: 2000, +); + +final clayman3 = Song( + album: 'Clayman', + albumId: 253, + artist: 'In Flames', + blockLevel: 0, + duration: const Duration(milliseconds: 293878), + path: '/storage/emulated/0/Music/In Flames/Clayman/03 - Only For The Weak.mp3', + title: 'Only For The Weak', + likeCount: 3, + playCount: 1, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 3, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/253', + color: const Color(0xffcd6815), + year: 2000, +); + +final clayman4 = Song( + album: 'Clayman', + albumId: 253, + artist: 'In Flames', + blockLevel: 0, + duration: const Duration(milliseconds: 206934), + path: '/storage/emulated/0/Music/In Flames/Clayman/04 - ...As The Future Repeats Today.mp3', + title: '...As The Future Repeats Today', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 4, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/253', + color: const Color(0xffcd6815), + year: 2000, +); + +final clayman5 = Song( + album: 'Clayman', + albumId: 253, + artist: 'In Flames', + blockLevel: 0, + duration: const Duration(milliseconds: 236574), + path: '/storage/emulated/0/Music/In Flames/Clayman/05 - Square Nothing.mp3', + title: 'Square Nothing', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 5, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/253', + color: const Color(0xffcd6815), + year: 2000, +); + +final clayman6 = Song( + album: 'Clayman', + albumId: 253, + artist: 'In Flames', + blockLevel: 0, + duration: const Duration(milliseconds: 207870), + path: '/storage/emulated/0/Music/In Flames/Clayman/06 - Clayman.mp3', + title: 'Clayman', + likeCount: 1, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 6, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/253', + color: const Color(0xffcd6815), + year: 2000, +); + +final clayman7 = Song( + album: 'Clayman', + albumId: 253, + artist: 'In Flames', + blockLevel: 0, + duration: const Duration(milliseconds: 299416), + path: '/storage/emulated/0/Music/In Flames/Clayman/07 - Satellites And Astronauts.mp3', + title: 'Satellites And Astronauts', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 7, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/253', + color: const Color(0xffcd6815), + year: 2000, +); + +final clayman8 = Song( + album: 'Clayman', + albumId: 253, + artist: 'In Flames', + blockLevel: 0, + duration: const Duration(milliseconds: 196326), + path: '/storage/emulated/0/Music/In Flames/Clayman/08 - Brush The Dust Away.mp3', + title: 'Brush The Dust Away', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 8, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/253', + color: const Color(0xffcd6815), + year: 2000, +); + +final clayman9 = Song( + album: 'Clayman', + albumId: 253, + artist: 'In Flames', + blockLevel: 0, + duration: const Duration(milliseconds: 193388), + path: '/storage/emulated/0/Music/In Flames/Clayman/09 - Swim.mp3', + title: 'Swim', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 9, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/253', + color: const Color(0xffcd6815), + year: 2000, +); + +final clayman10 = Song( + album: 'Clayman', + albumId: 253, + artist: 'In Flames', + blockLevel: 0, + duration: const Duration(milliseconds: 214968), + path: '/storage/emulated/0/Music/In Flames/Clayman/10 - Suburban Me.mp3', + title: 'Suburban Me', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 10, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/253', + color: const Color(0xffcd6815), + year: 2000, +); + +final clayman11 = Song( + album: 'Clayman', + albumId: 253, + artist: 'In Flames', + blockLevel: 0, + duration: const Duration(milliseconds: 237198), + path: '/storage/emulated/0/Music/In Flames/Clayman/11 - Another day In Quicksand.mp3', + title: 'Another day In Quicksand', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 11, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/253', + color: const Color(0xffcd6815), + year: 2000, +); + +final clayman12 = Song( + album: 'Clayman', + albumId: 253, + artist: 'In Flames', + blockLevel: 0, + duration: const Duration(milliseconds: 289978), + path: '/storage/emulated/0/Music/In Flames/Clayman/12 - Only For The Weak - Live.mp3', + title: 'Only For The Weak - Live', + likeCount: 1, + playCount: 1, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 12, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/253', + color: const Color(0xffcd6815), + year: 2000, +); + +final clayman13 = Song( + album: 'Clayman', + albumId: 253, + artist: 'In Flames', + blockLevel: 0, + duration: const Duration(milliseconds: 268840), + path: '/storage/emulated/0/Music/In Flames/Clayman/13 - Pinball Map - Live.mp3', + title: 'Pinball Map - Live', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 13, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/253', + color: const Color(0xffcd6815), + year: 2000, +); + +final clayman14 = Song( + album: 'Clayman', + albumId: 253, + artist: 'In Flames', + blockLevel: 0, + duration: const Duration(milliseconds: 155350), + path: '/storage/emulated/0/Music/In Flames/Clayman/14 - Strong And Smart.mp3', + title: 'Strong And Smart', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 14, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/253', + color: const Color(0xffcd6815), + year: 2000, +); + +final clayman15 = Song( + album: 'Clayman', + albumId: 253, + artist: 'In Flames', + blockLevel: 0, + duration: const Duration(milliseconds: 229138), + path: '/storage/emulated/0/Music/In Flames/Clayman/15 - World Of Promises.mp3', + title: 'World Of Promises', + likeCount: 0, + playCount: 0, + discNumber: 1, + next: false, + previous: false, + timeAdded: DateTime.fromMillisecondsSinceEpoch(1686000), + trackNumber: 15, + albumArtPath: '/data/user/0/rocks.mucke.dev/files/253', + color: const Color(0xffcd6815), + year: 2000, +); + +final claymanSongs = [ + clayman1, + clayman2, + clayman3, + clayman4, + clayman5, + clayman6, + clayman7, + clayman8, + clayman9, + clayman10, + clayman11, + clayman12, + clayman13, + clayman14, + clayman15, +]; diff --git a/src/test/domain/modules/dynamic_queue_test.dart b/src/test/domain/modules/dynamic_queue_test.dart index 232ef13..04d8945 100644 --- a/src/test/domain/modules/dynamic_queue_test.dart +++ b/src/test/domain/modules/dynamic_queue_test.dart @@ -87,5 +87,54 @@ void main() { expect(sut.availableSongs, [song1, song2]); }, ); + + test( + 'QueueItems missing in availableSongs get filtered out', + () async { + // arrange + final tQueue = [ + QueueItem(song1, originalIndex: 0, isAvailable: true), + QueueItem(song2, originalIndex: 4, isAvailable: true), + ]; + final tAvailableSongs = [ + QueueItem(song1, originalIndex: 0, isAvailable: true), + QueueItem(song2, originalIndex: 1, isAvailable: true), + QueueItem(song3, originalIndex: 2, isAvailable: false) + ]; + + final tPlayable = MockPlayable(); + + // act + sut.init(tQueue, tAvailableSongs, tPlayable); + + // assert + expect(sut.queueItemsStream.value, [QueueItem(song1, originalIndex: 0, isAvailable: true)]); + expect(sut.queue, [song1]); + }, + ); }); + + // group('moveQueueItem', () { + // test('basic', () { + // // arrange + // final tQueue = [ + // QueueItem(song1, originalIndex: 0, isAvailable: true), + // QueueItem(song2, originalIndex: 4, isAvailable: true), + // ]; + // final tAvailableSongs = [ + // QueueItem(song1, originalIndex: 0, isAvailable: true), + // QueueItem(song2, originalIndex: 1, isAvailable: true), + // QueueItem(song3, originalIndex: 2, isAvailable: false) + // ]; + + // final tPlayable = MockPlayable(); + + // // act + // sut.init(tQueue, tAvailableSongs, tPlayable); + + // // assert + // expect(sut.queueItemsStream.value, [QueueItem(song1, originalIndex: 0, isAvailable: true)]); + // expect(sut.queue, [song1]); + // }); + // }); }