mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-20 22:41:38 +02:00
154 lines
5.3 KiB
Dart
154 lines
5.3 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:spotiflac_android/models/settings.dart';
|
|
import 'package:spotiflac_android/providers/settings_provider.dart';
|
|
import 'package:spotiflac_android/utils/logger.dart';
|
|
|
|
class _Preferences implements SharedPreferences {
|
|
final writes = <Map<String, dynamic>>[];
|
|
final _started = <int, Completer<void>>{};
|
|
final _released = <int, Completer<bool>>{};
|
|
|
|
Future<void> whenStarted(int index) =>
|
|
(_started[index] ??= Completer<void>()).future;
|
|
|
|
Completer<bool> release(int index) => _released[index] ??= Completer<bool>();
|
|
|
|
@override
|
|
Future<bool> setString(String key, String value) {
|
|
expect(key, 'app_settings');
|
|
final index = writes.length;
|
|
writes.add(jsonDecode(value) as Map<String, dynamic>);
|
|
(_started[index] ??= Completer<void>()).complete();
|
|
return release(index).future;
|
|
}
|
|
|
|
@override
|
|
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
|
}
|
|
|
|
class _Settings extends SettingsNotifier {
|
|
_Settings(_Preferences preferences)
|
|
: super(preferences: () async => preferences);
|
|
|
|
_Settings.withLoader(Future<SharedPreferences> Function() load)
|
|
: super(preferences: load);
|
|
|
|
@override
|
|
AppSettings build() => const AppSettings();
|
|
}
|
|
|
|
void main() {
|
|
test('queued save callers await the complete coalescing drain', () async {
|
|
final preferences = _Preferences();
|
|
final container = ProviderContainer(
|
|
overrides: [settingsProvider.overrideWith(() => _Settings(preferences))],
|
|
);
|
|
addTearDown(container.dispose);
|
|
final notifier = container.read(settingsProvider.notifier);
|
|
final first = notifier.useAppFolderStorage('/output/first');
|
|
await preferences.whenStarted(0);
|
|
final second = notifier.setNetworkDownloadFolder('remote:first', 'First');
|
|
final third = notifier.setNetworkDownloadFolder('remote:latest', 'Latest');
|
|
var firstCompleted = false;
|
|
var secondCompleted = false;
|
|
var thirdCompleted = false;
|
|
unawaited(first.then((_) => firstCompleted = true));
|
|
unawaited(second.then((_) => secondCompleted = true));
|
|
unawaited(third.then((_) => thirdCompleted = true));
|
|
await Future<void>.delayed(Duration.zero);
|
|
expect(
|
|
[firstCompleted, secondCompleted, thirdCompleted],
|
|
[false, false, false],
|
|
);
|
|
|
|
preferences.release(0).complete(true);
|
|
await preferences.whenStarted(1);
|
|
expect(preferences.writes.length, 2);
|
|
expect(preferences.writes.last['networkDownloadFolder'], 'remote:latest');
|
|
expect(preferences.writes.last['networkDownloadLabel'], 'Latest');
|
|
expect(preferences.writes.last['downloadDirectory'], '/output/first');
|
|
expect(
|
|
[firstCompleted, secondCompleted, thirdCompleted],
|
|
[false, false, false],
|
|
);
|
|
preferences.release(1).complete(true);
|
|
await Future.wait([first, second, third]);
|
|
expect(
|
|
[firstCompleted, secondCompleted, thirdCompleted],
|
|
[true, true, true],
|
|
);
|
|
expect(preferences.writes.length, 2);
|
|
});
|
|
|
|
test(
|
|
'failed active save keeps logged-error API and releases its owner',
|
|
() async {
|
|
final preferences = _Preferences();
|
|
final container = ProviderContainer(
|
|
overrides: [
|
|
settingsProvider.overrideWith(() => _Settings(preferences)),
|
|
],
|
|
);
|
|
addTearDown(container.dispose);
|
|
final notifier = container.read(settingsProvider.notifier);
|
|
final first = notifier.useAppFolderStorage('/output/first');
|
|
await preferences.whenStarted(0);
|
|
final queued = notifier.setNetworkDownloadFolder(
|
|
'remote:queued',
|
|
'Queued',
|
|
);
|
|
preferences.release(0).completeError(StateError('settings_save_failed'));
|
|
await Future.wait([first, queued]);
|
|
expect(
|
|
LogBuffer().entries.any(
|
|
(entry) =>
|
|
entry.tag == 'SettingsProvider' &&
|
|
entry.message.contains('settings_save_failed'),
|
|
),
|
|
isTrue,
|
|
);
|
|
|
|
final retry = notifier.useAppFolderStorage('/output/retry');
|
|
await preferences.whenStarted(1);
|
|
expect(preferences.writes.last['downloadDirectory'], '/output/retry');
|
|
expect(preferences.writes.last['networkDownloadFolder'], 'remote:queued');
|
|
preferences.release(1).complete(true);
|
|
await retry;
|
|
},
|
|
);
|
|
|
|
test(
|
|
'synchronous preference failure does not retain a finished drain',
|
|
() async {
|
|
final preferences = _Preferences();
|
|
var loads = 0;
|
|
final container = ProviderContainer(
|
|
overrides: [
|
|
settingsProvider.overrideWith(
|
|
() => _Settings.withLoader(() {
|
|
if (loads++ == 0) throw StateError('settings_preferences_failed');
|
|
return Future.value(preferences);
|
|
}),
|
|
),
|
|
],
|
|
);
|
|
addTearDown(container.dispose);
|
|
final notifier = container.read(settingsProvider.notifier);
|
|
await notifier.useAppFolderStorage('/output/first');
|
|
expect(preferences.writes, isEmpty);
|
|
|
|
final retry = notifier.useAppFolderStorage('/output/retry');
|
|
await preferences.whenStarted(0);
|
|
expect(loads, 2);
|
|
expect(preferences.writes.single['downloadDirectory'], '/output/retry');
|
|
preferences.release(0).complete(true);
|
|
await retry;
|
|
},
|
|
);
|
|
}
|