mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-20 22:41:38 +02:00
315 lines
11 KiB
Dart
315 lines
11 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:spotiflac_android/l10n/l10n.dart';
|
|
import 'package:spotiflac_android/screens/network_storage_screen.dart';
|
|
import 'package:spotiflac_android/services/network_certificate.dart';
|
|
import 'package:spotiflac_android/services/network_storage_service.dart';
|
|
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
|
import 'package:spotiflac_android/widgets/app_action_button.dart';
|
|
|
|
void main() {
|
|
for (final olderFails in [false, true]) {
|
|
testWidgets('latest folder refresh wins (older fails: $olderFails)', (
|
|
tester,
|
|
) async {
|
|
final service = _DelayedListings();
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
child: MaterialApp(
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: NetworkStorageScreen(
|
|
service: service,
|
|
connection: const NetworkConnection(
|
|
id: 'nas',
|
|
name: 'NAS',
|
|
protocol: NetworkProtocol.webdav,
|
|
address: 'https://nas.test/music/',
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
service.requests.single.complete([
|
|
const NetworkEntry('Initial.flac', 'Initial.flac'),
|
|
]);
|
|
await tester.pumpAndSettle();
|
|
final refresh = find.byTooltip('Refresh');
|
|
// Two taps before the disabled state is painted can overlap requests.
|
|
await tester.tap(refresh);
|
|
await tester.tap(refresh);
|
|
expect(service.requests, hasLength(3));
|
|
service.requests.last.complete([
|
|
const NetworkEntry('Latest.flac', 'Latest.flac'),
|
|
]);
|
|
await tester.pumpAndSettle();
|
|
if (olderFails) {
|
|
service.requests[1].completeError(const SocketException('offline'));
|
|
} else {
|
|
service.requests[1].complete([
|
|
const NetworkEntry('Stale.flac', 'Stale.flac'),
|
|
]);
|
|
}
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Latest.flac'), findsOneWidget);
|
|
expect(find.text('Stale.flac'), findsNothing);
|
|
expect(
|
|
find.textContaining('The server could not be reached.'),
|
|
findsNothing,
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
}
|
|
|
|
testWidgets('failed sign-in stays on the form with a specific error', (
|
|
tester,
|
|
) async {
|
|
final service = _NetworkFixture()
|
|
..error = const SocketException('private-host');
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
child: MaterialApp(
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: NetworkStorageScreen(service: service),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byTooltip('Add connection'));
|
|
await tester.pumpAndSettle();
|
|
await tester.enterText(find.byKey(const ValueKey('Name')), 'NAS');
|
|
await tester.enterText(
|
|
find.byKey(const ValueKey('Server or audio URL')),
|
|
'smb://server/share',
|
|
);
|
|
final save = find.widgetWithText(AppActionButton, 'Connect and save');
|
|
await tester.ensureVisible(save);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(save);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.textContaining('The server could not be reached.'),
|
|
findsOneWidget,
|
|
);
|
|
expect(find.textContaining('private-host'), findsNothing);
|
|
expect(await service.connections(), isEmpty);
|
|
});
|
|
for (final mornye in [false, true]) {
|
|
testWidgets(
|
|
'certificate trust requires a tap and stays on its server (Mornye: $mornye)',
|
|
(tester) async {
|
|
const certificate = NetworkCertificateException(
|
|
origin: 'https://nas.test:6008',
|
|
fingerprint: 'fixture-fingerprint',
|
|
);
|
|
final service = _NetworkFixture()..error = certificate;
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
child: MaterialApp(
|
|
theme: mornye
|
|
? MornyeTheme.build(Brightness.dark)
|
|
: ThemeData(useMaterial3: true),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: NetworkStorageScreen(service: service),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byTooltip('Add connection'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('WebDAV'));
|
|
await tester.pumpAndSettle();
|
|
await tester.enterText(find.byKey(const ValueKey('Name')), 'NAS');
|
|
final address = find.byKey(const ValueKey('Server or audio URL'));
|
|
await tester.enterText(address, 'https://nas.test:6008/music');
|
|
await tester.pumpAndSettle();
|
|
final save = find.widgetWithText(AppActionButton, 'Connect and save');
|
|
await tester.ensureVisible(save);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(save);
|
|
await tester.pumpAndSettle();
|
|
expect(await service.connections(), isEmpty);
|
|
expect(find.textContaining(certificate.fingerprint), findsOneWidget);
|
|
final trust = find.widgetWithText(
|
|
AppActionButton,
|
|
'Trust certificate and retry',
|
|
);
|
|
// Editing the server after an error must not transfer the approval.
|
|
await tester.ensureVisible(address);
|
|
await tester.enterText(address, 'https://other.test:6008/music');
|
|
await tester.pumpAndSettle();
|
|
await tester.ensureVisible(trust);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(trust);
|
|
await tester.pumpAndSettle();
|
|
expect(service.lastAttempt!.trustedCertificateSha256, isNull);
|
|
expect(await service.connections(), isEmpty);
|
|
await tester.ensureVisible(address);
|
|
await tester.enterText(address, 'https://nas.test:6008/music');
|
|
await tester.pumpAndSettle();
|
|
await tester.ensureVisible(trust);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(trust);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
(await service.connections()).single.trustedCertificateSha256,
|
|
certificate.fingerprint,
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
},
|
|
);
|
|
testWidgets(
|
|
'saved networks open their own browser and retry (Mornye: $mornye)',
|
|
(tester) async {
|
|
final service = _NetworkFixture();
|
|
await service.save(
|
|
const NetworkConnection(
|
|
id: 'nas',
|
|
name: 'My NAS',
|
|
protocol: NetworkProtocol.webdav,
|
|
address: 'https://nas.test/music/',
|
|
),
|
|
);
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
child: MaterialApp(
|
|
theme: mornye
|
|
? MornyeTheme.build(Brightness.dark)
|
|
: ThemeData(useMaterial3: true),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: NetworkStorageScreen(service: service),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('My NAS'), findsOneWidget);
|
|
await tester.tap(find.text('My NAS'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Album'), findsOneWidget);
|
|
await tester.tap(find.text('Album'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Song.flac'), findsOneWidget);
|
|
expect(service.paths, ['', 'Album/']);
|
|
service.fail = true;
|
|
await tester.tap(find.byTooltip('Refresh').last);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.textContaining('Could not access the network source.'),
|
|
findsOneWidget,
|
|
);
|
|
service.fail = false;
|
|
await tester.tap(find.widgetWithText(AppActionButton, 'Retry'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Song.flac'), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
},
|
|
);
|
|
testWidgets(
|
|
'connection form saves in the selected theme (Mornye: $mornye)',
|
|
(tester) async {
|
|
tester.view.physicalSize = const Size(390, 844);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
final service = _NetworkFixture();
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
child: MaterialApp(
|
|
theme: mornye
|
|
? MornyeTheme.build(Brightness.light)
|
|
: ThemeData(useMaterial3: true),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: NetworkStorageScreen(service: service),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byTooltip('Add connection'));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.byType(CupertinoTextFormFieldRow),
|
|
mornye ? findsWidgets : findsNothing,
|
|
);
|
|
expect(
|
|
find.byType(TextFormField),
|
|
mornye ? findsNothing : findsWidgets,
|
|
);
|
|
await tester.enterText(
|
|
find.descendant(
|
|
of: find.byKey(const ValueKey('Name')),
|
|
matching: find.byType(EditableText),
|
|
),
|
|
'Home music',
|
|
);
|
|
await tester.enterText(
|
|
find.descendant(
|
|
of: find.byKey(const ValueKey('Server or audio URL')),
|
|
matching: find.byType(EditableText),
|
|
),
|
|
'smb://nas.test/Music/',
|
|
);
|
|
final save = find.widgetWithText(AppActionButton, 'Connect and save');
|
|
await tester.ensureVisible(save);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(save);
|
|
await tester.pumpAndSettle();
|
|
expect((await service.connections()).single.name, 'Home music');
|
|
expect(find.text('Home music'), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _NetworkFixture extends NetworkStorageService {
|
|
_NetworkFixture() : super(read: () async => null, write: (_) async {});
|
|
final paths = <String>[];
|
|
bool fail = false;
|
|
Object? error;
|
|
NetworkConnection? lastAttempt;
|
|
@override
|
|
Future<List<NetworkEntry>> list(
|
|
NetworkConnection c, [
|
|
String path = '',
|
|
]) async {
|
|
paths.add(path);
|
|
lastAttempt = c;
|
|
if (error case final NetworkCertificateException certificate) {
|
|
if (c.trustedCertificateSha256 != certificate.fingerprint ||
|
|
c.root.origin != certificate.origin) {
|
|
throw certificate;
|
|
}
|
|
} else if (error != null) {
|
|
throw error!;
|
|
}
|
|
if (fail) throw StateError('Offline');
|
|
return path.isEmpty
|
|
? [const NetworkEntry('Album/', 'Album', directory: true)]
|
|
: [const NetworkEntry('Album/Song.flac', 'Song.flac')];
|
|
}
|
|
}
|
|
|
|
class _DelayedListings extends NetworkStorageService {
|
|
_DelayedListings() : super(read: () async => null, write: (_) async {});
|
|
final requests = <Completer<List<NetworkEntry>>>[];
|
|
|
|
@override
|
|
Future<List<NetworkEntry>> list(
|
|
NetworkConnection connection, [
|
|
String path = '',
|
|
]) {
|
|
final request = Completer<List<NetworkEntry>>();
|
|
requests.add(request);
|
|
return request.future;
|
|
}
|
|
}
|