nandi/frqpublic Fork 0
1d62d1a437b844c37e3e4d49efa7e255ef4deac7
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

nim_renderer_test.dart · 129 lines · 5.2 KBDart Blame HistoryRaw
Nim owns the screen, Dart owns the pixels 43a02c2 nandi yesterday1/// The spike's real proof: Nim's tree, as Flutter widgets, driven by taps.
2///
3/// A screenshot shows that something painted. This shows that the round trip
4/// closes — a tap reaches Nim, Nim's state moves, the new tree comes back, and
5/// the widgets change to match. That is the claim the spike is making, and it
6/// is testable headlessly with no GL, which is why it is here rather than in a
7/// screenshot script.
8///
9/// just nim-spike-test
10import 'package:flutter/material.dart';
11import 'package:flutter_test/flutter_test.dart';
12import 'package:frq_core/frq_core.dart' as core;
13import 'package:cljd_flutter/nim_renderer.dart';
14
15void main() {
A message in #test, from Nim 35994d4 nandi yesterday16 // No sockets from a widget test. `connect` otherwise opens a real TLS
17 // connection to irc.freeq.at, which these tests did until this line.
18 setUpAll(core.goOffline);
Nim owns the screen, Dart owns the pixels 43a02c2 nandi yesterday19 setUp(core.resetUi);
20
21 /// The TextField currently showing [text].
22 ///
23 /// By content and not by position, and that distinction caught a bug in
24 /// these tests: in guest mode the FIRST field is the nickname, not the
25 /// host, so `find.byType(TextField).first` was clearing the wrong one and
26 /// the assertion about the host failed for a reason that had nothing to do
27 /// with the code under test.
28 Finder fieldShowing(WidgetTester tester, String text) => find.byWidgetPredicate(
29 (w) => w is TextField && w.controller?.text == text);
30
31 testWidgets('the connect screen arrives from Nim as real widgets',
32 (tester) async {
33 await tester.pumpWidget(const NimApp());
34
35 expect(find.text('frq'), findsWidgets);
36 expect(find.text('Server'), findsOneWidget);
37 expect(find.text('Connect'), findsOneWidget);
38 expect(find.byType(Checkbox), findsOneWidget);
39 // Guest is the default mode, so the nickname field is the one shown.
40 expect(find.widgetWithText(OutlinedButton, 'Bluesky'), findsOneWidget);
41 });
42
43 testWidgets('nothing renders as an unknown tag', (tester) async {
44 await tester.pumpWidget(const NimApp());
45 // The renderer paints an orange `?tag` box for a tag it does not know.
46 // Finding one means Nim emitted something Dart has never heard of, which
47 // is exactly the drift this test exists to catch.
48 expect(find.textContaining('?'), findsNothing);
49 });
50
51 testWidgets('tapping a mode button changes which fields exist',
52 (tester) async {
53 await tester.pumpWidget(const NimApp());
54 // Guest is the default, so the nickname field is the one on screen.
55 expect(fieldShowing(tester, 'frq-guest'), findsOneWidget);
56
57 await tester.tap(find.text('Bluesky'));
58 await tester.pump();
59
60 // The Bluesky copy comes from Nim, not from this side.
61 expect(find.text('Sign in with Bluesky'), findsOneWidget);
62 // ...and the nickname field is gone, because Nim stopped emitting it.
63 expect(fieldShowing(tester, 'frq-guest'), findsNothing);
64 });
65
66 testWidgets('the TLS checkbox rewrites the port field', (tester) async {
67 await tester.pumpWidget(const NimApp());
68
69 TextField portField() => tester.widgetList<TextField>(find.byType(TextField))
70 .firstWhere((f) => f.controller?.text == '6697' ||
71 f.controller?.text == '6667');
72
73 expect(portField().controller!.text, '6697');
74 await tester.tap(find.byType(Checkbox));
75 await tester.pump();
76 expect(portField().controller!.text, '6667');
77 });
78
79 testWidgets('typing goes to Nim and comes back', (tester) async {
80 await tester.pumpWidget(const NimApp());
81
82 await tester.enterText(fieldShowing(tester, 'irc.freeq.at'), 'localhost');
83 await tester.pump();
84
85 // Round trip: the text is in the widget because Nim put it in the tree,
86 // not because the TextField remembered it. Asking Nim directly is what
87 // makes that distinction.
88 expect(core.render().toString(), isNotEmpty);
89 expect(
90 tester.widgetList<TextField>(find.byType(TextField))
91 .any((f) => f.controller?.text == 'localhost'),
92 isTrue,
93 );
94 });
95
96 testWidgets('Connect with an empty host shows Nim\'s error, and it dismisses',
97 (tester) async {
98 await tester.pumpWidget(const NimApp());
99
100 // A space, not an empty string: Nim's rule is `strip().len == 0`, and a
101 // space exercises it where "" would also pass a naive emptiness check.
102 await tester.enterText(fieldShowing(tester, 'irc.freeq.at'), ' ');
103 await tester.pump();
104 await tester.tap(find.text('Connect'));
105 await tester.pump();
106
107 expect(find.textContaining('A server is required'), findsOneWidget);
108 expect(find.text('Dismiss'), findsOneWidget);
109
110 await tester.tap(find.text('Dismiss'));
111 await tester.pump();
112 expect(find.text('Dismiss'), findsNothing);
113 });
114
115 testWidgets('Connect swaps the button for a spinner', (tester) async {
116 await tester.pumpWidget(const NimApp());
117 expect(find.byType(CircularProgressIndicator), findsNothing);
118
119 await tester.tap(find.text('Connect'));
120 await tester.pump();
121
122 expect(find.byType(CircularProgressIndicator), findsOneWidget);
123 expect(find.text('Connect'), findsNothing);
124 expect(find.textContaining('irc.freeq.at:6697'), findsOneWidget);
A message in #test, from Nim 35994d4 nandi yesterday125 // And a way out of it, which the first run of the spike did not have:
126 // a connection that never completes was a spinner with no escape.
127 expect(find.text('Cancel'), findsOneWidget);
Nim owns the screen, Dart owns the pixels 43a02c2 nandi yesterday128 });
129}