236 lines
7.7 KiB
Dart
236 lines
7.7 KiB
Dart
|
|
import 'package:flutter_test/flutter_test.dart';
|
||
|
|
import 'package:halkyon_app/halkyon_app.dart';
|
||
|
|
import 'package:halkyon_app/testing.dart';
|
||
|
|
import 'package:halkyon_oidc_flutter/halkyon_oidc_flutter.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}')),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const password = 'correct horse battery';
|
||
|
|
|
||
|
|
void main() {
|
||
|
|
late FakeAuthGateway gateway;
|
||
|
|
late TestAppDependencies dependencies;
|
||
|
|
|
||
|
|
setUp(() {
|
||
|
|
gateway = FakeAuthGateway();
|
||
|
|
dependencies = TestAppDependencies(gateway: gateway);
|
||
|
|
dependencies.simpleLoginGateway.isOffered = true;
|
||
|
|
});
|
||
|
|
|
||
|
|
/// Starts signed out, on a phone tall enough for every sheet.
|
||
|
|
Future<void> start(WidgetTester tester) async {
|
||
|
|
tester.view.physicalSize = const Size(412, 1800);
|
||
|
|
tester.view.devicePixelRatio = 1;
|
||
|
|
addTearDown(tester.view.reset);
|
||
|
|
await dependencies.signedOut.write(signedOut: true);
|
||
|
|
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();
|
||
|
|
}
|
||
|
|
|
||
|
|
Finder field(String label) => find.widgetWithText(TextField, label);
|
||
|
|
|
||
|
|
Future<void> tapButton(WidgetTester tester, String label) async {
|
||
|
|
final button = find.widgetWithText(FilledButton, label).last;
|
||
|
|
await tester.ensureVisible(button);
|
||
|
|
await tester.tap(button);
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Types the recovery phrase's words the check asks for.
|
||
|
|
Future<void> typeCheckedWords(WidgetTester tester, String phrase) async {
|
||
|
|
final words = wordsOf(phrase);
|
||
|
|
for (final element in find.byType(TextField).evaluate()) {
|
||
|
|
if ((element.widget as TextField).decoration?.labelText case final label?
|
||
|
|
when label.startsWith('Word ')) {
|
||
|
|
await tester.enterText(
|
||
|
|
field(label),
|
||
|
|
words[int.parse(label.substring('Word '.length)) - 1],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
testWidgets('the start screen offers SimpleLogin first, saying what it '
|
||
|
|
'learns and what the instance keeps', (tester) async {
|
||
|
|
await start(tester);
|
||
|
|
|
||
|
|
expect(
|
||
|
|
find.widgetWithText(FilledButton, 'Continue with SimpleLogin'),
|
||
|
|
findsOneWidget,
|
||
|
|
);
|
||
|
|
expect(
|
||
|
|
find.textContaining('keeps only your SimpleLogin ID'),
|
||
|
|
findsOneWidget,
|
||
|
|
);
|
||
|
|
expect(find.textContaining('test.example.org'), findsWidgets);
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets('offers no SimpleLogin where the instance has none', (
|
||
|
|
tester,
|
||
|
|
) async {
|
||
|
|
dependencies.simpleLoginGateway.isOffered = false;
|
||
|
|
await start(tester);
|
||
|
|
|
||
|
|
expect(find.text('Continue with SimpleLogin'), findsNothing);
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets('someone new creates an account: the handle suggested, the '
|
||
|
|
'password twice, the phrase written down, then signed in', (
|
||
|
|
tester,
|
||
|
|
) async {
|
||
|
|
await start(tester);
|
||
|
|
|
||
|
|
await tapButton(tester, 'Continue with SimpleLogin');
|
||
|
|
expect(find.text('New here?'), findsOneWidget);
|
||
|
|
await tester.tap(find.widgetWithText(ListTile, 'Create an account'));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
expect(find.text('anna_mueller'), findsOneWidget);
|
||
|
|
expect(find.text('@anna_mueller is free'), findsOneWidget);
|
||
|
|
await tester.enterText(field('Handle'), 'anna_m');
|
||
|
|
await tester.pump();
|
||
|
|
expect(find.text('@anna_mueller is free'), findsNothing);
|
||
|
|
await tester.enterText(field('Handle'), 'anna_mueller');
|
||
|
|
await tapButton(tester, 'Continue');
|
||
|
|
|
||
|
|
await tester.enterText(field('New password'), password);
|
||
|
|
await tester.enterText(field('New password again'), password);
|
||
|
|
await tapButton(tester, 'Continue');
|
||
|
|
expect(find.text('Your recovery phrase'), findsOneWidget);
|
||
|
|
await tapButton(tester, "I've written it down");
|
||
|
|
|
||
|
|
await typeCheckedWords(tester, fakePhrase);
|
||
|
|
await tapButton(tester, 'Create the account');
|
||
|
|
|
||
|
|
expect(find.text('App of person-1'), findsOneWidget);
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets('an account opens its keys with its password, a wrong one '
|
||
|
|
'refused', (tester) async {
|
||
|
|
final material = await dependencies.crypto.newKeys(password);
|
||
|
|
dependencies.simpleLoginGateway.addAccount(
|
||
|
|
'person-9',
|
||
|
|
subject: 'simplelogin-anna',
|
||
|
|
handle: 'anna',
|
||
|
|
keys: material.setup,
|
||
|
|
);
|
||
|
|
await start(tester);
|
||
|
|
|
||
|
|
await tapButton(tester, 'Continue with SimpleLogin');
|
||
|
|
expect(find.textContaining('as @anna'), findsOneWidget);
|
||
|
|
await tester.enterText(field('Password'), 'wrong horse battery');
|
||
|
|
await tapButton(tester, 'Open');
|
||
|
|
expect(find.text("The password isn't right."), findsOneWidget);
|
||
|
|
|
||
|
|
await tester.enterText(field('Password'), password);
|
||
|
|
await tapButton(tester, 'Open');
|
||
|
|
|
||
|
|
expect(find.text('App of person-9'), findsOneWidget);
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets('someone whose SimpleLogin account was lost links theirs '
|
||
|
|
'again with its handle and recovery phrase', (tester) async {
|
||
|
|
final material = await dependencies.crypto.newKeys(password);
|
||
|
|
dependencies.simpleLoginGateway.addAccount(
|
||
|
|
'person-9',
|
||
|
|
subject: 'simplelogin-lost',
|
||
|
|
handle: 'anna',
|
||
|
|
keys: material.setup,
|
||
|
|
);
|
||
|
|
await start(tester);
|
||
|
|
|
||
|
|
await tapButton(tester, 'Continue with SimpleLogin');
|
||
|
|
await tester.tap(find.widgetWithText(ListTile, 'I already have one'));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
expect(find.text('Link your account'), findsOneWidget);
|
||
|
|
expect(find.text('anna_mueller'), findsNothing);
|
||
|
|
|
||
|
|
await tester.enterText(field('Handle'), 'anna');
|
||
|
|
await tester.enterText(find.byType(TextField).last, otherFakePhrase);
|
||
|
|
await tapButton(tester, 'Link');
|
||
|
|
expect(
|
||
|
|
find.text("The handle or the recovery phrase isn't right."),
|
||
|
|
findsOneWidget,
|
||
|
|
);
|
||
|
|
|
||
|
|
await tester.enterText(find.byType(TextField).last, fakePhrase);
|
||
|
|
await tapButton(tester, 'Link');
|
||
|
|
|
||
|
|
expect(find.text('App of person-9'), findsOneWidget);
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets('closing the browser leaves the start screen as it was', (
|
||
|
|
tester,
|
||
|
|
) async {
|
||
|
|
dependencies.browser.comesBack = () => null;
|
||
|
|
await start(tester);
|
||
|
|
|
||
|
|
await tapButton(tester, 'Continue with SimpleLogin');
|
||
|
|
|
||
|
|
expect(find.text('New here?'), findsNothing);
|
||
|
|
expect(
|
||
|
|
find.widgetWithText(FilledButton, 'Continue with SimpleLogin'),
|
||
|
|
findsOneWidget,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets("a refusal on SimpleLogin's page shows, and it begins again", (
|
||
|
|
tester,
|
||
|
|
) async {
|
||
|
|
dependencies.browser.comesBack = () => const OidcDenied('access_denied');
|
||
|
|
await start(tester);
|
||
|
|
|
||
|
|
await tapButton(tester, 'Continue with SimpleLogin');
|
||
|
|
expect(find.textContaining('nothing was shared'), findsOneWidget);
|
||
|
|
|
||
|
|
dependencies.browser.comesBack = null;
|
||
|
|
await tapButton(tester, 'Try again');
|
||
|
|
|
||
|
|
expect(find.text('New here?'), findsOneWidget);
|
||
|
|
});
|
||
|
|
|
||
|
|
for (final locale in HalkyonAppLocalizations.supportedLocales) {
|
||
|
|
testWidgets('every refusal has its words in $locale', (tester) async {
|
||
|
|
late BuildContext context;
|
||
|
|
await tester.pumpWidget(
|
||
|
|
MaterialApp(
|
||
|
|
locale: locale,
|
||
|
|
localizationsDelegates: testLocalizationsDelegates,
|
||
|
|
supportedLocales: HalkyonAppLocalizations.supportedLocales,
|
||
|
|
home: Builder(
|
||
|
|
builder: (built) {
|
||
|
|
context = built;
|
||
|
|
return const SizedBox();
|
||
|
|
},
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
for (final problem in SimpleLoginProblem.values) {
|
||
|
|
expect(
|
||
|
|
simpleLoginFailureText(context, SimpleLoginFailed(problem)),
|
||
|
|
isNotEmpty,
|
||
|
|
reason: '$problem',
|
||
|
|
);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|