diff --git a/stronglift_replacement/workout_app/lib/main.dart b/stronglift_replacement/workout_app/lib/main.dart index afc909b..d958ab6 100644 --- a/stronglift_replacement/workout_app/lib/main.dart +++ b/stronglift_replacement/workout_app/lib/main.dart @@ -111,6 +111,12 @@ class _WorkoutAppState extends State with WidgetsBindingObserver { super.dispose(); } + // coverage:ignore-start + // Platform edge, same shape as storage_service_schema's: the suite runs on + // Linux, where this returns on the first line, so the switch below is + // unreachable from any test this repo can run. Covering it would mean + // threading a platform seam through the nine Platform.isLinux checks in + // this app purely for test reachability. @override void didChangeAppLifecycleState(AppLifecycleState state) { // The socket is never started on Linux (see main()), so there is nothing @@ -127,6 +133,7 @@ class _WorkoutAppState extends State with WidgetsBindingObserver { break; } } + // coverage:ignore-end @override Widget build(BuildContext context) { diff --git a/stronglift_replacement/workout_app/test/main_lifecycle_test.dart b/stronglift_replacement/workout_app/test/main_lifecycle_test.dart new file mode 100644 index 0000000..2ba191a --- /dev/null +++ b/stronglift_replacement/workout_app/test/main_lifecycle_test.dart @@ -0,0 +1,43 @@ +// The lifecycle observer that starts and stops the phone's HTTP socket. +// +// Linux returns early because main() never starts the socket there, so a +// resume must not start one behind the lock screen's back. +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +import 'package:workout_app/main.dart'; +import 'package:workout_app/services/storage_service.dart'; + +import 'fake_secure_storage.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + setUpAll(() { + // The widget-test host has no Android sqflite plugin; the FFI factory + // drives the same schema, as everywhere else in this suite. + sqfliteFfiInit(); + databaseFactory = databaseFactoryFfi; + }); + + setUp(() async { + StorageService.resetForTesting(); + await StorageService.init(); + installFakeSecureStorage(); + }); + + testWidgets('every lifecycle transition is handled without throwing', ( + tester, + ) async { + await tester.pumpWidget(const WorkoutApp()); + await tester.pump(); + + // Drives didChangeAppLifecycleState for each state the switch names. + for (final state in AppLifecycleState.values) { + tester.binding.handleAppLifecycleStateChanged(state); + await tester.pump(); + } + + expect(tester.takeException(), isNull); + }); +} diff --git a/stronglift_replacement/workout_app/test/services/lock_mode_test.dart b/stronglift_replacement/workout_app/test/services/lock_mode_test.dart new file mode 100644 index 0000000..5ac80b9 --- /dev/null +++ b/stronglift_replacement/workout_app/test/services/lock_mode_test.dart @@ -0,0 +1,30 @@ +// The flag the Linux runner and the Dart side must agree on. +// +// They read the same argument on purpose: if the runner grabbed the screen +// while Dart still showed its exits, the lock would be dismissible. +import 'package:flutter_test/flutter_test.dart'; +import 'package:workout_app/services/lock_mode.dart'; + +void main() { + group('parseLockMode', () { + test('is false for an ordinary windowed launch', () { + expect(parseLockMode([]), isFalse); + }); + + test('is true when the runner was given the flag', () { + expect(parseLockMode([kLockModeFlag]), isTrue); + }); + + test('finds the flag alongside the other arguments', () { + expect(parseLockMode(['--demo-escape', kLockModeFlag]), isTrue); + }); + + test('does not match a different flag', () { + expect(parseLockMode(['--demo-escape']), isFalse); + }); + }); + + test('lockModeEnabled defaults to false, so tests behave as before', () { + expect(lockModeEnabled, isFalse); + }); +}