mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
Add automated test suite for the Flutter frontend
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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e6813a78fb
commit
98b5fa267f
@@ -0,0 +1,60 @@
|
||||
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());
|
||||
Reference in New Issue
Block a user