44@TestOn ('node' )
55library ;
66
7+ import 'dart:async' ;
8+ import 'dart:js_interop' ;
9+ import 'dart:js_interop_unsafe' ;
10+
11+ import 'package:dart_node_core/dart_node_core.dart' ;
712import 'package:dart_node_coverage/dart_node_coverage.dart' ;
813import 'package:dart_node_ws/dart_node_ws.dart' ;
914import 'package:test/test.dart' ;
1015
11- //TODO: we need actual web socket server/client interaction tests here.
12-
1316void main () {
1417 setUp (initCoverage);
1518 tearDownAll (() => writeCoverageFile ('coverage/coverage.json' ));
@@ -79,14 +82,12 @@ void main() {
7982 test ('onConnection registers handler' , () {
8083 createWebSocketServer (port: 9994 )
8184 ..onConnection ((client, url) {
82- // Handler registered - just verify it doesn't throw
85+ // Handler registered
8386 })
8487 ..close ();
8588 });
8689
8790 test ('onConnection receives client on connection' , () {
88- // The connection test happens in websocket_test.dart (integration tests)
89- // Here we just verify the API works without throwing
9091 createWebSocketServer (port: 9993 )
9192 ..onConnection ((client, url) {
9293 expect (client, isNotNull);
@@ -95,4 +96,132 @@ void main() {
9596 ..close ();
9697 });
9798 });
99+
100+ group ('WebSocket Integration Tests' , () {
101+ late WebSocketServer server;
102+ const testPort = 3456 ;
103+
104+ setUp (() async {
105+ server = createWebSocketServer (port: testPort);
106+ });
107+
108+ tearDown (() async {
109+ server.close ();
110+ await Future <void >.delayed (const Duration (milliseconds: 50 ));
111+ });
112+
113+ test ('client can connect to server' , () async {
114+ final completer = Completer <void >();
115+
116+ server.onConnection ((client, url) {
117+ completer.complete ();
118+ });
119+
120+ final client = _createWebSocketClient ('ws://localhost:$testPort ' );
121+
122+ await completer.future.timeout (const Duration (seconds: 2 ));
123+ await _waitForOpen (client);
124+ client.close ();
125+ });
126+
127+ test ('server receives messages from client' , () async {
128+ final messageCompleter = Completer <String >();
129+
130+ server.onConnection ((serverClient, url) {
131+ serverClient.onMessage ((message) {
132+ messageCompleter.complete (message.text ?? '' );
133+ });
134+ });
135+
136+ final client = _createWebSocketClient ('ws://localhost:$testPort ' );
137+
138+ await _waitForOpen (client);
139+ _sendMessage (client, 'Hello from client' );
140+
141+ final receivedMessage = await messageCompleter.future.timeout (
142+ const Duration (seconds: 2 ),
143+ );
144+ expect (receivedMessage, equals ('Hello from client' ));
145+ client.close ();
146+ });
147+
148+ test ('client receives messages from server' , () async {
149+ final messageCompleter = Completer <String >();
150+
151+ server.onConnection ((serverClient, url) {
152+ serverClient.send ('Welcome to server' );
153+ });
154+
155+ final client = _createWebSocketClient ('ws://localhost:$testPort ' );
156+
157+ _onMessage (client, (data) {
158+ final message = _extractMessage (data);
159+ messageCompleter.complete (message);
160+ });
161+
162+ final receivedMessage = await messageCompleter.future.timeout (
163+ const Duration (seconds: 2 ),
164+ );
165+ expect (receivedMessage, equals ('Welcome to server' ));
166+ client.close ();
167+ });
168+ });
169+ }
170+
171+ /// Creates a WebSocket client using Node.js ws package
172+ JSWebSocket _createWebSocketClient (String url) {
173+ final ws = requireModule ('ws' );
174+ final wsClass = switch (ws) {
175+ final JSFunction f => f,
176+ _ => throw StateError ('WebSocket module not found' ),
177+ };
178+ return JSWebSocket (wsClass.callAsConstructor <JSObject >(url.toJS));
179+ }
180+
181+ /// Waits for WebSocket to reach OPEN state
182+ Future <void > _waitForOpen (JSWebSocket ws) async {
183+ final completer = Completer <void >();
184+
185+ if (ws.readyState == 1 ) {
186+ completer.complete ();
187+ } else {
188+ ws.on ('open' , (() => completer.complete ()).toJS);
189+ ws.on (
190+ 'error' ,
191+ ((JSAny error) => completer.completeError (
192+ 'Connection failed: $error ' ,
193+ )).toJS,
194+ );
195+ }
196+
197+ return completer.future.timeout (const Duration (seconds: 2 ));
198+ }
199+
200+ /// Sends a message through WebSocket
201+ void _sendMessage (JSWebSocket ws, String message) {
202+ ws.send (message.toJS);
203+ }
204+
205+ /// Sets up message handler for WebSocket
206+ void _onMessage (JSWebSocket ws, void Function (JSAny ) handler) {
207+ ws.on ('message' , handler.toJS);
208+ }
209+
210+ /// Extracts string message from JSAny data
211+ String _extractMessage (JSAny data) {
212+ // Convert using JavaScript String() function for safety
213+ try {
214+ final stringConstructor = globalContext['String' ] as JSFunction ;
215+ return (stringConstructor.callAsFunction (null , data) as JSString ).toDart;
216+ } catch (_) {
217+ return data.toString ();
218+ }
219+ }
220+
221+ /// JS interop types for WebSocket client
222+ extension type JSWebSocket (JSObject _) implements JSObject {
223+ external void on (String event, JSFunction handler);
224+ external void send (JSAny data);
225+ external void close ([int ? code, String ? reason]);
226+ external int get readyState;
98227}
0 commit comments