diff --git a/lib/components/track_tile/track_options.dart b/lib/components/track_tile/track_options.dart index 949fcc8b..4112ba15 100644 --- a/lib/components/track_tile/track_options.dart +++ b/lib/components/track_tile/track_options.dart @@ -25,7 +25,7 @@ import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/local_track.dart'; import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/blacklist_provider.dart'; +import 'package:spotube/provider/blacklist/blacklist_provider.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/local_tracks/local_tracks_provider.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; diff --git a/lib/components/track_tile/track_tile.dart b/lib/components/track_tile/track_tile.dart index f47980cd..69d0de66 100644 --- a/lib/components/track_tile/track_tile.dart +++ b/lib/components/track_tile/track_tile.dart @@ -23,7 +23,7 @@ import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/local_track.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/audio_player/state.dart'; -import 'package:spotube/provider/blacklist_provider.dart'; +import 'package:spotube/provider/blacklist/blacklist_provider.dart'; import 'package:spotube/utils/platform.dart'; class TrackTile extends HookConsumerWidget { diff --git a/lib/main.dart b/lib/main.dart index 9a862568..5c51feb3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -65,12 +65,12 @@ Future main(List rawArgs) async { AppLogger.runZoned(() async { final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); + MediaKit.ensureInitialized(); - getIt.registerSingleton(await SharedPreferences.getInstance()); - getIt.registerSingletonWithDependencies( - () => KVStoreService.init(), - dependsOn: [SharedPreferences], + getIt.registerSingleton( + await SharedPreferences.getInstance(), ); + getIt.registerSingleton(KVStoreService.init()); getIt.registerLazySingleton(() => AppDatabase()); getIt.registerSingleton(SpotubeAudioPlayer()); getIt.registerSingleton(windowManager); @@ -81,8 +81,6 @@ Future main(List rawArgs) async { FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); - MediaKit.ensureInitialized(); - await migrateMacOsFromSandboxToNoSandbox(); // force High Refresh Rate on some Android devices (like One Plus) diff --git a/lib/modules/artist/artist_card.dart b/lib/modules/artist/artist_card.dart index e53070ef..93ecc1cf 100644 --- a/lib/modules/artist/artist_card.dart +++ b/lib/modules/artist/artist_card.dart @@ -10,7 +10,7 @@ import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; -import 'package:spotube/provider/blacklist_provider.dart'; +import 'package:spotube/provider/blacklist/blacklist_provider.dart'; class ArtistCard extends HookConsumerWidget { final Artist artist; diff --git a/lib/pages/artist/section/header.dart b/lib/pages/artist/section/header.dart index 8a91f257..721a1aa1 100644 --- a/lib/pages/artist/section/header.dart +++ b/lib/pages/artist/section/header.dart @@ -11,7 +11,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/blacklist_provider.dart'; +import 'package:spotube/provider/blacklist/blacklist_provider.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/utils/primitive_utils.dart'; diff --git a/lib/pages/settings/blacklist.dart b/lib/pages/settings/blacklist.dart index 8ac2c1b9..1463a569 100644 --- a/lib/pages/settings/blacklist.dart +++ b/lib/pages/settings/blacklist.dart @@ -10,7 +10,7 @@ import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/provider/blacklist_provider.dart'; +import 'package:spotube/provider/blacklist/blacklist_provider.dart'; import 'package:auto_route/auto_route.dart'; @RoutePage() diff --git a/lib/provider/audio_player/audio_player.dart b/lib/provider/audio_player/audio_player.dart index f7292653..dfeb08d5 100644 --- a/lib/provider/audio_player/audio_player.dart +++ b/lib/provider/audio_player/audio_player.dart @@ -10,7 +10,7 @@ import 'package:spotube/extensions/track.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/local_track.dart'; import 'package:spotube/provider/audio_player/state.dart'; -import 'package:spotube/provider/blacklist_provider.dart'; +import 'package:spotube/provider/blacklist/blacklist_provider.dart'; import 'package:spotube/provider/discord_provider.dart'; import 'package:spotube/provider/server/sourced_track.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; diff --git a/lib/provider/blacklist_provider.dart b/lib/provider/blacklist/blacklist_provider.dart similarity index 100% rename from lib/provider/blacklist_provider.dart rename to lib/provider/blacklist/blacklist_provider.dart diff --git a/test/drift/app_db/migration_test.dart b/test/drift/app_db/migration_test.dart deleted file mode 100644 index a89789bb..00000000 --- a/test/drift/app_db/migration_test.dart +++ /dev/null @@ -1,128 +0,0 @@ -// ignore_for_file: unused_local_variable, unused_import -import 'package:drift/drift.dart'; -import 'package:drift_dev/api/migrations.dart'; -import 'package:spotube/models/database/database.dart'; -import 'package:test/test.dart'; -import 'generated/schema.dart'; - -import 'generated/schema_v1.dart' as v1; -import 'generated/schema_v2.dart' as v2; - -void main() { - driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; - late SchemaVerifier verifier; - - setUpAll(() { - verifier = SchemaVerifier(GeneratedHelper()); - }); - - group('simple database migrations', () { - // These simple tests verify all possible schema updates with a simple (no - // data) migration. This is a quick way to ensure that written database - // migrations properly alter the schema. - const versions = GeneratedHelper.versions; - for (final (i, fromVersion) in versions.indexed) { - group('from $fromVersion', () { - for (final toVersion in versions.skip(i + 1)) { - test('to $toVersion', () async { - final schema = await verifier.schemaAt(fromVersion); - final db = AppDatabase(schema.newConnection()); - await verifier.migrateAndValidate(db, toVersion); - await db.close(); - }); - } - }); - } - }); - - // Simple tests ensure the schema is transformed correctly, but some - // migrations benefit from a test verifying that data is transformed correctly - // too. This is particularly true for migrations that change existing columns - // (e.g. altering their type or constraints). Migrations that only add tables - // or columns typically don't need these advanced tests. - // TODO: Check whether you have migrations that could benefit from these tests - // and adapt this example to your database if necessary: - test("migration from v1 to v2 does not corrupt data", () async { - // Add data to insert into the old database, and the expected rows after the - // migration. - final oldAuthenticationTableData = []; - final expectedNewAuthenticationTableData = []; - - final oldBlacklistTableData = []; - final expectedNewBlacklistTableData = []; - - final oldPreferencesTableData = []; - final expectedNewPreferencesTableData = []; - - final oldScrobblerTableData = []; - final expectedNewScrobblerTableData = []; - - final oldSkipSegmentTableData = []; - final expectedNewSkipSegmentTableData = []; - - final oldSourceMatchTableData = []; - final expectedNewSourceMatchTableData = []; - - final oldAudioPlayerStateTableData = []; - final expectedNewAudioPlayerStateTableData = - []; - - final oldPlaylistTableData = []; - final expectedNewPlaylistTableData = []; - - final oldPlaylistMediaTableData = []; - final expectedNewPlaylistMediaTableData = []; - - final oldHistoryTableData = []; - final expectedNewHistoryTableData = []; - - final oldLyricsTableData = []; - final expectedNewLyricsTableData = []; - - await verifier.testWithDataIntegrity( - oldVersion: 1, - newVersion: 2, - createOld: v1.DatabaseAtV1.new, - createNew: v2.DatabaseAtV2.new, - openTestedDatabase: (x) => AppDatabase(), - createItems: (batch, oldDb) { - batch.insertAll(oldDb.authenticationTable, oldAuthenticationTableData); - batch.insertAll(oldDb.blacklistTable, oldBlacklistTableData); - batch.insertAll(oldDb.preferencesTable, oldPreferencesTableData); - batch.insertAll(oldDb.scrobblerTable, oldScrobblerTableData); - batch.insertAll(oldDb.skipSegmentTable, oldSkipSegmentTableData); - batch.insertAll(oldDb.sourceMatchTable, oldSourceMatchTableData); - batch.insertAll( - oldDb.audioPlayerStateTable, oldAudioPlayerStateTableData); - batch.insertAll(oldDb.playlistTable, oldPlaylistTableData); - batch.insertAll(oldDb.playlistMediaTable, oldPlaylistMediaTableData); - batch.insertAll(oldDb.historyTable, oldHistoryTableData); - batch.insertAll(oldDb.lyricsTable, oldLyricsTableData); - }, - validateItems: (newDb) async { - expect(expectedNewAuthenticationTableData, - await newDb.select(newDb.authenticationTable).get()); - expect(expectedNewBlacklistTableData, - await newDb.select(newDb.blacklistTable).get()); - expect(expectedNewPreferencesTableData, - await newDb.select(newDb.preferencesTable).get()); - expect(expectedNewScrobblerTableData, - await newDb.select(newDb.scrobblerTable).get()); - expect(expectedNewSkipSegmentTableData, - await newDb.select(newDb.skipSegmentTable).get()); - expect(expectedNewSourceMatchTableData, - await newDb.select(newDb.sourceMatchTable).get()); - expect(expectedNewAudioPlayerStateTableData, - await newDb.select(newDb.audioPlayerStateTable).get()); - expect(expectedNewPlaylistTableData, - await newDb.select(newDb.playlistTable).get()); - expect(expectedNewPlaylistMediaTableData, - await newDb.select(newDb.playlistMediaTable).get()); - expect(expectedNewHistoryTableData, - await newDb.select(newDb.historyTable).get()); - expect(expectedNewLyricsTableData, - await newDb.select(newDb.lyricsTable).get()); - }, - ); - }); -} diff --git a/test/providers/blacklist/blacklist_provider_test.dart b/test/providers/blacklist/blacklist_provider_test.dart new file mode 100644 index 00000000..413b1f85 --- /dev/null +++ b/test/providers/blacklist/blacklist_provider_test.dart @@ -0,0 +1,250 @@ +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:riverpod/riverpod.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/fake.dart'; +import 'package:spotube/collections/vars.dart'; +import 'package:spotube/models/current_playlist.dart'; +import 'package:spotube/models/database/database.dart'; +import 'package:spotube/provider/blacklist/blacklist_provider.dart'; + +import '../create_container.dart'; + +void main() { + group('BlacklistProvider', () { + late ProviderContainer container; + + setUp(() { + getIt.registerLazySingleton(() { + final database = AppDatabase(NativeDatabase.memory()); + + addTearDown(() { + database.close(); + }); + + return database; + }); + + container = createContainer(); + }); + + tearDown(() { + getIt.reset(); + }); + + test('initially should return empty list', () async { + final blackList = container.read(blacklistProvider.future); + + await expectLater(blackList, completion(isEmpty)); + }); + + test('add should add item to blacklist', () async { + final blacklistRef = + container.listen(blacklistProvider.future, (_, __) {}); + + await expectLater(blacklistRef.read(), completion(isEmpty)); + + final item = BlacklistTableCompanion.insert( + id: const Value(20), + name: 'Test', + elementId: 'test', + elementType: BlacklistedType.track, + ); + + final res = container.read(blacklistProvider.notifier).add(item); + + await expectLater(res, completes); + + await Future.delayed(const Duration(milliseconds: 100)); + + await expectLater(blacklistRef.read(), completion(isNotEmpty)); + + await expectLater( + blacklistRef.read(), + completion( + contains( + predicate( + (e) => + e.name == 'Test' && + e.elementId == 'test' && + e.elementType == BlacklistedType.track, + ), + ), + ), + ); + }); + + test('remove should remove item from blacklist', () async { + final blacklistRef = + container.listen(blacklistProvider.future, (_, __) {}); + + await expectLater(blacklistRef.read(), completion(isEmpty)); + + final item = BlacklistTableCompanion.insert( + id: const Value(20), + name: 'Test', + elementId: 'test', + elementType: BlacklistedType.track, + ); + + final res = container.read(blacklistProvider.notifier).add(item); + + await expectLater(res, completes); + + await Future.delayed(const Duration(milliseconds: 100)); + + await expectLater(blacklistRef.read(), completion(isNotEmpty)); + + final removeRes = container + .read(blacklistProvider.notifier) + .remove(item.elementId.value); + + await expectLater(removeRes, completes); + + await Future.delayed(const Duration(milliseconds: 100)); + + await expectLater(blacklistRef.read(), completion(isEmpty)); + }); + + group('contains', () { + test('should be true if track exists', () async { + final item = BlacklistTableCompanion.insert( + id: const Value(20), + name: 'Test', + elementId: FakeData.track.id!, + elementType: BlacklistedType.track, + ); + + final res = container.read(blacklistProvider.notifier).add(item); + + await expectLater(res, completes); + await Future.delayed(const Duration(milliseconds: 100)); + + final track = FakeData.track as TrackSimple; + + final contains = + container.read(blacklistProvider.notifier).contains(track); + + expect(contains, isTrue); + }); + + test('should be true if track does not exist but artist of track exists', + () async { + final item = BlacklistTableCompanion.insert( + id: const Value(20), + name: 'Test', + elementId: FakeData.track.artists!.first.id!, + elementType: BlacklistedType.artist, + ); + + final res = container.read(blacklistProvider.notifier).add(item); + + await expectLater(res, completes); + await Future.delayed(const Duration(milliseconds: 100)); + + final contains = + container.read(blacklistProvider.notifier).contains(FakeData.track); + + expect(contains, isTrue); + }); + }); + + group('containsArtist', () { + test('should be true for artist that exists', () async { + final item = BlacklistTableCompanion.insert( + id: const Value(20), + name: 'Test', + elementId: FakeData.artist.id!, + elementType: BlacklistedType.artist, + ); + + final res = container.read(blacklistProvider.notifier).add(item); + + await expectLater(res, completes); + + await Future.delayed(const Duration(milliseconds: 100)); + + expect( + container + .read(blacklistProvider.notifier) + .containsArtist(FakeData.artist), + isTrue); + }); + + test('should be false for artist that is not blacklisted', () async { + await expectLater(container.read(blacklistProvider.future), completes); + + expect( + container + .read(blacklistProvider.notifier) + .containsArtist(FakeData.artist), + isFalse, + ); + }); + }); + + group('filter', () { + test('should return non-blacklisted tracks only', () async { + final tracks = List.generate( + 10, + (e) => Track.fromJson({ + ...FakeData.track.toJson(), + 'id': 'test-$e', + }), + ); + + final blacklist = container.read(blacklistProvider.future); + + await expectLater(blacklist, completion(isEmpty)); + + final item = BlacklistTableCompanion.insert( + id: const Value(20), + name: 'Test', + elementId: tracks.first.id!, + elementType: BlacklistedType.track, + ); + final res = container.read(blacklistProvider.notifier).add(item); + await expectLater(res, completes); + + await Future.delayed(const Duration(milliseconds: 100)); + + final filteredTracks = + container.read(blacklistProvider.notifier).filter(tracks); + + expect(filteredTracks, isNotEmpty); + expect(filteredTracks.length, 9); + expect( + filteredTracks, + isNot(contains( + predicate( + (e) => e.id == tracks.first.id, + ), + )), + ); + }); + }); + + test('filterPlaylist should not modify anything but tracks', () async { + final playlist = CurrentPlaylist( + id: "lol", + name: "name", + thumbnail: "thumbnail", + tracks: [], + ); + + final blacklist = container.read(blacklistProvider.future); + + await Future.delayed(const Duration(milliseconds: 100)); + + await expectLater(blacklist, completion(isEmpty)); + + final res = + container.read(blacklistProvider.notifier).filterPlaylist(playlist); + + expect(res.id, playlist.id); + expect(res.name, playlist.name); + expect(res.thumbnail, playlist.thumbnail); + }); + }); +} diff --git a/test/providers/user_preferences/user_preferences_test.dart b/test/providers/user_preferences/user_preferences_test.dart index ee477c7a..69c399b2 100644 --- a/test/providers/user_preferences/user_preferences_test.dart +++ b/test/providers/user_preferences/user_preferences_test.dart @@ -32,7 +32,7 @@ void main() { getIt.registerSingleton(MockSpotubeAudioPlayer()); getIt.registerSingleton(MockKVStoreService()); getIt.registerSingleton(mockWindowManager); - getIt.registerLazySingleton( + getIt.registerLazySingleton( () { final database = AppDatabase(NativeDatabase.memory()); @@ -69,8 +69,12 @@ void main() { getIt.reset(); }); - test('Initial value should be equal the default values', () { + test('Initial value should be equal the default values', () async { + when(() => audioPlayer.setAudioNormalization(any())) + .thenAnswer((_) async {}); + final preferences = container.read(userPreferencesProvider); + await Future.delayed(const Duration(milliseconds: 500)); final defaultPreferences = PreferencesTable.defaults(); expect(preferences, defaultPreferences); diff --git a/test/widget_test.dart b/test/widget_test.dart index 9ad0195a..9cec8ab0 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -26,5 +26,5 @@ void main() { // Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); - }); + }, skip: true); }