From cf39667de4094b84ad8a14b178fe38c32acea1f0 Mon Sep 17 00:00:00 2001 From: Joseph Ajayi Date: Sat, 24 Jan 2026 13:28:56 +0000 Subject: [PATCH 1/2] Add WebSocket integration tests and improve client-server interaction handling --- packages/dart_node_ws/test/ws_test.dart | 133 +++++++++++++++++++++++- 1 file changed, 128 insertions(+), 5 deletions(-) diff --git a/packages/dart_node_ws/test/ws_test.dart b/packages/dart_node_ws/test/ws_test.dart index 61e50e5..83fd91d 100644 --- a/packages/dart_node_ws/test/ws_test.dart +++ b/packages/dart_node_ws/test/ws_test.dart @@ -1,15 +1,19 @@ + /// Tests for dart_node_ws library types and APIs. /// /// These tests run in Node.js environment to get coverage for the library. @TestOn('node') library; +import 'dart:async'; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; + +import 'package:dart_node_core/dart_node_core.dart'; import 'package:dart_node_coverage/dart_node_coverage.dart'; import 'package:dart_node_ws/dart_node_ws.dart'; import 'package:test/test.dart'; -//TODO: we need actual web socket server/client interaction tests here. - void main() { setUp(initCoverage); tearDownAll(() => writeCoverageFile('coverage/coverage.json')); @@ -79,14 +83,12 @@ void main() { test('onConnection registers handler', () { createWebSocketServer(port: 9994) ..onConnection((client, url) { - // Handler registered - just verify it doesn't throw + // Handler registered }) ..close(); }); test('onConnection receives client on connection', () { - // The connection test happens in websocket_test.dart (integration tests) - // Here we just verify the API works without throwing createWebSocketServer(port: 9993) ..onConnection((client, url) { expect(client, isNotNull); @@ -95,4 +97,125 @@ void main() { ..close(); }); }); + + group('WebSocket Integration Tests', () { + late WebSocketServer server; + const testPort = 3456; + + setUp(() async { + server = createWebSocketServer(port: testPort); + }); + + tearDown(() async { + server.close(); + await Future.delayed(const Duration(milliseconds: 50)); + }); + + test('client can connect to server', () async { + final completer = Completer(); + + server.onConnection((client, url) { + completer.complete(); + }); + + final client = _createWebSocketClient('ws://localhost:$testPort'); + + await completer.future.timeout(const Duration(seconds: 2)); + await _waitForOpen(client); + client.close(); + }); + + test('server receives messages from client', () async { + final messageCompleter = Completer(); + + server.onConnection((serverClient, url) { + serverClient.onMessage((message) { + messageCompleter.complete(message.text ?? ''); + }); + }); + + final client = _createWebSocketClient('ws://localhost:$testPort'); + + await _waitForOpen(client); + _sendMessage(client, 'Hello from client'); + + final receivedMessage = await messageCompleter.future + .timeout(const Duration(seconds: 2)); + expect(receivedMessage, equals('Hello from client')); + client.close(); + }); + + test('client receives messages from server', () async { + final messageCompleter = Completer(); + + server.onConnection((serverClient, url) { + serverClient.send('Welcome to server'); + }); + + final client = _createWebSocketClient('ws://localhost:$testPort'); + + _onMessage(client, (data) { + final message = _extractMessage(data); + messageCompleter.complete(message); + }); + + final receivedMessage = await messageCompleter.future + .timeout(const Duration(seconds: 2)); + expect(receivedMessage, equals('Welcome to server')); + client.close(); + }); + }); +} + +/// Creates a WebSocket client using Node.js ws package +JSWebSocket _createWebSocketClient(String url) { + final ws = requireModule('ws'); + final wsClass = switch (ws) { + final JSFunction f => f, + _ => throw StateError('WebSocket module not found'), + }; + return JSWebSocket(wsClass.callAsConstructor(url.toJS)); +} + +/// Waits for WebSocket to reach OPEN state +Future _waitForOpen(JSWebSocket ws) async { + final completer = Completer(); + + if (ws.readyState == 1) { + completer.complete(); + } else { + ws.on('open', (() => completer.complete()).toJS); + ws.on('error', ((JSAny error) => completer.completeError('Connection failed: $error')).toJS); + } + + return completer.future.timeout(const Duration(seconds: 2)); +} + +/// Sends a message through WebSocket +void _sendMessage(JSWebSocket ws, String message) { + ws.send(message.toJS); +} + +/// Sets up message handler for WebSocket +void _onMessage(JSWebSocket ws, void Function(JSAny) handler) { + ws.on('message', handler.toJS); +} + +/// Extracts string message from JSAny data +String _extractMessage(JSAny data) { + // Convert using JavaScript String() function for safety + try { + final stringConstructor = globalContext['String'] as JSFunction; + return (stringConstructor.callAsFunction(null, data) as JSString).toDart; + } catch (_) { + return data.toString(); + } +} + +/// JS interop types for WebSocket client +extension type JSWebSocket(JSObject _) implements JSObject { + external void on(String event, JSFunction handler); + external void send(JSAny data); + external void close([int? code, String? reason]); + external int get readyState; } From de6c11085a06e6f9f49bd942514c0663f55eb46d Mon Sep 17 00:00:00 2001 From: Joseph Ajayi Date: Tue, 27 Jan 2026 11:20:45 +0000 Subject: [PATCH 2/2] Refactor WebSocket tests for improved readability and formatting --- packages/dart_node_ws/test/ws_test.dart | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/dart_node_ws/test/ws_test.dart b/packages/dart_node_ws/test/ws_test.dart index 83fd91d..f73f564 100644 --- a/packages/dart_node_ws/test/ws_test.dart +++ b/packages/dart_node_ws/test/ws_test.dart @@ -1,4 +1,3 @@ - /// Tests for dart_node_ws library types and APIs. /// /// These tests run in Node.js environment to get coverage for the library. @@ -119,7 +118,7 @@ void main() { }); final client = _createWebSocketClient('ws://localhost:$testPort'); - + await completer.future.timeout(const Duration(seconds: 2)); await _waitForOpen(client); client.close(); @@ -139,8 +138,9 @@ void main() { await _waitForOpen(client); _sendMessage(client, 'Hello from client'); - final receivedMessage = await messageCompleter.future - .timeout(const Duration(seconds: 2)); + final receivedMessage = await messageCompleter.future.timeout( + const Duration(seconds: 2), + ); expect(receivedMessage, equals('Hello from client')); client.close(); }); @@ -159,8 +159,9 @@ void main() { messageCompleter.complete(message); }); - final receivedMessage = await messageCompleter.future - .timeout(const Duration(seconds: 2)); + final receivedMessage = await messageCompleter.future.timeout( + const Duration(seconds: 2), + ); expect(receivedMessage, equals('Welcome to server')); client.close(); }); @@ -180,14 +181,19 @@ JSWebSocket _createWebSocketClient(String url) { /// Waits for WebSocket to reach OPEN state Future _waitForOpen(JSWebSocket ws) async { final completer = Completer(); - + if (ws.readyState == 1) { completer.complete(); } else { ws.on('open', (() => completer.complete()).toJS); - ws.on('error', ((JSAny error) => completer.completeError('Connection failed: $error')).toJS); + ws.on( + 'error', + ((JSAny error) => completer.completeError( + 'Connection failed: $error', + )).toJS, + ); } - + return completer.future.timeout(const Duration(seconds: 2)); }