[verified] Publish ReelBox source under GPLv3

This commit is contained in:
Hermes committed 2026-11-12 23:41:13 +00:00
commit db2b505ca3
445 files changed
+105274

No files matched your search

+202
View File
@@ -0,0 +1,202 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:reelbox/data/api_service.dart';
import 'package:reelbox/state/app_state.dart';
import 'package:reelbox/ui/catalog.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'api_service_test.dart' show StubAdapter, jsonBody;
Future<AppState> mountPaging(
WidgetTester tester,
ApiService api,
Widget page, {
bool settle = true,
}) async {
SharedPreferences.setMockInitialValues({});
final state = AppState(
api: api,
storage: await SharedPreferences.getInstance(),
);
addTearDown(state.dispose);
await state.initialize(discover: false);
await tester.pumpWidget(
AppScope(
state: state,
child: MaterialApp(home: page),
),
);
if (settle) await tester.pumpAndSettle();
return state;
}
Map<String, dynamic> titleRow(int id, String kind) => {
'id': id,
'type': kind,
'title': '$kind $id',
};
void main() {
testWidgets(
'failed raw page retries the same cursor without losing prior titles',
(tester) async {
final pages = <int>[];
var failed = false;
final adapter = StubAdapter((request) {
if (request.uri.path.contains('/all/')) return jsonBody([]);
final page = int.parse(request.uri.pathSegments[7]);
pages.add(page);
if (page == 1 && !failed) {
failed = true;
return jsonBody({}, status: 503);
}
return jsonBody(page < 2 ? [titleRow(30 + page, 'movie')] : []);
});
await mountPaging(
tester,
ApiService(dio: Dio()..httpClientAdapter = adapter),
const BrowsePage(initialCountry: 9),
);
// Short pages now prefetch automatically; a failure still retains its cursor.
expect(pages, [0, 1]);
expect(find.text('movie 30'), findsOneWidget);
await tester.ensureVisible(find.text('Try again'));
await tester.pumpAndSettle();
await tester.tap(find.text('Try again'));
await tester.pumpAndSettle();
expect(find.text('movie 30'), findsOneWidget);
expect(find.text('movie 31'), findsOneWidget);
expect(pages, [0, 1, 1, 2]);
},
);
testWidgets('filter changes reject stale raw pages', (tester) async {
final delayed = Completer<ResponseBody>();
addTearDown(() {
if (!delayed.isCompleted) delayed.complete(jsonBody([]));
});
var pendingRequested = false;
final adapter = StubAdapter((request) {
final path = request.uri.path;
if (path.contains('/all/')) return jsonBody([]);
if (path.contains('/movie/')) {
if (path.contains('/created/1/')) {
pendingRequested = true;
return delayed.future;
}
return jsonBody([titleRow(40, 'movie')]);
}
return jsonBody(
path.contains('/created/0/') ? [titleRow(50, 'serie')] : [],
);
});
await mountPaging(
tester,
ApiService(dio: Dio()..httpClientAdapter = adapter),
const BrowsePage(),
settle: false,
);
for (var frame = 0; frame < 24 && !pendingRequested; frame++) {
await tester.pump(const Duration(milliseconds: 16));
}
expect(pendingRequested, true);
await tester.ensureVisible(find.text('Series'));
await tester.tap(find.text('Series'));
await tester.pumpAndSettle();
delayed.complete(jsonBody([titleRow(41, 'movie')]));
await tester.pumpAndSettle();
expect(find.text('serie 50'), findsOneWidget);
expect(find.text('movie 41'), findsNothing);
expect(find.text('movie 40'), findsNothing);
});
testWidgets('duplicate-only title pages do not truncate later results', (
tester,
) async {
final pages = <int>[];
final adapter = StubAdapter((request) {
final query = request.uri.queryParameters;
if (['search-trand', 'best-rand-top'].contains(query['action'])) {
return jsonBody([]);
}
final page = int.parse(query['pageno']!);
pages.add(page);
return jsonBody(switch (page) {
1 || 2 => [titleRow(20, 'movie')],
3 => [titleRow(21, 'movie')],
_ => [],
});
});
await mountPaging(
tester,
ApiService(dio: Dio()..httpClientAdapter = adapter),
const SearchPage(),
);
await tester.enterText(find.byType(TextField).first, 'movie');
await tester.testTextInput.receiveAction(TextInputAction.search);
await tester.pumpAndSettle();
expect(find.text('movie 20'), findsOneWidget);
expect(find.text('movie 21'), findsOneWidget);
expect(pages, [1, 2, 3, 4]);
expect(find.text('Load more'), findsNothing);
});
testWidgets(
'duplicate-only actor directory pages retain server continuation',
(tester) async {
final pages = <int>[];
final adapter = StubAdapter((request) {
final page = int.parse(request.uri.queryParameters['pageno']!);
pages.add(page);
return jsonBody(switch (page) {
1 || 2 => [
{'id': 1, 'name': 'Actor one'},
],
3 => [
{'id': 2, 'name': 'Actor two'},
],
_ => [],
});
});
await mountPaging(
tester,
ApiService(dio: Dio()..httpClientAdapter = adapter),
const ActorsPage(),
);
expect(find.text('Actor one'), findsOneWidget);
expect(find.text('Actor two'), findsOneWidget);
expect(pages, [1, 2, 3, 4]);
},
);
testWidgets('country movies retain continuation across opposite-kind pages', (
tester,
) async {
final pages = <int>[];
final adapter = StubAdapter((request) {
if (request.uri.path.contains('/all/')) return jsonBody([]);
final page = int.parse(request.uri.pathSegments[7]);
pages.add(page);
return jsonBody(switch (page) {
0 => [titleRow(10, 'serie')],
1 => [titleRow(11, 'serie')],
2 => [titleRow(12, 'movie')],
_ => [],
});
});
await mountPaging(
tester,
ApiService(dio: Dio()..httpClientAdapter = adapter),
const BrowsePage(initialCountry: 9),
);
expect(find.text('No titles found'), findsNothing);
expect(find.text('movie 12'), findsOneWidget);
expect(find.text('serie 10'), findsNothing);
expect(pages, [0, 1, 2, 3]);
expect(find.text('Load more'), findsNothing);
expect(find.text('You’re all caught up'), findsOneWidget);
});
}