import 'package:flutter_bloc/flutter_bloc.dart'; 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, and a way to /// sign out. class const _PersonApp(final SignedIn person) extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( localizationsDelegates: testLocalizationsDelegates, supportedLocales: HalkyonAppLocalizations.supportedLocales, home: Scaffold( body: Builder( builder: (context) => Column( children: [ Text('App of ${person.authUserId}'), TextButton( onPressed: () => context.read().add( const SessionEventSignOutRequested(), ), child: const Text('Sign out'), ), ], ), ), ), ); } void main() { late FakeAuthGateway gateway; late TestAppDependencies dependencies; setUp(() { gateway = FakeAuthGateway(); dependencies = TestAppDependencies(gateway: gateway); }); Future start(WidgetTester tester) async { 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(); } /// Nobody signed in, as at a first start, on an instance offering every /// way in. void signedOut() { gateway = FakeAuthGateway(status: const SignedOut()); dependencies = TestAppDependencies(gateway: gateway); dependencies.simpleLoginGateway.isOffered = true; } testWidgets("shows the signed-in person's app", (tester) async { await start(tester); expect(find.text('App of person-1'), findsOneWidget); }); testWidgets('nobody signed in, it asks how to start on the instance, ' 'making no account: there is no starting without one (ADR 0020, D4)', ( tester, ) async { signedOut(); await start(tester); expect(find.text('Halkyon Test'), findsOneWidget); expect(find.text('Start on test.example.org:'), findsOneWidget); expect(find.text('Continue with SimpleLogin'), findsOneWidget); expect(find.text('Sign in'), findsOneWidget); expect(find.text('Create an account'), findsOneWidget); expect( find.textContaining('GitHub'), findsNothing, reason: 'GitHub signs nobody in any more (ADR 0020, D36)', ); expect(find.text('Start without an account'), findsNothing); expect( find.text('Use another instance'), findsNothing, reason: 'no instances to choose among here', ); expect(gateway.status, const SignedOut()); }); testWidgets("an anonymous account's session left from before ends as the " 'app starts, the account deleted, and the start screen asks (D25)', ( tester, ) async { gateway = FakeAuthGateway( status: const SignedIn(authUserId: 'anonymous-1', isIdentified: false), ); dependencies = TestAppDependencies(gateway: gateway); await start(tester); expect(find.text('App of anonymous-1'), findsNothing); expect(find.text('Start on test.example.org:'), findsOneWidget); expect(gateway.accountDeletions, 1); }); testWidgets('an instance with no way to create an account says so', ( tester, ) async { signedOut(); dependencies.passwordGateway.offered = false; dependencies.simpleLoginGateway.isOffered = false; await start(tester); expect( find.text('test.example.org offers no way to create an account.'), findsOneWidget, ); expect(find.text('Continue with SimpleLogin'), findsNothing); expect(find.text('Create an account'), findsNothing); }); testWidgets('offers only the ways the instance has', (tester) async { signedOut(); dependencies.passwordGateway.offered = false; await start(tester); expect(find.text('Continue with SimpleLogin'), findsOneWidget); expect(find.text('Sign in'), findsNothing); expect(find.text('Create an account'), findsNothing); expect(find.textContaining('offers no way'), findsNothing); }); testWidgets('an instance out of reach is said so, offering nothing, and ' 'asked again', (tester) async { signedOut(); dependencies.passwordGateway.failing = const PasswordFailed( PasswordProblem.failed, ); dependencies.simpleLoginGateway.failing = const SimpleLoginFailed( SimpleLoginProblem.failed, ); await start(tester); expect(find.text("Couldn't reach the server."), findsOneWidget); expect(find.text('Continue with SimpleLogin'), findsNothing); expect(find.textContaining('offers no way'), findsNothing); dependencies.passwordGateway.failing = null; dependencies.simpleLoginGateway.failing = null; await tester.tap(find.text('Try again')); await tester.pumpAndSettle(); expect(find.text('Continue with SimpleLogin'), findsOneWidget); }); testWidgets('signing out, or the instance ending the session, asks how to ' 'start', (tester) async { gateway = FakeAuthGateway( status: const SignedIn(authUserId: 'ada', isIdentified: true), ); dependencies = TestAppDependencies(gateway: gateway); await start(tester); expect(find.text('App of ada'), findsOneWidget); await tester.tap(find.text('Sign out')); await tester.pumpAndSettle(); expect(find.text('Start on test.example.org:'), findsOneWidget); // Signed in anew, as a sign-in's session put in place does it. await gateway.useSession(const SessionGrant('ada', 'session-2')); await tester.pumpAndSettle(); expect(find.text('App of ada'), findsOneWidget); gateway.revokeSession(); await tester.pumpAndSettle(); expect(find.text('Start on test.example.org:'), findsOneWidget); }); testWidgets("a sign-out under way or failed leaves the person's app as it " 'is, built once', (tester) async { gateway = FakeAuthGateway( status: const SignedIn(authUserId: 'ada', isIdentified: true), )..signOutError = Exception('The keyring is locked'); dependencies = TestAppDependencies(gateway: gateway); var builds = 0; await tester.pumpWidget( AppFrameScope( frame: testFrame(), child: AppScope( dependencies: dependencies, child: SessionGate( look: (context) => (themeMode: ThemeMode.light, locale: null), app: (person) { builds++; return _PersonApp(person); }, ), ), ), ); await tester.pumpAndSettle(); expect(builds, 1); await tester.tap(find.text('Sign out')); await tester.pumpAndSettle(); expect(find.text('App of ada'), findsOneWidget); expect(builds, 1, reason: "busy and its problem are the account tiles'"); }); testWidgets("where the instance's operator takes no new accounts, nobody " "is offered to create one (Halkyon Learn's ADR 0021, D19)", ( tester, ) async { signedOut(); gateway.offeredSignUps = SignUps.closed; await start(tester); expect( find.text('test.example.org takes no new accounts: sign in with yours.'), findsOneWidget, ); expect(find.text('Create an account'), findsNothing); expect(find.text('Sign in'), findsOneWidget); expect(find.text('Continue with SimpleLogin'), findsOneWidget); }); testWidgets('where new accounts wait for approval, the start screen says ' 'so, and offers all as before', (tester) async { signedOut(); gateway.offeredSignUps = SignUps.approval; await start(tester); expect( find.text( 'New accounts on test.example.org wait until its operator approves ' 'them.', ), findsOneWidget, ); expect(find.text('Create an account'), findsOneWidget); }); testWidgets('open, the start screen says nothing of it', (tester) async { signedOut(); await start(tester); expect(find.textContaining('new accounts'), findsNothing); expect(find.textContaining('New accounts'), findsNothing); }); }