234 lines
7.3 KiB
Dart
234 lines
7.3 KiB
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.
|
||
|
|
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<void> 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<void> 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);
|
||
|
|
});
|
||
|
|
}
|