nandi/frqpublic Fork 0
35994d45d04d2f4d7320773e58286de34ac4085a
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.

A message in #test, from Nim 35994d4 · on 35994d45d04d2f4d7320773e58286de34ac4085a · nandi · yesterday
live_send.dart · 76 lines · 2.7 KBDart Blame HistoryRaw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/// The spike's end to end: connect to a real freeq, join #test, say a line.
///
/// Not in the test suite on purpose. It needs a network, a DNS server and a
/// running freeq, and it sends a message to a public channel — none of which
/// belongs in something CI runs on every push. `just nim-live` runs it when
/// somebody means to.
///
/// Everything below goes through the FFI, so what it proves is the whole
/// stack: Nim's socket, Nim's TLS, Nim's IRC registration, Nim's state, and
/// the Dart boundary over all of it.
import 'dart:io';
import 'package:frq_core/frq_core.dart' as core;

Future<void> main(List<String> args) async {
  final host = args.isNotEmpty ? args[0] : 'irc.freeq.at';
  final nick = args.length > 1
      ? args[1]
      : 'frq-spike-${DateTime.now().millisecondsSinceEpoch % 10000}';
  final text = args.length > 2
      ? args[2]
      : 'frq nim spike: hello from Nim over dart:ffi';

  core.resetUi();
  print('$host as $nick');

  core.dispatch('nick.change', nick);
  core.dispatch('host.change', host);
  core.dispatch('connect');

  // Poll exactly as the renderer does — same call, same cadence — so this
  // exercises the path the app uses rather than a special one for testing.
  var tree = core.poll();
  final deadline = DateTime.now().add(const Duration(seconds: 25));
  while (DateTime.now().isBefore(deadline)) {
    await Future<void>.delayed(const Duration(milliseconds: 100));
    tree = core.poll();
    if (_find(tree, 'title').any((t) => t.prop('label', '') == '#test')) break;
    final err = _find(tree, 'label')
        .map((l) => l.prop('label', ''))
        .where((l) => l.startsWith(''));
    if (err.isNotEmpty) {
      print('${err.first}');
      exit(1);
    }
  }

  if (!_find(tree, 'title').any((t) => t.prop('label', '') == '#test')) {
    print('✗ never registered — still on ${_find(tree, "title").map((t) => t.prop("label", ""))}');
    print('  run with FRQ_TRACE=1 to see the wire');
    exit(1);
  }
  print('✓ registered and joined #test');

  core.dispatch('draft.change', text);
  tree = core.dispatch('send');

  final said = _find(tree, 'label').map((l) => l.prop('label', ''));
  if (said.contains(text)) {
    print('✓ sent: $text');
  } else {
    print('✗ the line did not reach the backlog');
    exit(1);
  }

  // Give the server a moment to echo anything back, then leave cleanly so the
  // reader thread is joined rather than killed with the process.
  await Future<void>.delayed(const Duration(seconds: 3));
  for (final m in _find(core.poll(), 'label')) {
    print('   | ${m.prop("label", "")}');
  }
  core.dispatch('disconnect');
  print('✓ disconnected');
}

List<core.UiNode> _find(core.UiNode n, String tag) =>
    [if (n.tag == tag) n, for (final c in n.children) ..._find(c, tag)];