feat: setup for provider tests and add preferences provider test

This commit is contained in:
Kingkor Roy Tirtho 2024-09-19 21:50:45 +06:00
parent ee8406772c
commit b534e2a229
14 changed files with 320 additions and 75 deletions

View File

@ -54,7 +54,7 @@ part 'typeconverters/subtitle.dart';
],
)
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
@override
int get schemaVersion => 1;

View File

@ -111,7 +111,7 @@ class PreferencesTable extends Table {
market: Market.US,
searchMode: SearchMode.youtube,
downloadLocation: "",
localLibraryLocation: [],
localLibraryLocation: const [],
pipedInstance: "https://pipedapi.kavin.rocks",
themeMode: ThemeMode.system,
audioSource: AudioSource.youtube,

View File

@ -22,6 +22,14 @@ class SpotubeColor extends Color {
String toString() {
return "$name:$value";
}
@override
operator ==(Object other) {
return other is SpotubeColor && other.value == value && other.name == name;
}
@override
int get hashCode => Object.hashAll([value, name]);
}
final Set<SpotubeColor> colorsMap = {

View File

@ -1,9 +1,9 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/provider/tray_manager/tray_menu.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/provider/window_manager/window_manager.dart';
import 'package:spotube/utils/platform.dart';
import 'package:tray_manager/tray_manager.dart';
import 'package:window_manager/window_manager.dart';
class SystemTrayManager with TrayListener {
final Ref ref;
@ -40,7 +40,7 @@ class SystemTrayManager with TrayListener {
@override
onTrayIconMouseDown() {
if (kIsWindows) {
windowManager.show();
ref.read(windowManagerProvider).show();
} else {
trayManager.popUpContextMenu();
}
@ -49,7 +49,7 @@ class SystemTrayManager with TrayListener {
@override
onTrayIconRightMouseDown() {
if (!kIsWindows) {
windowManager.show();
ref.read(windowManagerProvider).show();
} else {
trayManager.popUpContextMenu();
}

View File

@ -2,10 +2,10 @@ import 'dart:io';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/provider/audio_player/audio_player.dart';
import 'package:spotube/provider/window_manager/window_manager.dart';
import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:media_kit/media_kit.dart' hide Track;
import 'package:tray_manager/tray_manager.dart';
import 'package:window_manager/window_manager.dart';
final audioPlayerLoopMode = StreamProvider<PlaylistMode>((ref) {
return audioPlayer.loopModeStream;
@ -19,6 +19,8 @@ final audioPlayerPlaying = StreamProvider<bool>((ref) {
});
final trayMenuProvider = Provider((ref) {
final windowManager = ref.watch(windowManagerProvider);
final playlistNotifier = ref.watch(audioPlayerProvider.notifier);
final isPlaybackPlaying =
ref.watch(audioPlayerProvider.select((s) => s.activeTrack != null));

View File

@ -0,0 +1,16 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:spotube/utils/platform.dart';
final defaultDownloadDirectoryProvider = FutureProvider<String>((ref) async {
if (kIsAndroid) return "/storage/emulated/0/Download/Spotube";
if (kIsMacOS) {
return join((await getLibraryDirectory()).path, "Caches");
}
return getDownloadsDirectory().then((dir) {
return join(dir!.path, "Spotube");
});
});

View File

@ -1,14 +1,14 @@
import 'package:drift/drift.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart';
import 'package:spotube/provider/audio_player/audio_player_streams.dart';
import 'package:spotube/provider/database/database.dart';
import 'package:spotube/provider/palette_provider.dart';
import 'package:spotube/provider/user_preferences/default_download_dir_provider.dart';
import 'package:spotube/provider/window_manager/window_manager.dart';
import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/services/logger/logger.dart';
import 'package:spotube/services/sourced_track/enums.dart';
@ -29,7 +29,9 @@ class UserPreferencesNotifier extends Notifier<PreferencesTableData> {
await db.into(db.preferencesTable).insert(
PreferencesTableCompanion.insert(
id: const Value(0),
downloadLocation: Value(await _getDefaultDownloadDirectory()),
downloadLocation: Value(
await ref.read(defaultDownloadDirectoryProvider.future),
),
),
);
}
@ -46,11 +48,11 @@ class UserPreferencesNotifier extends Notifier<PreferencesTableData> {
state = event;
if (kIsDesktop) {
await windowManager.setTitleBarStyle(
state.systemTitleBar
? TitleBarStyle.normal
: TitleBarStyle.hidden,
);
await ref.read(windowManagerProvider).setTitleBarStyle(
state.systemTitleBar
? TitleBarStyle.normal
: TitleBarStyle.hidden,
);
}
await audioPlayer.setAudioNormalization(state.normalizeAudio);
@ -67,18 +69,6 @@ class UserPreferencesNotifier extends Notifier<PreferencesTableData> {
return PreferencesTable.defaults();
}
Future<String> _getDefaultDownloadDirectory() async {
if (kIsAndroid) return "/storage/emulated/0/Download/Spotube";
if (kIsMacOS) {
return join((await getLibraryDirectory()).path, "Caches");
}
return getDownloadsDirectory().then((dir) {
return join(dir!.path, "Spotube");
});
}
Future<void> setData(PreferencesTableCompanion data) async {
final db = ref.read(databaseProvider);
@ -95,28 +85,28 @@ class UserPreferencesNotifier extends Notifier<PreferencesTableData> {
await query.replace(PreferencesTableCompanion.insert());
}
void setStreamMusicCodec(SourceCodecs codec) {
setData(PreferencesTableCompanion(streamMusicCodec: Value(codec)));
Future<void> setStreamMusicCodec(SourceCodecs codec) async {
await setData(PreferencesTableCompanion(streamMusicCodec: Value(codec)));
}
void setDownloadMusicCodec(SourceCodecs codec) {
setData(PreferencesTableCompanion(downloadMusicCodec: Value(codec)));
Future<void> setDownloadMusicCodec(SourceCodecs codec) async {
await setData(PreferencesTableCompanion(downloadMusicCodec: Value(codec)));
}
void setThemeMode(ThemeMode mode) {
setData(PreferencesTableCompanion(themeMode: Value(mode)));
Future<void> setThemeMode(ThemeMode mode) async {
await setData(PreferencesTableCompanion(themeMode: Value(mode)));
}
void setRecommendationMarket(Market country) {
setData(PreferencesTableCompanion(market: Value(country)));
Future<void> setRecommendationMarket(Market country) async {
await setData(PreferencesTableCompanion(market: Value(country)));
}
void setAccentColorScheme(SpotubeColor color) {
setData(PreferencesTableCompanion(accentColorScheme: Value(color)));
Future<void> setAccentColorScheme(SpotubeColor color) async {
await setData(PreferencesTableCompanion(accentColorScheme: Value(color)));
}
void setAlbumColorSync(bool sync) {
setData(PreferencesTableCompanion(albumColorSync: Value(sync)));
Future<void> setAlbumColorSync(bool sync) async {
await setData(PreferencesTableCompanion(albumColorSync: Value(sync)));
if (!sync) {
ref.read(paletteProvider.notifier).state = null;
@ -125,87 +115,88 @@ class UserPreferencesNotifier extends Notifier<PreferencesTableData> {
}
}
void setCheckUpdate(bool check) {
setData(PreferencesTableCompanion(checkUpdate: Value(check)));
Future<void> setCheckUpdate(bool check) async {
await setData(PreferencesTableCompanion(checkUpdate: Value(check)));
}
void setAudioQuality(SourceQualities quality) {
setData(PreferencesTableCompanion(audioQuality: Value(quality)));
Future<void> setAudioQuality(SourceQualities quality) async {
await setData(PreferencesTableCompanion(audioQuality: Value(quality)));
}
void setDownloadLocation(String downloadDir) {
Future<void> setDownloadLocation(String downloadDir) async {
if (downloadDir.isEmpty) return;
setData(PreferencesTableCompanion(downloadLocation: Value(downloadDir)));
await setData(
PreferencesTableCompanion(downloadLocation: Value(downloadDir)));
}
void setLocalLibraryLocation(List<String> localLibraryDirs) {
//if (localLibraryDir.isEmpty) return;
setData(
Future<void> setLocalLibraryLocation(List<String> localLibraryDirs) async {
await setData(
PreferencesTableCompanion(
localLibraryLocation: Value(localLibraryDirs),
),
);
}
void setLayoutMode(LayoutMode mode) {
setData(PreferencesTableCompanion(layoutMode: Value(mode)));
Future<void> setLayoutMode(LayoutMode mode) async {
await setData(PreferencesTableCompanion(layoutMode: Value(mode)));
}
void setCloseBehavior(CloseBehavior behavior) {
setData(PreferencesTableCompanion(closeBehavior: Value(behavior)));
Future<void> setCloseBehavior(CloseBehavior behavior) async {
await setData(PreferencesTableCompanion(closeBehavior: Value(behavior)));
}
void setShowSystemTrayIcon(bool show) {
setData(PreferencesTableCompanion(showSystemTrayIcon: Value(show)));
Future<void> setShowSystemTrayIcon(bool show) async {
await setData(PreferencesTableCompanion(showSystemTrayIcon: Value(show)));
}
void setLocale(Locale locale) {
setData(PreferencesTableCompanion(locale: Value(locale)));
Future<void> setLocale(Locale locale) async {
await setData(PreferencesTableCompanion(locale: Value(locale)));
}
void setPipedInstance(String instance) {
setData(PreferencesTableCompanion(pipedInstance: Value(instance)));
Future<void> setPipedInstance(String instance) async {
await setData(PreferencesTableCompanion(pipedInstance: Value(instance)));
}
void setSearchMode(SearchMode mode) {
setData(PreferencesTableCompanion(searchMode: Value(mode)));
Future<void> setSearchMode(SearchMode mode) async {
await setData(PreferencesTableCompanion(searchMode: Value(mode)));
}
void setSkipNonMusic(bool skip) {
setData(PreferencesTableCompanion(skipNonMusic: Value(skip)));
Future<void> setSkipNonMusic(bool skip) async {
await setData(PreferencesTableCompanion(skipNonMusic: Value(skip)));
}
void setAudioSource(AudioSource type) {
setData(PreferencesTableCompanion(audioSource: Value(type)));
Future<void> setAudioSource(AudioSource type) async {
await setData(PreferencesTableCompanion(audioSource: Value(type)));
}
void setSystemTitleBar(bool isSystemTitleBar) {
setData(
Future<void> setSystemTitleBar(bool isSystemTitleBar) async {
await setData(
PreferencesTableCompanion(
systemTitleBar: Value(isSystemTitleBar),
),
);
}
void setDiscordPresence(bool discordPresence) {
setData(PreferencesTableCompanion(discordPresence: Value(discordPresence)));
Future<void> setDiscordPresence(bool discordPresence) async {
await setData(
PreferencesTableCompanion(discordPresence: Value(discordPresence)));
}
void setAmoledDarkTheme(bool isAmoled) {
setData(PreferencesTableCompanion(amoledDarkTheme: Value(isAmoled)));
Future<void> setAmoledDarkTheme(bool isAmoled) async {
await setData(PreferencesTableCompanion(amoledDarkTheme: Value(isAmoled)));
}
void setNormalizeAudio(bool normalize) {
setData(PreferencesTableCompanion(normalizeAudio: Value(normalize)));
Future<void> setNormalizeAudio(bool normalize) async {
await setData(PreferencesTableCompanion(normalizeAudio: Value(normalize)));
audioPlayer.setAudioNormalization(normalize);
}
void setEndlessPlayback(bool endless) {
setData(PreferencesTableCompanion(endlessPlayback: Value(endless)));
Future<void> setEndlessPlayback(bool endless) async {
await setData(PreferencesTableCompanion(endlessPlayback: Value(endless)));
}
void setEnableConnect(bool enable) {
setData(PreferencesTableCompanion(enableConnect: Value(enable)));
Future<void> setEnableConnect(bool enable) async {
await setData(PreferencesTableCompanion(enableConnect: Value(enable)));
}
}

View File

@ -0,0 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:window_manager/window_manager.dart';
final windowManagerProvider = Provider((ref) => windowManager);

View File

@ -369,6 +369,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.1"
coverage:
dependency: transitive
description:
name: coverage
sha256: c1fb2dce3c0085f39dc72668e85f8e0210ec7de05345821ff58530567df345a5
url: "https://pub.dev"
source: hosted
version: "1.9.2"
cross_file:
dependency: transitive
description:
@ -1432,6 +1440,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.5"
mocktail:
dependency: "direct dev"
description:
name: mocktail
sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
node_preamble:
dependency: transitive
description:
name: node_preamble
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
oauth2:
dependency: transitive
description:
@ -1849,6 +1873,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.1"
shelf_packages_handler:
dependency: transitive
description:
name: shelf_packages_handler
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
shelf_router:
dependency: "direct main"
description:
@ -1857,6 +1889,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.4"
shelf_static:
dependency: transitive
description:
name: shelf_static
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
url: "https://pub.dev"
source: hosted
version: "1.1.3"
shelf_web_socket:
dependency: "direct main"
description:
@ -1935,6 +1975,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.4"
source_map_stack_trace:
dependency: transitive
description:
name: source_map_stack_trace
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
url: "https://pub.dev"
source: hosted
version: "2.1.2"
source_maps:
dependency: transitive
description:
name: source_maps
sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
url: "https://pub.dev"
source: hosted
version: "0.10.12"
source_span:
dependency: transitive
description:
@ -2087,6 +2143,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test:
dependency: "direct dev"
description:
name: test
sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073"
url: "https://pub.dev"
source: hosted
version: "1.25.2"
test_api:
dependency: transitive
description:
@ -2095,6 +2159,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.0"
test_core:
dependency: transitive
description:
name: test_core
sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
time:
dependency: transitive
description:
@ -2327,6 +2399,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.3"
webkit_inspection_protocol:
dependency: transitive
description:
name: webkit_inspection_protocol
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
wikipedia_api:
dependency: "direct main"
description:

View File

@ -158,6 +158,8 @@ dev_dependencies:
xml: ^6.5.0
io: ^1.0.4
drift_dev: ^2.18.0
test: ^1.25.2
mocktail: ^1.0.4
dependency_overrides:
uuid: ^4.4.0

View File

@ -0,0 +1,22 @@
import 'package:riverpod/riverpod.dart';
import 'package:test/test.dart';
/// A testing utility which creates a [ProviderContainer] and automatically
/// disposes it at the end of the test.
ProviderContainer createContainer({
ProviderContainer? parent,
List<Override> overrides = const [],
List<ProviderObserver>? observers,
}) {
// Create a ProviderContainer, and optionally allow specifying parameters.
final container = ProviderContainer(
parent: parent,
overrides: overrides,
observers: observers,
);
// When the test ends, dispose the container.
addTearDown(container.dispose);
return container;
}

View File

@ -0,0 +1,5 @@
import 'package:mocktail/mocktail.dart';
import 'package:spotube/provider/audio_player/audio_player_streams.dart';
class MockAudioPlayerStreamListeners extends Mock
implements AudioPlayerStreamListeners {}

View File

@ -0,0 +1,4 @@
import 'package:mocktail/mocktail.dart';
import 'package:window_manager/window_manager.dart';
class MockWindowManager extends Mock implements WindowManager {}

View File

@ -0,0 +1,111 @@
import 'dart:async';
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:mocktail/mocktail.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/provider/audio_player/audio_player_streams.dart';
import 'package:spotube/provider/database/database.dart';
import 'package:spotube/provider/user_preferences/default_download_dir_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/provider/window_manager/window_manager.dart';
import 'package:spotube/services/logger/logger.dart';
import 'package:window_manager/window_manager.dart';
import '../create_container.dart';
import '../mocks/audio_player_listeners_mock.dart';
import '../mocks/window_manager_mock.dart';
List<Override> _createDefaultOverrides() => [
databaseProvider.overrideWith(
(ref) {
final database = AppDatabase(NativeDatabase.memory());
ref.onDispose(database.close);
return database;
},
),
audioPlayerStreamListenersProvider.overrideWith(
(ref) {
final streamListeners = MockAudioPlayerStreamListeners();
when(() => streamListeners.updatePalette()).thenReturn(
Future.value(),
);
return streamListeners;
},
),
defaultDownloadDirectoryProvider.overrideWith(
(ref) {
return Future.value("/storage/emulated/0/Download/Spotube");
},
)
];
void main() {
group('UserPreferences', () {
setUpAll(() {
registerFallbackValue(TitleBarStyle.normal);
AppLogger.initialize(false);
});
test('Initial value should be equal the default values', () {
final ref = createContainer(overrides: _createDefaultOverrides());
final preferences = ref.read(userPreferencesProvider);
final defaultPreferences = PreferencesTable.defaults();
expect(preferences, defaultPreferences);
});
test('[setSystemTitleBar] should update UI titlebar', () async {
TestWidgetsFlutterBinding.ensureInitialized();
final ref = createContainer(overrides: [
..._createDefaultOverrides(),
windowManagerProvider.overrideWith(
(ref) {
final mockWindowManager = MockWindowManager();
when(() => mockWindowManager.setTitleBarStyle(any()))
.thenAnswer((_) => Future.value());
return mockWindowManager;
},
)
]);
final db = ref.read(databaseProvider);
final preferences = ref.read(userPreferencesProvider);
await Future.delayed(const Duration(milliseconds: 300));
final preferencesNotifier = ref.read(userPreferencesProvider.notifier);
expect(preferences.systemTitleBar, false);
await preferencesNotifier.setSystemTitleBar(true);
final completer = Completer<bool>();
final subscription = (db.select(db.preferencesTable)
..where((tbl) => tbl.id.equals(0)))
.watchSingle()
.listen((event) {
completer.complete(event.systemTitleBar);
});
addTearDown(() {
subscription.cancel();
});
final systemTitleBar = await completer.future;
expect(systemTitleBar, true);
verify(
() => ref
.read(windowManagerProvider)
.setTitleBarStyle(TitleBarStyle.normal),
).called(1);
});
});
}