import 'package:flutter_test/flutter_test.dart'; import 'package:halkyon_app/halkyon_app.dart'; import 'package:halkyon_app/testing.dart'; import 'package:material_ui/material_ui.dart'; /// A signed-in person's app as a test makes one: whose it is. class const _PersonApp(final SignedIn person) extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( home: Scaffold(body: Text('App of ${person.authUserId}')), ); } /// The wait screen (Halkyon Learn's ADR 0021, D31, D33), as the session gate /// shows it while an account signed in or up to here waits for the /// instance's operator. void main() { late FakeAuthGateway gateway; late TestAppDependencies dependencies; final anna = fakeWaitOf('anna', handle: 'anna'); setUp(() { gateway = FakeAuthGateway(status: Waiting(anna)); dependencies = TestAppDependencies(gateway: gateway); }); Future start(WidgetTester tester) async { tester.view.physicalSize = const Size(412, 1200); tester.view.devicePixelRatio = 1; addTearDown(tester.view.reset); await tester.pumpWidget( AppFrameScope( frame: testFrame(), child: AppScope( dependencies: dependencies, child: SessionGate( look: (context) => (themeMode: ThemeMode.light, locale: null), app: _PersonApp.new, ), ), ), ); await tester.pumpAndSettle(); } Future tap(WidgetTester tester, String label) async { final button = find.text(label); await tester.ensureVisible(button); await tester.tap(button); await tester.pumpAndSettle(); } const waiting = 'Your account waits until the operator of test.example.org approves it.'; testWidgets('shows the handle, the words to match and what to do with ' 'them: only check them, never read them out (D33)', (tester) async { await start(tester); expect(find.text('Halkyon Test'), findsOneWidget); expect(find.text('@anna'), findsOneWidget); expect(find.text(waiting), findsOneWidget); expect(find.text('Matching code'), findsOneWidget); for (final word in fakeMatchingWords) { expect(find.text(word), findsOneWidget); } expect( find.text( 'When the operator of test.example.org reads you four words, check ' 'that they are these. Never read them out yourself: Halkyon never ' 'asks anyone to.', ), findsOneWidget, ); expect(find.text(anna.pass), findsNothing, reason: 'the pass is a secret'); expect(find.text('Start on test.example.org:'), findsNothing); }); testWidgets('asks the instance as it opens, every 30 seconds, as the app ' 'comes back, and when asked', (tester) async { await start(tester); expect(gateway.looks, 1); await tester.pump(const Duration(seconds: 30)); expect(gateway.looks, 2); // Away and back, as the system moves an app. [ AppLifecycleState.inactive, AppLifecycleState.hidden, AppLifecycleState.paused, AppLifecycleState.hidden, AppLifecycleState.inactive, AppLifecycleState.resumed, ].forEach(tester.binding.handleAppLifecycleStateChanged); await tester.pump(); expect(gateway.looks, 3); await tap(tester, 'Check now'); expect(gateway.looks, 4); }); testWidgets('approved, it says so and leads to the usual sign-in, once ' 'more as before (D31): the pass holds no session', (tester) async { await start(tester); gateway.waitAnswer = AccountWaitState.approved; await tester.pump(const Duration(seconds: 30)); await tester.pumpAndSettle(); expect( find.text( 'The operator of test.example.org has approved your account. Sign ' 'in once more, as you did before.', ), findsOneWidget, ); expect(find.text('Matching code'), findsNothing); expect(gateway.usedSessions, isEmpty); // Nothing left to ask. await tester.pump(const Duration(seconds: 60)); expect(gateway.looks, 2); await tap(tester, 'Sign in'); expect(find.text('Start on test.example.org:'), findsOneWidget); expect(gateway.waitsStopped, 1); }); testWidgets('a wait the instance knows no more ends: refused, or too old', ( tester, ) async { await start(tester); gateway.waitAnswer = AccountWaitState.ended; await tap(tester, 'Check now'); expect( find.text( 'This sign-up has ended: the operator of test.example.org refused ' 'it, or it waited too long.', ), findsOneWidget, ); await tap(tester, 'Back to the start'); expect(find.text('Start on test.example.org:'), findsOneWidget); }); testWidgets('a new account is deleted with its pass, after asking', ( tester, ) async { await start(tester); await tap(tester, 'Delete this account'); expect(find.text('Delete this account?'), findsOneWidget); await tap(tester, 'Cancel'); expect(gateway.waitingDeletions, 0); expect(find.text(waiting), findsOneWidget); await tap(tester, 'Delete this account'); await tester.tap(find.widgetWithText(FilledButton, 'Delete this account')); await tester.pumpAndSettle(); expect(gateway.waitingDeletions, 1); expect(find.text('Start on test.example.org:'), findsOneWidget); }); testWidgets('a deletion that fails says so, and the wait stays', ( tester, ) async { gateway.waitingDeletionError = Exception('No connection'); await start(tester); await tap(tester, 'Delete this account'); await tester.tap(find.widgetWithText(FilledButton, 'Delete this account')); await tester.pumpAndSettle(); expect( find.text("Couldn't delete the account. Try again."), findsOneWidget, ); expect(find.text(waiting), findsOneWidget); }); testWidgets('a suspended account shows its words but offers no deletion: ' "that takes its owner's password and a code (ADR 0017)", (tester) async { gateway = FakeAuthGateway( status: Waiting(anna.standing(AccountWaitState.suspended)), ); dependencies = TestAppDependencies(gateway: gateway); await start(tester); expect( find.text( 'The operator of test.example.org has suspended your account. Only ' 'they can lift it.', ), findsOneWidget, ); expect(find.text('abandon'), findsOneWidget); expect(find.text('Delete this account'), findsNothing); expect(find.text('Sign out'), findsOneWidget); }); testWidgets('an instance out of reach is said so, and asked again', ( tester, ) async { gateway.lookError = Exception('No connection'); await start(tester); expect( find.text( "test.example.org couldn't be reached. It's asked again every 30 " 'seconds.', ), findsOneWidget, ); expect(find.text(waiting), findsOneWidget); gateway.lookError = null; await tester.pump(const Duration(seconds: 30)); await tester.pumpAndSettle(); expect(find.textContaining("couldn't be reached"), findsNothing); }); testWidgets('signing out stops waiting on this device: the start screen ' 'follows, where signing in again waits anew', (tester) async { await start(tester); await tap(tester, 'Sign out'); expect(gateway.waitsStopped, 1); expect(find.text('Start on test.example.org:'), findsOneWidget); }); }