feat(app): the recovery phrase's words typed back are suggested from BIP-39's list (ADR 0017)
Each of the three fields suggests the words of BIP-39's whole English list beginning with what is typed, never the phrase's own, and refuses a word not on it as its field is left, so that a typo is caught before the words are compared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
18f456eed9
commit
acb291afcb
1 changed files with 280 additions and 24 deletions
|
|
@ -0,0 +1,111 @@
|
|||
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';
|
||||
|
||||
/// The recovery phrase's words typed back (ADR 0017): each field suggests
|
||||
/// words of BIP-39's whole English list as they are typed, never the
|
||||
/// phrase's own, and takes no word that isn't on it, so a typo is caught
|
||||
/// before the words are compared.
|
||||
void main() {
|
||||
late List<TextEditingController> controllers;
|
||||
late int done;
|
||||
|
||||
setUp(() {
|
||||
controllers = [for (var i = 0; i < 3; i++) TextEditingController()];
|
||||
done = 0;
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
for (final controller in controllers) {
|
||||
controller.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
Future<void> pump(WidgetTester tester) => tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: testLocalizationsDelegates,
|
||||
supportedLocales: HalkyonAppLocalizations.supportedLocales,
|
||||
locale: const Locale('en'),
|
||||
home: Scaffold(
|
||||
body: SingleChildScrollView(
|
||||
child: PhraseCheckFields(
|
||||
positions: const [2, 7, 19],
|
||||
controllers: controllers,
|
||||
onDone: () => done++,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Finder field(int i) => find.byType(TextField).at(i);
|
||||
|
||||
testWidgets("the list's words are suggested as they are typed, and one "
|
||||
'picked fills the field and moves on', (tester) async {
|
||||
await pump(tester);
|
||||
await tester.enterText(field(0), 'zo');
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('zone'), findsOneWidget);
|
||||
expect(find.text('zoo'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('zoo'));
|
||||
await tester.pump();
|
||||
expect(controllers[0].text, 'zoo');
|
||||
expect(
|
||||
tester.widget<TextField>(field(1)).focusNode!.hasFocus,
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('a word typed whole suggests nothing more, unless a longer '
|
||||
'one begins with it', (tester) async {
|
||||
await pump(tester);
|
||||
await tester.enterText(field(0), 'zoo');
|
||||
await tester.pump();
|
||||
// The field's own text alone, no option below it.
|
||||
expect(find.text('zoo'), findsOneWidget);
|
||||
|
||||
await tester.enterText(field(0), 'act');
|
||||
await tester.pump();
|
||||
expect(find.text('action'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets("a word that isn't on the list is refused as the field is "
|
||||
'left, and the words are not handed on', (tester) async {
|
||||
await pump(tester);
|
||||
await tester.enterText(field(0), 'abandom');
|
||||
await tester.testTextInput.receiveAction(TextInputAction.next);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Not a word a recovery phrase is made of'), findsOne);
|
||||
expect(
|
||||
tester.widget<TextField>(field(0)).focusNode!.hasFocus,
|
||||
isTrue,
|
||||
);
|
||||
|
||||
await tester.enterText(field(0), 'abandon');
|
||||
await tester.enterText(field(1), 'Zoo ');
|
||||
await tester.enterText(field(2), 'qwerty');
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pump();
|
||||
expect(done, 0);
|
||||
expect(find.text('Not a word a recovery phrase is made of'), findsOne);
|
||||
|
||||
await tester.enterText(field(2), 'ability');
|
||||
await tester.pump();
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pump();
|
||||
expect(done, 1);
|
||||
expect(find.text('Not a word a recovery phrase is made of'), findsNothing);
|
||||
});
|
||||
|
||||
test("a phrase's word is one of BIP-39's English list, whatever its case "
|
||||
'and the spaces around it', () {
|
||||
expect(isPhraseWord(' Abandon '), isTrue);
|
||||
expect(isPhraseWord('zoo'), isTrue);
|
||||
expect(isPhraseWord('abandom'), isFalse);
|
||||
expect(isPhraseWord(''), isFalse);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue