ws method

Future<TestWsConnection> ws(
  1. String path, {
  2. Map<String, String>? headers,
})

Opens a WebSocket session on a server.ws route. The handshake runs through real runner dispatch (pattern, params, query, headers), then messages flow both ways with no sockets and no frames.

Implementation

Future<TestWsConnection> ws(
  String path, {
  Map<String, String>? headers,
}) async {
  var clean = path;
  var query = '';
  final q = path.indexOf('?');
  if (q != -1) {
    clean = path.substring(0, q);
    query = path.substring(q + 1);
  }
  if (clean.isEmpty) clean = '/';
  final match = _native.matchRoute('GET', clean, wsOnly: true);
  if (match == null) {
    throw StateError('no websocket route for $path');
  }
  final id = ++_nextId;
  _native.wsOutbox[id] = StreamController<WsMessage>();
  _native.wsCloseCodes[id] = Completer<int>();
  _native.heads.add(
    RawIncomingRequest(
      requestId: id,
      path: clean,
      query: query,
      packedHeaders: packHeaders((headers ?? const {}).entries),
      routePattern: match.pattern,
      params: _rawParams(match),
    ),
  );
  // The session opens on dispatch (a microtask away); wait for it so
  // sends cannot land before the session exists. Production cannot race
  // here — the 101 round-trips before any frame — so the client restores
  // that ordering explicitly.
  final deadline = DateTime.now().add(_responseTimeout);
  while (!_runner.wsOpenedIdsForTesting.contains(id) &&
      DateTime.now().isBefore(deadline)) {
    await Future<void>.delayed(const Duration(milliseconds: 5));
  }
  if (!_runner.wsOpenedIdsForTesting.contains(id)) {
    throw StateError('websocket session never opened for $path');
  }
  return TestWsConnection._(_native, id);
}