| A message in #test, from Nim 35994d4 nandi yesterday | 1 | /// The spike's end to end: connect to a real freeq, join #test, say a line. |
| 2 | /// |
| 3 | /// Not in the test suite on purpose. It needs a network, a DNS server and a |
| 4 | /// running freeq, and it sends a message to a public channel — none of which |
| 5 | /// belongs in something CI runs on every push. `just nim-live` runs it when |
| 6 | /// somebody means to. |
| 7 | /// |
| 8 | /// Everything below goes through the FFI, so what it proves is the whole |
| 9 | /// stack: Nim's socket, Nim's TLS, Nim's IRC registration, Nim's state, and |
| 10 | /// the Dart boundary over all of it. |
| 11 | import 'dart:io'; |
| 12 | import 'package:frq_core/frq_core.dart' as core; |
| 13 | |
| 14 | Future<void> main(List<String> args) async { |
| 15 | final host = args.isNotEmpty ? args[0] : 'irc.freeq.at'; |
| 16 | final nick = args.length > 1 |
| 17 | ? args[1] |
| 18 | : 'frq-spike-${DateTime.now().millisecondsSinceEpoch % 10000}'; |
| 19 | final text = args.length > 2 |
| 20 | ? args[2] |
| 21 | : 'frq nim spike: hello from Nim over dart:ffi'; |
| 22 | |
| 23 | core.resetUi(); |
| 24 | print('→ $host as $nick'); |
| 25 | |
| 26 | core.dispatch('nick.change', nick); |
| 27 | core.dispatch('host.change', host); |
| 28 | core.dispatch('connect'); |
| 29 | |
| 30 | // Poll exactly as the renderer does — same call, same cadence — so this |
| 31 | // exercises the path the app uses rather than a special one for testing. |
| 32 | var tree = core.poll(); |
| 33 | final deadline = DateTime.now().add(const Duration(seconds: 25)); |
| 34 | while (DateTime.now().isBefore(deadline)) { |
| 35 | await Future<void>.delayed(const Duration(milliseconds: 100)); |
| 36 | tree = core.poll(); |
| 37 | if (_find(tree, 'title').any((t) => t.prop('label', '') == '#test')) break; |
| 38 | final err = _find(tree, 'label') |
| 39 | .map((l) => l.prop('label', '')) |
| 40 | .where((l) => l.startsWith('⚠')); |
| 41 | if (err.isNotEmpty) { |
| 42 | print('✗ ${err.first}'); |
| 43 | exit(1); |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | if (!_find(tree, 'title').any((t) => t.prop('label', '') == '#test')) { |
| 48 | print('✗ never registered — still on ${_find(tree, "title").map((t) => t.prop("label", ""))}'); |
| 49 | print(' run with FRQ_TRACE=1 to see the wire'); |
| 50 | exit(1); |
| 51 | } |
| 52 | print('✓ registered and joined #test'); |
| 53 | |
| 54 | core.dispatch('draft.change', text); |
| 55 | tree = core.dispatch('send'); |
| 56 | |
| 57 | final said = _find(tree, 'label').map((l) => l.prop('label', '')); |
| 58 | if (said.contains(text)) { |
| 59 | print('✓ sent: $text'); |
| 60 | } else { |
| 61 | print('✗ the line did not reach the backlog'); |
| 62 | exit(1); |
| 63 | } |
| 64 | |
| 65 | // Give the server a moment to echo anything back, then leave cleanly so the |
| 66 | // reader thread is joined rather than killed with the process. |
| 67 | await Future<void>.delayed(const Duration(seconds: 3)); |
| 68 | for (final m in _find(core.poll(), 'label')) { |
| 69 | print(' | ${m.prop("label", "")}'); |
| 70 | } |
| 71 | core.dispatch('disconnect'); |
| 72 | print('✓ disconnected'); |
| 73 | } |
| 74 | |
| 75 | List<core.UiNode> _find(core.UiNode n, String tag) => |
| 76 | [if (n.tag == tag) n, for (final c in n.children) ..._find(c, tag)]; |