[verified] Publish ReelBox source under GPLv3
This commit is contained in:
commit
db2b505ca3
445 files changed
+105274
No files matched your search
@@ -0,0 +1,323 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:reelbox/data/api_service.dart';
|
||||
import 'package:reelbox/data/models.dart';
|
||||
import 'package:reelbox/app.dart';
|
||||
import 'package:reelbox/state/app_state.dart';
|
||||
import 'package:reelbox/ui/library.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class _LocalApi extends ApiService {
|
||||
@override
|
||||
Future<void> initialize({bool discover = true}) async {}
|
||||
@override
|
||||
Future<Map<String, dynamic>> home() async => {
|
||||
'genres': [],
|
||||
'actors': [],
|
||||
'countries': [],
|
||||
};
|
||||
@override
|
||||
Future<List<Map<String, dynamic>>> actors({
|
||||
int page = 1,
|
||||
String search = '',
|
||||
}) async => [];
|
||||
@override
|
||||
Future<List<Tag>> countries() async => [];
|
||||
}
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
setUp(() => SharedPreferences.setMockInitialValues({}));
|
||||
Future<AppState> state() async {
|
||||
final s = AppState(api: _LocalApi());
|
||||
await s.initialize(discover: false);
|
||||
addTearDown(s.dispose);
|
||||
return s;
|
||||
}
|
||||
|
||||
Widget host(AppState s, Widget child) => AppScope(
|
||||
state: s,
|
||||
child: MaterialApp(home: child),
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'fresh app routes through introduction before exposing navigation',
|
||||
(tester) async {
|
||||
final s = await state();
|
||||
expect(s.prefs.introCompleted, false);
|
||||
await tester.pumpWidget(MyApp(state: s));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(FirstRunIntroPage), findsOneWidget);
|
||||
expect(find.byType(NavigationBar), findsNothing);
|
||||
await tester.tap(find.byKey(const Key('intro-skip')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(s.prefs.introCompleted, true);
|
||||
expect(find.byType(FirstRunIntroPage), findsNothing);
|
||||
expect(find.byType(NavigationBar), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'counted library tabs remain readable and reachable with large text',
|
||||
(tester) async {
|
||||
tester.view.physicalSize = const Size(390, 844);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
final s = await state();
|
||||
await tester.pumpWidget(
|
||||
host(
|
||||
s,
|
||||
MediaQuery(
|
||||
data: const MediaQueryData(textScaler: TextScaler.linear(2)),
|
||||
child: const LibraryPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final label = find.text('Collections (0)');
|
||||
await tester.ensureVisible(label);
|
||||
await tester.pumpAndSettle();
|
||||
final render = tester.renderObject<RenderBox>(label);
|
||||
expect(
|
||||
render.size.width,
|
||||
greaterThanOrEqualTo(
|
||||
render.getMaxIntrinsicWidth(double.infinity) - 0.1,
|
||||
),
|
||||
);
|
||||
await tester.tap(label);
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
|
||||
for (final locale in ['en', 'fa']) {
|
||||
testWidgets(
|
||||
'More shares independent app information in $locale without an invented install link',
|
||||
(tester) async {
|
||||
const channel = MethodChannel('dev.fluttercommunity.plus/share');
|
||||
final payloads = <Map>[];
|
||||
final methods = <String>[];
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
channel,
|
||||
(call) async {
|
||||
methods.add(call.method);
|
||||
payloads.add(call.arguments as Map);
|
||||
return 'fixture.receiver';
|
||||
},
|
||||
);
|
||||
addTearDown(
|
||||
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
channel,
|
||||
null,
|
||||
),
|
||||
);
|
||||
final s = await state();
|
||||
await s.updateSettings(s.prefs.copyWith(locale: locale));
|
||||
await tester.pumpWidget(host(s, const MorePage()));
|
||||
expect(payloads, isEmpty);
|
||||
final action = find.byKey(const Key('share-reelbox'));
|
||||
await tester.scrollUntilVisible(action, 300);
|
||||
await tester.ensureVisible(action);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(action);
|
||||
await tester.pumpAndSettle();
|
||||
expect(payloads, hasLength(1));
|
||||
expect(methods, ['share']);
|
||||
expect(
|
||||
payloads.single['text'],
|
||||
locale == 'en'
|
||||
? 'Reelbox — an account-free film and series client with a private, device-local library'
|
||||
: 'Reelbox — فیلم و سریال، با کتابخانه خصوصی روی دستگاه و بدون نیاز به حساب کاربری',
|
||||
);
|
||||
expect(payloads.single['title'], 'Reelbox');
|
||||
expect(
|
||||
payloads.single['text'],
|
||||
isNot(
|
||||
matches(
|
||||
RegExp(
|
||||
r'https?://|telegram|t\.me/|ملودی|نورتون',
|
||||
caseSensitive: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
variant: TargetPlatformVariant.only(TargetPlatform.android),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets(
|
||||
'unavailable app sharing shows a safe error rather than native details',
|
||||
(tester) async {
|
||||
const channel = MethodChannel('dev.fluttercommunity.plus/share');
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(channel, (
|
||||
_,
|
||||
) async {
|
||||
throw PlatformException(
|
||||
code: 'fixture',
|
||||
message: 'private native fixture detail',
|
||||
);
|
||||
});
|
||||
addTearDown(
|
||||
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
channel,
|
||||
null,
|
||||
),
|
||||
);
|
||||
final s = await state();
|
||||
await tester.pumpWidget(host(s, const MorePage()));
|
||||
final action = find.byKey(const Key('share-reelbox'));
|
||||
await tester.scrollUntilVisible(action, 300);
|
||||
await tester.ensureVisible(action);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(action);
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find.text('Sharing is unavailable on this device'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.textContaining('private native fixture detail'),
|
||||
findsNothing,
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
variant: TargetPlatformVariant.only(TargetPlatform.android),
|
||||
);
|
||||
|
||||
testWidgets('first-run skip persists completion without touching library', (
|
||||
tester,
|
||||
) async {
|
||||
final s = await state();
|
||||
await tester.pumpWidget(host(s, const FirstRunIntroPage()));
|
||||
await tester.tap(find.byKey(const Key('intro-skip')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(s.prefs.introCompleted, true);
|
||||
});
|
||||
testWidgets('first-run finish persists completion', (tester) async {
|
||||
final s = await state();
|
||||
await tester.pumpWidget(host(s, const FirstRunIntroPage()));
|
||||
await tester.tap(find.byKey(const Key('intro-complete')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(s.prefs.introCompleted, true);
|
||||
});
|
||||
testWidgets(
|
||||
'introduction keeps a short description and local-data disclosure',
|
||||
(tester) async {
|
||||
final s = await state();
|
||||
await tester.pumpWidget(host(s, const FirstRunIntroPage()));
|
||||
expect(
|
||||
find.text(
|
||||
'Save films and series, resume watching and download for offline viewing.',
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.textContaining('No account needed'), findsOneWidget);
|
||||
expect(
|
||||
find.textContaining('external catalog and media providers'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('English'), findsOneWidget);
|
||||
expect(find.text('فارسی'), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'more exposes no removed service links and about uses runtime package info',
|
||||
(tester) async {
|
||||
final s = await state();
|
||||
PackageInfo.setMockInitialValues(
|
||||
appName: 'Reelbox',
|
||||
packageName: 'org.reelbox.app',
|
||||
version: '8.9.10',
|
||||
buildNumber: '456',
|
||||
buildSignature: '',
|
||||
);
|
||||
await tester.pumpWidget(host(s, const MorePage()));
|
||||
expect(find.text('Live channels'), findsNothing);
|
||||
expect(find.text('Public playlists'), findsNothing);
|
||||
expect(find.text('Original service support'), findsNothing);
|
||||
await tester.scrollUntilVisible(find.text('About Reelbox'), 300);
|
||||
await tester.tap(find.text('About Reelbox'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('8.9.10'), findsOneWidget);
|
||||
expect(find.textContaining('456'), findsOneWidget);
|
||||
expect(find.textContaining('org.reelbox.app'), findsOneWidget);
|
||||
for (final label in [
|
||||
'Dart runtime',
|
||||
'Playback engine',
|
||||
'Display',
|
||||
'Device platform',
|
||||
'Flutter framework',
|
||||
'Flutter engine revision',
|
||||
]) {
|
||||
expect(find.text(label), findsNothing);
|
||||
}
|
||||
expect(find.text('Open-source licenses'), findsOneWidget);
|
||||
await tester.ensureVisible(find.text('Open-source licenses'));
|
||||
await tester.tap(find.text('Open-source licenses'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(LicensePage), findsOneWidget);
|
||||
},
|
||||
);
|
||||
testWidgets(
|
||||
'custom ARGB color and opacity save exact RGB without changing background',
|
||||
(tester) async {
|
||||
final s = await state();
|
||||
await tester.pumpWidget(host(s, const SettingsPage()));
|
||||
await tester.scrollUntilVisible(find.text('Custom subtitle color'), 450);
|
||||
await tester.tap(find.text('Custom subtitle color'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.enterText(
|
||||
find.byKey(const Key('custom-color-hex')),
|
||||
'12345678',
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.tap(find.byKey(const Key('custom-color-opacity')));
|
||||
await tester.pump();
|
||||
await tester.tap(
|
||||
find.descendant(
|
||||
of: find.byType(AlertDialog),
|
||||
matching: find.text('Save'),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(s.prefs.subtitleColor & 0xFFFFFF, 0x345678);
|
||||
expect(s.prefs.subtitleColor >> 24, inInclusiveRange(1, 254));
|
||||
expect(s.prefs.subtitleBackground, 0x80000000);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'settings safely displays imported custom values and resets subtitle style only',
|
||||
(tester) async {
|
||||
final s = await state();
|
||||
await s.updateSettings(
|
||||
s.prefs.copyWith(
|
||||
subtitleFontFamily: 'My Custom Font',
|
||||
subtitleColor: 0x12123456,
|
||||
subtitleFontSize: 72,
|
||||
playbackSpeed: 1.7,
|
||||
parallelDownloads: 7,
|
||||
playerVolume: 0.33,
|
||||
),
|
||||
);
|
||||
await tester.pumpWidget(host(s, const SettingsPage()));
|
||||
await tester.scrollUntilVisible(
|
||||
find.byKey(const Key('reset-subtitle-style')),
|
||||
450,
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
await tester.tap(find.byKey(const Key('reset-subtitle-style')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(s.prefs.subtitleColor, 0xFFFFFFFF);
|
||||
expect(s.prefs.parallelDownloads, 7);
|
||||
expect(s.prefs.playbackSpeed, 1.7);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user