mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
Introduce a headless `flutter test` suite (83 tests) covering models, utilities, every API endpoint, and the five screens, with shared helpers and fixtures so new tests stay terse. API endpoint methods are statically-dispatched extensions on the BackendAPI singleton, so they cannot be mocked via `implements`. Mock at the Dio HTTP-adapter layer (http_mock_adapter) instead, enabled by two small @visibleForTesting seams: BackendAPI.dioForTesting swaps the singleton's Dio, and BackendReachability.forceOnlineForTesting() forces a deterministic online state so polling widgets load. Add frontend/run_tests.sh and document it in CLAUDE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
61 lines
2.2 KiB
Dart
61 lines
2.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:frontend/main.dart';
|
|
import 'package:frontend/theme/catppuccin_mocha_theme.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
/// Pumps a single screen/widget under test inside the minimal scaffolding the
|
|
/// app's widgets expect: an [AppState] provider (read by Settings) and a
|
|
/// [MaterialApp] carrying a real app theme. The real theme is required because
|
|
/// several widgets read `Theme.of(context).extension<AppColorExtension>()!`
|
|
/// non-null and would crash under a bare [ThemeData].
|
|
///
|
|
/// Widgets are pumped directly (never through the router shell) so the AppBar's
|
|
/// Google Fonts lookup is never reached.
|
|
Future<void> pumpScreen(
|
|
WidgetTester tester,
|
|
Widget child, {
|
|
Size size = const Size(900, 1200),
|
|
}) async {
|
|
tester.view.physicalSize = size;
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
|
|
await tester.pumpWidget(
|
|
ChangeNotifierProvider(
|
|
create: (_) => AppState(),
|
|
child: MaterialApp(
|
|
theme: catppuccinMochaDarkTheme,
|
|
home: Scaffold(body: child),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Pumps frames (advancing fake time in small steps) until [finder] matches or
|
|
/// [maxFrames] is reached. Needed because Dio resolves each request through an
|
|
/// internal zero-duration timer, so the response only lands once the fake clock
|
|
/// is advanced — `pumpAndSettle` is unusable here as the polling widgets keep a
|
|
/// periodic timer alive forever.
|
|
Future<void> pumpUntilFound(
|
|
WidgetTester tester,
|
|
Finder finder, {
|
|
int maxFrames = 40,
|
|
Duration step = const Duration(milliseconds: 10),
|
|
}) async {
|
|
for (var i = 0; i < maxFrames; i++) {
|
|
if (finder.evaluate().isNotEmpty) return;
|
|
await tester.pump(step);
|
|
}
|
|
if (finder.evaluate().isEmpty) {
|
|
fail('Timed out waiting for $finder');
|
|
}
|
|
}
|
|
|
|
/// Unmounts the widget tree so widgets' `dispose()` cancel their timers and the
|
|
/// test ends without "pending timer" failures. Call at the end of widget tests
|
|
/// that exercise polling widgets.
|
|
Future<void> tearDownTree(WidgetTester tester) =>
|
|
tester.pumpWidget(const SizedBox());
|