Skip to content

Commit cf39667

Browse files
committed
Add WebSocket integration tests and improve client-server interaction handling
1 parent cb5e2fa commit cf39667

1 file changed

Lines changed: 128 additions & 5 deletions

File tree

packages/dart_node_ws/test/ws_test.dart

Lines changed: 128 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1+
12
/// Tests for dart_node_ws library types and APIs.
23
///
34
/// These tests run in Node.js environment to get coverage for the library.
45
@TestOn('node')
56
library;
67

8+
import 'dart:async';
9+
import 'dart:js_interop';
10+
import 'dart:js_interop_unsafe';
11+
12+
import 'package:dart_node_core/dart_node_core.dart';
713
import 'package:dart_node_coverage/dart_node_coverage.dart';
814
import 'package:dart_node_ws/dart_node_ws.dart';
915
import 'package:test/test.dart';
1016

11-
//TODO: we need actual web socket server/client interaction tests here.
12-
1317
void main() {
1418
setUp(initCoverage);
1519
tearDownAll(() => writeCoverageFile('coverage/coverage.json'));
@@ -79,14 +83,12 @@ void main() {
7983
test('onConnection registers handler', () {
8084
createWebSocketServer(port: 9994)
8185
..onConnection((client, url) {
82-
// Handler registered - just verify it doesn't throw
86+
// Handler registered
8387
})
8488
..close();
8589
});
8690

8791
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
9092
createWebSocketServer(port: 9993)
9193
..onConnection((client, url) {
9294
expect(client, isNotNull);
@@ -95,4 +97,125 @@ void main() {
9597
..close();
9698
});
9799
});
100+
101+
group('WebSocket Integration Tests', () {
102+
late WebSocketServer server;
103+
const testPort = 3456;
104+
105+
setUp(() async {
106+
server = createWebSocketServer(port: testPort);
107+
});
108+
109+
tearDown(() async {
110+
server.close();
111+
await Future<void>.delayed(const Duration(milliseconds: 50));
112+
});
113+
114+
test('client can connect to server', () async {
115+
final completer = Completer<void>();
116+
117+
server.onConnection((client, url) {
118+
completer.complete();
119+
});
120+
121+
final client = _createWebSocketClient('ws://localhost:$testPort');
122+
123+
await completer.future.timeout(const Duration(seconds: 2));
124+
await _waitForOpen(client);
125+
client.close();
126+
});
127+
128+
test('server receives messages from client', () async {
129+
final messageCompleter = Completer<String>();
130+
131+
server.onConnection((serverClient, url) {
132+
serverClient.onMessage((message) {
133+
messageCompleter.complete(message.text ?? '');
134+
});
135+
});
136+
137+
final client = _createWebSocketClient('ws://localhost:$testPort');
138+
139+
await _waitForOpen(client);
140+
_sendMessage(client, 'Hello from client');
141+
142+
final receivedMessage = await messageCompleter.future
143+
.timeout(const Duration(seconds: 2));
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
163+
.timeout(const Duration(seconds: 2));
164+
expect(receivedMessage, equals('Welcome to server'));
165+
client.close();
166+
});
167+
});
168+
}
169+
170+
/// Creates a WebSocket client using Node.js ws package
171+
JSWebSocket _createWebSocketClient(String url) {
172+
final ws = requireModule('ws');
173+
final wsClass = switch (ws) {
174+
final JSFunction f => f,
175+
_ => throw StateError('WebSocket module not found'),
176+
};
177+
return JSWebSocket(wsClass.callAsConstructor<JSObject>(url.toJS));
178+
}
179+
180+
/// Waits for WebSocket to reach OPEN state
181+
Future<void> _waitForOpen(JSWebSocket ws) async {
182+
final completer = Completer<void>();
183+
184+
if (ws.readyState == 1) {
185+
completer.complete();
186+
} else {
187+
ws.on('open', (() => completer.complete()).toJS);
188+
ws.on('error', ((JSAny error) => completer.completeError('Connection failed: $error')).toJS);
189+
}
190+
191+
return completer.future.timeout(const Duration(seconds: 2));
192+
}
193+
194+
/// Sends a message through WebSocket
195+
void _sendMessage(JSWebSocket ws, String message) {
196+
ws.send(message.toJS);
197+
}
198+
199+
/// Sets up message handler for WebSocket
200+
void _onMessage(JSWebSocket ws, void Function(JSAny) handler) {
201+
ws.on('message', handler.toJS);
202+
}
203+
204+
/// Extracts string message from JSAny data
205+
String _extractMessage(JSAny data) {
206+
// Convert using JavaScript String() function for safety
207+
try {
208+
final stringConstructor = globalContext['String'] as JSFunction;
209+
return (stringConstructor.callAsFunction(null, data) as JSString).toDart;
210+
} catch (_) {
211+
return data.toString();
212+
}
213+
}
214+
215+
/// JS interop types for WebSocket client
216+
extension type JSWebSocket(JSObject _) implements JSObject {
217+
external void on(String event, JSFunction handler);
218+
external void send(JSAny data);
219+
external void close([int? code, String? reason]);
220+
external int get readyState;
98221
}

0 commit comments

Comments
 (0)