Skip to content

Commit 3387380

Browse files
Merge branch 'main' into fix_tests_on_container
2 parents ae53b94 + fc95acf commit 3387380

2 files changed

Lines changed: 195 additions & 8 deletions

File tree

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,73 @@
1-
/// Extension methods FP style transformations
1+
/// Functional programming extensions for nullable and non-null types.
2+
///
3+
/// Provides pattern matching and transformation utilities inspired by
4+
/// functional programming languages like Kotlin and Rust.
5+
library;
6+
7+
/// Extension methods for nullable values enabling pattern matching and
8+
/// functional transformations.
9+
///
10+
/// Example:
11+
/// ```dart
12+
/// String? getName() => 'World';
13+
///
14+
/// final greeting = getName().match(
15+
/// some: (name) => 'Hello, $name!',
16+
/// none: () => 'Hello, stranger!',
17+
/// );
18+
/// ```
219
extension NullableExtensions<T extends Object> on T? {
320
/// Pattern match on nullable value with cases for non-null and null.
21+
///
22+
/// This provides a safe way to handle nullable values by requiring
23+
/// explicit handling of both the present (`some`) and absent (`none`) cases.
24+
///
25+
/// **Parameters:**
26+
/// - `some`: Function called when this value is non-null
27+
/// - `none`: Function called when this value is null
28+
///
29+
/// **Returns:** The result of calling either `some` or `none`
30+
///
31+
/// Example:
32+
/// ```dart
33+
/// int? maybeNumber = 42;
34+
///
35+
/// final result = maybeNumber.match(
36+
/// some: (n) => 'Number: $n',
37+
/// none: () => 'No number',
38+
/// ); // Returns: "Number: 42"
39+
/// ```
440
R match<R>({required R Function(T) some, required R Function() none}) =>
541
switch (this) {
642
final T value => some(value),
743
null => none(),
844
};
945
}
1046

11-
/// Extension methods FP style transformations
47+
/// Extension methods for non-null values enabling functional transformations.
48+
///
49+
/// Provides utilities for applying transformations to values in a functional style.
1250
extension ObjectExtensions<T extends Object> on T {
13-
/// Apply function [op] to this value if non-null and return the result.
51+
/// Apply function [op] to this value and return the result.
52+
///
53+
/// This is useful for chaining operations and avoiding temporary variables.
54+
/// Also known as the "let" operation in Kotlin or "tap" in other languages.
55+
///
56+
/// **Parameters:**
57+
/// - `op`: Function that transforms this value into a result of type `R`
58+
///
59+
/// **Returns:** The result of calling `op` with this value
60+
///
61+
/// Example:
62+
/// ```dart
63+
/// final length = 'hello world'
64+
/// .let((s) => s.split(' '))
65+
/// .let((words) => words.length); // Returns: 2
66+
///
67+
/// // Instead of:
68+
/// final text = 'hello world';
69+
/// final words = text.split(' ');
70+
/// final length = words.length;
71+
/// ```
1472
R let<R>(R Function(T) op) => op(this);
1573
}

packages/dart_node_ws/test/ws_test.dart

Lines changed: 134 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
@TestOn('node')
55
library;
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';
712
import 'package:dart_node_coverage/dart_node_coverage.dart';
813
import 'package:dart_node_ws/dart_node_ws.dart';
914
import 'package:test/test.dart';
1015

11-
//TODO: we need actual web socket server/client interaction tests here.
12-
1316
void 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

Comments
 (0)