nandi/frqpublic Fork 0
43a02c2ddd7ebb7cdcd7d46f8c9a20b5b1b55b5e
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 · 123 lines · 4.8 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() {
16 setUp(core.resetUi);
17
18 /// The TextField currently showing [text].
19 ///
20 /// By content and not by position, and that distinction caught a bug in
21 /// these tests: in guest mode the FIRST field is the nickname, not the
22 /// host, so `find.byType(TextField).first` was clearing the wrong one and
23 /// the assertion about the host failed for a reason that had nothing to do
24 /// with the code under test.
25 Finder fieldShowing(WidgetTester tester, String text) => find.byWidgetPredicate(
26 (w) => w is TextField && w.controller?.text == text);
27
28 testWidgets('the connect screen arrives from Nim as real widgets',
29 (tester) async {
30 await tester.pumpWidget(const NimApp());
31
32 expect(find.text('frq'), findsWidgets);
33 expect(find.text('Server'), findsOneWidget);
34 expect(find.text('Connect'), findsOneWidget);
35 expect(find.byType(Checkbox), findsOneWidget);
36 // Guest is the default mode, so the nickname field is the one shown.
37 expect(find.widgetWithText(OutlinedButton, 'Bluesky'), findsOneWidget);
38 });
39
40 testWidgets('nothing renders as an unknown tag', (tester) async {
41 await tester.pumpWidget(const NimApp());
42 // The renderer paints an orange `?tag` box for a tag it does not know.
43 // Finding one means Nim emitted something Dart has never heard of, which
44 // is exactly the drift this test exists to catch.
45 expect(find.textContaining('?'), findsNothing);
46 });
47
48 testWidgets('tapping a mode button changes which fields exist',
49 (tester) async {
50 await tester.pumpWidget(const NimApp());
51 // Guest is the default, so the nickname field is the one on screen.
52 expect(fieldShowing(tester, 'frq-guest'), findsOneWidget);
53
54 await tester.tap(find.text('Bluesky'));
55 await tester.pump();
56
57 // The Bluesky copy comes from Nim, not from this side.
58 expect(find.text('Sign in with Bluesky'), findsOneWidget);
59 // ...and the nickname field is gone, because Nim stopped emitting it.
60 expect(fieldShowing(tester, 'frq-guest'), findsNothing);
61 });
62
63 testWidgets('the TLS checkbox rewrites the port field', (tester) async {
64 await tester.pumpWidget(const NimApp());
65
66 TextField portField() => tester.widgetList<TextField>(find.byType(TextField))
67 .firstWhere((f) => f.controller?.text == '6697' ||
68 f.controller?.text == '6667');
69
70 expect(portField().controller!.text, '6697');
71 await tester.tap(find.byType(Checkbox));
72 await tester.pump();
73 expect(portField().controller!.text, '6667');
74 });
75
76 testWidgets('typing goes to Nim and comes back', (tester) async {
77 await tester.pumpWidget(const NimApp());
78
79 await tester.enterText(fieldShowing(tester, 'irc.freeq.at'), 'localhost');
80 await tester.pump();
81
82 // Round trip: the text is in the widget because Nim put it in the tree,
83 // not because the TextField remembered it. Asking Nim directly is what
84 // makes that distinction.
85 expect(core.render().toString(), isNotEmpty);
86 expect(
87 tester.widgetList<TextField>(find.byType(TextField))
88 .any((f) => f.controller?.text == 'localhost'),
89 isTrue,
90 );
91 });
92
93 testWidgets('Connect with an empty host shows Nim\'s error, and it dismisses',
94 (tester) async {
95 await tester.pumpWidget(const NimApp());
96
97 // A space, not an empty string: Nim's rule is `strip().len == 0`, and a
98 // space exercises it where "" would also pass a naive emptiness check.
99 await tester.enterText(fieldShowing(tester, 'irc.freeq.at'), ' ');
100 await tester.pump();
101 await tester.tap(find.text('Connect'));
102 await tester.pump();
103
104 expect(find.textContaining('A server is required'), findsOneWidget);
105 expect(find.text('Dismiss'), findsOneWidget);
106
107 await tester.tap(find.text('Dismiss'));
108 await tester.pump();
109 expect(find.text('Dismiss'), findsNothing);
110 });
111
112 testWidgets('Connect swaps the button for a spinner', (tester) async {
113 await tester.pumpWidget(const NimApp());
114 expect(find.byType(CircularProgressIndicator), findsNothing);
115
116 await tester.tap(find.text('Connect'));
117 await tester.pump();
118
119 expect(find.byType(CircularProgressIndicator), findsOneWidget);
120 expect(find.text('Connect'), findsNothing);
121 expect(find.textContaining('irc.freeq.at:6697'), findsOneWidget);
122 });
123}