xworkmate-app/test/features/desktop/desktop_client_test.dart
2026-06-04 22:38:09 +08:00

202 lines
5.0 KiB
Dart

import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:xworkmate/features/desktop/desktop_client.dart';
class FakeMediaStream extends MediaStream {
FakeMediaStream(String id) : super(id, 'test');
final List<MediaStreamTrack> tracks = [];
final List<bool> addToNativeValues = [];
@override
bool? get active => true;
@override
Future<void> addTrack(
MediaStreamTrack track, {
bool addToNative = true,
}) async {
tracks.add(track);
addToNativeValues.add(addToNative);
}
@override
Future<void> getMediaTracks() async {}
@override
List<MediaStreamTrack> getAudioTracks() =>
tracks.where((track) => track.kind == 'audio').toList();
@override
MediaStreamTrack? getTrackById(String trackId) {
for (final track in tracks) {
if (track.id == trackId) {
return track;
}
}
return null;
}
@override
List<MediaStreamTrack> getTracks() => List.unmodifiable(tracks);
@override
List<MediaStreamTrack> getVideoTracks() =>
tracks.where((track) => track.kind == 'video').toList();
@override
Future<void> removeTrack(
MediaStreamTrack track, {
bool removeFromNative = true,
}) async {
tracks.remove(track);
}
}
class FakeMediaStreamTrack extends MediaStreamTrack {
FakeMediaStreamTrack({required this.trackId, required this.trackKind});
final String trackId;
final String trackKind;
bool _enabled = true;
@override
Future<ByteBuffer> captureFrame() {
throw UnimplementedError();
}
@override
Future<void> dispose() async {}
@override
bool get enabled => _enabled;
@override
set enabled(bool b) {
_enabled = b;
}
@override
Future<bool> hasTorch() async => false;
@override
String? get id => trackId;
@override
String? get kind => trackKind;
@override
String? get label => trackKind;
@override
bool? get muted => false;
@override
Future<void> setTorch(bool torch) async {}
@override
Future<void> stop() async {}
}
void main() {
group('DesktopClient protocol helpers', () {
test('normalizes WebRTC connection states for view gating', () {
expect(
desktopConnectionStateName(
RTCPeerConnectionState.RTCPeerConnectionStateConnected,
),
'connected',
);
expect(
desktopConnectionStateName(
RTCPeerConnectionState.RTCPeerConnectionStateFailed,
),
'failed',
);
});
test('builds desktop offer params with native numeric values', () {
final params = desktopOfferParams(
sessionId: 'desktop-session-1',
sdpOffer: 'v=0',
display: ':0.0',
width: 1280,
height: 720,
fps: 30,
bitrate: 2000,
useGpu: false,
);
expect(params['sessionId'], 'desktop-session-1');
expect(params['sdpOffer'], 'v=0');
expect(params['display'], ':0.0');
expect(params['width'], isA<int>());
expect(params['height'], isA<int>());
expect(params['fps'], isA<int>());
expect(params['bitrate'], isA<int>());
expect(params['useGpu'], isA<bool>());
expect(params['width'], 1280);
expect(params['height'], 720);
});
test('uses bridge-provided remote stream when present', () async {
var fallbackCreated = false;
final providedStream = FakeMediaStream('provided-stream');
final track = FakeMediaStreamTrack(
trackId: 'video-track-1',
trackKind: 'video',
);
final stream = await desktopRemoteVideoStreamForTrack(
RTCTrackEvent(streams: [providedStream], track: track),
createFallbackStream: (label) async {
fallbackCreated = true;
return FakeMediaStream(label);
},
);
expect(stream, same(providedStream));
expect(fallbackCreated, isFalse);
expect(providedStream.tracks, isEmpty);
});
test('synthesizes stream for streamless remote video track', () async {
final track = FakeMediaStreamTrack(
trackId: 'video-track-1',
trackKind: 'video',
);
final fallbackStream = FakeMediaStream('fallback-stream');
final stream = await desktopRemoteVideoStreamForTrack(
RTCTrackEvent(streams: const [], track: track),
createFallbackStream: (label) async => fallbackStream,
);
expect(stream, same(fallbackStream));
expect(fallbackStream.tracks, [same(track)]);
expect(fallbackStream.addToNativeValues, [isFalse]);
});
test('ignores streamless non-video tracks', () async {
var fallbackCreated = false;
final track = FakeMediaStreamTrack(
trackId: 'audio-track-1',
trackKind: 'audio',
);
final stream = await desktopRemoteVideoStreamForTrack(
RTCTrackEvent(streams: const [], track: track),
createFallbackStream: (label) async {
fallbackCreated = true;
return FakeMediaStream(label);
},
);
expect(stream, isNull);
expect(fallbackCreated, isFalse);
});
});
}