forked from eatvio/eatvio_app
feat(video_calls): changed accept flow, fixed ui bugs for PIP and video render sizes
This commit is contained in:
@@ -1,173 +0,0 @@
|
||||
import 'package:audio_session/audio_session.dart';
|
||||
import 'package:eatvio_app/features/video_calls/managers/video_call_manager.dart';
|
||||
import 'package:eatvio_app/features/video_calls/services/video_call_audio_session_coordinator.dart';
|
||||
import 'package:eatvio_app/features/video_calls/services/video_call_foreground_coordinator.dart';
|
||||
import 'package:eatvio_app/features/video_calls/services/video_call_lifecycle_binding.dart';
|
||||
import 'package:eatvio_app/features/video_calls/repositories/video_call_repository.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:livekit_client/livekit_client.dart' as lk;
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'video_call_manager_audio_interruption_test.mocks.dart';
|
||||
|
||||
class _FakeAudioSessionCoordinator extends VideoCallAudioSessionCoordinator {
|
||||
_FakeAudioSessionCoordinator(this.onStart);
|
||||
|
||||
final void Function(void Function(AudioInterruptionEvent event) onInterruption) onStart;
|
||||
bool reactivateCalled = false;
|
||||
|
||||
@override
|
||||
Future<void> start({required void Function(AudioInterruptionEvent event) onInterruption}) async {
|
||||
onStart(onInterruption);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> reactivate() async {
|
||||
reactivateCalled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stop() async {}
|
||||
}
|
||||
|
||||
@GenerateNiceMocks([MockSpec<VideoCallRepository>(), MockSpec<lk.Room>(), MockSpec<lk.LocalParticipant>()])
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('VideoCallManager audio interruption', () {
|
||||
late MockVideoCallRepository mockRepository;
|
||||
late MockRoom mockRoom;
|
||||
late MockLocalParticipant mockLocalParticipant;
|
||||
late _FakeAudioSessionCoordinator audioCoordinator;
|
||||
late VideoCallManager manager;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockVideoCallRepository();
|
||||
mockRoom = MockRoom();
|
||||
mockLocalParticipant = MockLocalParticipant();
|
||||
audioCoordinator = _FakeAudioSessionCoordinator((_) {});
|
||||
manager = VideoCallManager(
|
||||
videoCallRepository: mockRepository,
|
||||
foregroundCoordinator: VideoCallForegroundCoordinator(),
|
||||
lifecycleBinding: VideoCallLifecycleBinding(),
|
||||
audioSessionCoordinator: audioCoordinator,
|
||||
);
|
||||
when(mockRoom.connectionState).thenReturn(lk.ConnectionState.connected);
|
||||
when(mockRoom.localParticipant).thenReturn(mockLocalParticipant);
|
||||
when(mockLocalParticipant.setMicrophoneEnabled(any, audioCaptureOptions: anyNamed('audioCaptureOptions'))).thenAnswer((_) async {
|
||||
return null;
|
||||
});
|
||||
when(mockLocalParticipant.getTrackPublicationBySource(lk.TrackSource.microphone)).thenReturn(null);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
manager.dispose();
|
||||
});
|
||||
|
||||
test('duck interruption mutes mic and sets interrupted UI state', () async {
|
||||
manager.markActiveCallForTesting(mockRoom);
|
||||
|
||||
manager.handleAudioInterruptionForTesting(
|
||||
AudioInterruptionEvent(true, AudioInterruptionType.duck),
|
||||
);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(manager.snapshot.systemAudioInterrupted, isTrue);
|
||||
verify(
|
||||
mockLocalParticipant.setMicrophoneEnabled(
|
||||
false,
|
||||
audioCaptureOptions: anyNamed('audioCaptureOptions'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('interruption begin mutes mic and sets interrupted state', () async {
|
||||
manager.markActiveCallForTesting(mockRoom);
|
||||
|
||||
manager.handleAudioInterruptionForTesting(AudioInterruptionEvent(true, AudioInterruptionType.pause));
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(manager.snapshot.systemAudioInterrupted, isTrue);
|
||||
expect(manager.snapshot.micEnabled, isTrue);
|
||||
verify(mockLocalParticipant.setMicrophoneEnabled(false, audioCaptureOptions: anyNamed('audioCaptureOptions'))).called(1);
|
||||
verifyNever(mockRepository.notifyCallEnded(any));
|
||||
});
|
||||
|
||||
test('interruption end reactivates session and restores mic', () async {
|
||||
manager.markActiveCallForTesting(mockRoom);
|
||||
manager.handleAudioInterruptionForTesting(AudioInterruptionEvent(true, AudioInterruptionType.pause));
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
manager.handleAudioInterruptionForTesting(AudioInterruptionEvent(false, AudioInterruptionType.pause));
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(manager.snapshot.systemAudioInterrupted, isFalse);
|
||||
expect(audioCoordinator.reactivateCalled, isTrue);
|
||||
verify(mockLocalParticipant.setMicrophoneEnabled(true, audioCaptureOptions: anyNamed('audioCaptureOptions'))).called(1);
|
||||
});
|
||||
|
||||
test('toggleMic before call stage updates intent without livekit', () async {
|
||||
manager.markActiveCallForTesting(mockRoom);
|
||||
manager.setCallStageEnteredForTesting(false);
|
||||
|
||||
await manager.toggleMic(false);
|
||||
|
||||
expect(manager.snapshot.micEnabled, isTrue);
|
||||
verifyNever(
|
||||
mockLocalParticipant.setMicrophoneEnabled(
|
||||
any,
|
||||
audioCaptureOptions: anyNamed('audioCaptureOptions'),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'toggleMic from UI off while interrupted keeps interrupted and does not unmute',
|
||||
() async {
|
||||
manager.markActiveCallForTesting(mockRoom);
|
||||
manager.handleAudioInterruptionForTesting(
|
||||
AudioInterruptionEvent(true, AudioInterruptionType.pause),
|
||||
);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
clearInteractions(mockLocalParticipant);
|
||||
|
||||
await manager.toggleMic(false);
|
||||
|
||||
expect(manager.snapshot.systemAudioInterrupted, isTrue);
|
||||
expect(manager.snapshot.micEnabled, isTrue);
|
||||
verifyNever(
|
||||
mockLocalParticipant.setMicrophoneEnabled(
|
||||
true,
|
||||
audioCaptureOptions: anyNamed('audioCaptureOptions'),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('toggleMic during interruption disables intent and keeps mic paused', () async {
|
||||
manager.markActiveCallForTesting(mockRoom);
|
||||
manager.handleAudioInterruptionForTesting(AudioInterruptionEvent(true, AudioInterruptionType.pause));
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
clearInteractions(mockLocalParticipant);
|
||||
|
||||
await manager.toggleMic(true);
|
||||
|
||||
expect(manager.snapshot.micEnabled, isFalse);
|
||||
expect(manager.snapshot.systemAudioInterrupted, isTrue);
|
||||
verify(
|
||||
mockLocalParticipant.setMicrophoneEnabled(
|
||||
false,
|
||||
audioCaptureOptions: anyNamed('audioCaptureOptions'),
|
||||
),
|
||||
).called(1);
|
||||
verifyNever(
|
||||
mockLocalParticipant.setMicrophoneEnabled(
|
||||
true,
|
||||
audioCaptureOptions: anyNamed('audioCaptureOptions'),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,18 +3,19 @@ import 'package:eatvio_app/features/video_calls/managers/video_call_manager_snap
|
||||
import 'package:eatvio_app/features/video_calls/presentation/bloc/video_call_session/video_call_session_cubit.dart';
|
||||
import 'package:eatvio_app/features/video_calls/presentation/bloc/video_call_session/video_call_session_state.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:livekit_client/livekit_client.dart' as lk;
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'video_call_session_cubit_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<VideoCallManager>()])
|
||||
@GenerateNiceMocks([MockSpec<VideoCallManager>(), MockSpec<lk.Room>()])
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('VideoCallSessionCubit', () {
|
||||
late MockVideoCallManager mockManager;
|
||||
late VideoCallSessionCubit cubit;
|
||||
late _TestVideoCallSessionCubit cubit;
|
||||
|
||||
late VideoCallManagerListener managerListener;
|
||||
|
||||
@@ -26,7 +27,7 @@ void main() {
|
||||
managerListener =
|
||||
invocation.positionalArguments[0] as VideoCallManagerListener;
|
||||
});
|
||||
cubit = VideoCallSessionCubit(videoCallManager: mockManager);
|
||||
cubit = _TestVideoCallSessionCubit(videoCallManager: mockManager);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
@@ -120,12 +121,9 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
test('isMicActiveForUi is false when system audio interrupted', () {
|
||||
const state = VideoCallSessionState(
|
||||
micEnabled: true,
|
||||
systemAudioInterrupted: true,
|
||||
);
|
||||
expect(state.isMicActiveForUi, isFalse);
|
||||
test('isMicActiveForUi mirrors micEnabled', () {
|
||||
const state = VideoCallSessionState(micEnabled: true);
|
||||
expect(state.isMicActiveForUi, isTrue);
|
||||
});
|
||||
|
||||
test('onCallEndedExternally resets session state', () async {
|
||||
@@ -143,6 +141,213 @@ void main() {
|
||||
expect(cubit.state, const VideoCallSessionState());
|
||||
});
|
||||
|
||||
test('syncPipUiStateFromSystem skips overlay when call screen is visible', () async {
|
||||
cubit.emit(
|
||||
const VideoCallSessionState(
|
||||
conversationUuid: 'uuid',
|
||||
connectPhase: VideoCallConnectPhase.connected,
|
||||
callStageEntered: true,
|
||||
uiMode: VideoCallUiMode.fullscreen,
|
||||
),
|
||||
);
|
||||
|
||||
await cubit.onCallScreenVisible(conversationUuid: 'uuid');
|
||||
await cubit.syncPipUiStateFromSystem();
|
||||
|
||||
expect(cubit.state.uiMode, VideoCallUiMode.fullscreen);
|
||||
});
|
||||
|
||||
test('manager idle snapshot ignored while call screen is visible', () async {
|
||||
when(
|
||||
mockManager.startCall(
|
||||
conversationUuid: anyNamed('conversationUuid'),
|
||||
counterpartName: anyNamed('counterpartName'),
|
||||
localParticipantName: anyNamed('localParticipantName'),
|
||||
micEnabled: anyNamed('micEnabled'),
|
||||
videoEnabled: anyNamed('videoEnabled'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
await cubit.onCallScreenVisible(
|
||||
conversationUuid: 'uuid',
|
||||
entryMode: VideoCallEntryMode.incomingAccepted,
|
||||
);
|
||||
|
||||
managerListener(VideoCallManagerSnapshot.initial);
|
||||
|
||||
expect(cubit.state.entryMode, VideoCallEntryMode.incomingAccepted);
|
||||
expect(cubit.state.conversationUuid, 'uuid');
|
||||
});
|
||||
|
||||
test('incoming accept auto-enters call stage after connect', () async {
|
||||
when(
|
||||
mockManager.startCall(
|
||||
conversationUuid: anyNamed('conversationUuid'),
|
||||
counterpartName: anyNamed('counterpartName'),
|
||||
localParticipantName: anyNamed('localParticipantName'),
|
||||
micEnabled: anyNamed('micEnabled'),
|
||||
videoEnabled: anyNamed('videoEnabled'),
|
||||
),
|
||||
).thenAnswer((_) async {
|
||||
managerListener(
|
||||
const VideoCallManagerSnapshot(
|
||||
conversationUuid: 'uuid',
|
||||
micEnabled: false,
|
||||
videoEnabled: false,
|
||||
connectPhase: VideoCallConnectPhase.connected,
|
||||
linkIssue: VideoCallLinkIssue.none,
|
||||
),
|
||||
);
|
||||
});
|
||||
when(mockManager.onEnterCallStage()).thenAnswer((_) async => true);
|
||||
|
||||
await cubit.onCallScreenVisible(
|
||||
conversationUuid: 'uuid',
|
||||
entryMode: VideoCallEntryMode.incomingAccepted,
|
||||
);
|
||||
|
||||
verify(
|
||||
mockManager.startCall(
|
||||
conversationUuid: 'uuid',
|
||||
counterpartName: anyNamed('counterpartName'),
|
||||
localParticipantName: anyNamed('localParticipantName'),
|
||||
micEnabled: false,
|
||||
videoEnabled: false,
|
||||
),
|
||||
).called(1);
|
||||
verify(mockManager.onEnterCallStage()).called(1);
|
||||
expect(cubit.state.entryMode, VideoCallEntryMode.incomingAccepted);
|
||||
expect(cubit.state.callStageEntered, isTrue);
|
||||
});
|
||||
|
||||
test('outgoing call keeps lobby until callee joins', () async {
|
||||
when(
|
||||
mockManager.startCall(
|
||||
conversationUuid: anyNamed('conversationUuid'),
|
||||
counterpartName: anyNamed('counterpartName'),
|
||||
localParticipantName: anyNamed('localParticipantName'),
|
||||
micEnabled: anyNamed('micEnabled'),
|
||||
videoEnabled: anyNamed('videoEnabled'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
when(mockManager.ensureLobbyRoomJoined()).thenAnswer((_) async {});
|
||||
when(mockManager.onEnterCallStage()).thenAnswer((_) async => true);
|
||||
|
||||
await cubit.onCallScreenVisible(conversationUuid: 'uuid');
|
||||
|
||||
verifyNever(mockManager.onEnterCallStage());
|
||||
expect(cubit.state.entryMode, VideoCallEntryMode.outgoing);
|
||||
expect(cubit.state.callStageEntered, isFalse);
|
||||
expect(cubit.state.micEnabled, isFalse);
|
||||
|
||||
managerListener(
|
||||
const VideoCallManagerSnapshot(
|
||||
conversationUuid: 'uuid',
|
||||
micEnabled: false,
|
||||
videoEnabled: false,
|
||||
connectPhase: VideoCallConnectPhase.connected,
|
||||
linkIssue: VideoCallLinkIssue.none,
|
||||
),
|
||||
);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
verify(mockManager.ensureLobbyRoomJoined()).called(1);
|
||||
verifyNever(mockManager.onEnterCallStage());
|
||||
});
|
||||
|
||||
test('outgoing call auto-enters when callee joins lobby room', () async {
|
||||
when(
|
||||
mockManager.startCall(
|
||||
conversationUuid: anyNamed('conversationUuid'),
|
||||
counterpartName: anyNamed('counterpartName'),
|
||||
localParticipantName: anyNamed('localParticipantName'),
|
||||
micEnabled: anyNamed('micEnabled'),
|
||||
videoEnabled: anyNamed('videoEnabled'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
when(mockManager.ensureLobbyRoomJoined()).thenAnswer((_) async {});
|
||||
when(mockManager.onEnterCallStage()).thenAnswer((_) async => true);
|
||||
|
||||
await cubit.onCallScreenVisible(conversationUuid: 'uuid');
|
||||
|
||||
managerListener(
|
||||
const VideoCallManagerSnapshot(
|
||||
conversationUuid: 'uuid',
|
||||
micEnabled: false,
|
||||
videoEnabled: false,
|
||||
connectPhase: VideoCallConnectPhase.connected,
|
||||
linkIssue: VideoCallLinkIssue.none,
|
||||
counterpartPresentInLobby: true,
|
||||
),
|
||||
);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
verify(mockManager.onEnterCallStage()).called(1);
|
||||
expect(cubit.state.callStageEntered, isTrue);
|
||||
});
|
||||
|
||||
test('duplicate onCallScreenVisible does not restart connecting call', () async {
|
||||
when(
|
||||
mockManager.startCall(
|
||||
conversationUuid: anyNamed('conversationUuid'),
|
||||
counterpartName: anyNamed('counterpartName'),
|
||||
localParticipantName: anyNamed('localParticipantName'),
|
||||
micEnabled: anyNamed('micEnabled'),
|
||||
videoEnabled: anyNamed('videoEnabled'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
await cubit.onCallScreenVisible(
|
||||
conversationUuid: 'uuid',
|
||||
entryMode: VideoCallEntryMode.incomingAccepted,
|
||||
);
|
||||
await cubit.onCallScreenVisible(
|
||||
conversationUuid: 'uuid',
|
||||
entryMode: VideoCallEntryMode.outgoing,
|
||||
);
|
||||
|
||||
verify(
|
||||
mockManager.startCall(
|
||||
conversationUuid: 'uuid',
|
||||
counterpartName: anyNamed('counterpartName'),
|
||||
localParticipantName: anyNamed('localParticipantName'),
|
||||
micEnabled: false,
|
||||
videoEnabled: false,
|
||||
),
|
||||
).called(1);
|
||||
expect(cubit.state.entryMode, VideoCallEntryMode.incomingAccepted);
|
||||
});
|
||||
|
||||
test('leaveCallRoute minimizes to pip and marks call screen not visible', () async {
|
||||
final mockRoom = MockRoom();
|
||||
when(mockManager.room).thenReturn(mockRoom);
|
||||
when(
|
||||
mockManager.onCallScreenVisible(
|
||||
conversationUuid: anyNamed('conversationUuid'),
|
||||
counterpartName: anyNamed('counterpartName'),
|
||||
localParticipantName: anyNamed('localParticipantName'),
|
||||
micEnabled: anyNamed('micEnabled'),
|
||||
videoEnabled: anyNamed('videoEnabled'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
cubit.emit(
|
||||
const VideoCallSessionState(
|
||||
conversationUuid: 'uuid',
|
||||
connectPhase: VideoCallConnectPhase.connected,
|
||||
callStageEntered: true,
|
||||
uiMode: VideoCallUiMode.fullscreen,
|
||||
),
|
||||
);
|
||||
await cubit.onCallScreenVisible(conversationUuid: 'uuid');
|
||||
expect(cubit.isCallScreenRouteVisible, isTrue);
|
||||
|
||||
await cubit.leaveCallRoute();
|
||||
|
||||
expect(cubit.state.uiMode, VideoCallUiMode.pip);
|
||||
expect(cubit.isCallScreenRouteVisible, isFalse);
|
||||
});
|
||||
|
||||
test('tracks whether call screen route is visible', () async {
|
||||
when(
|
||||
mockManager.startCall(
|
||||
@@ -171,3 +376,12 @@ void main() {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class _TestVideoCallSessionCubit extends VideoCallSessionCubit {
|
||||
_TestVideoCallSessionCubit({required super.videoCallManager});
|
||||
|
||||
bool grantMicOnRequest = false;
|
||||
|
||||
@override
|
||||
Future<bool> requestMicPermissionForCall() async => grantMicOnRequest;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import 'video_call_livekit_track_utils_test.mocks.dart';
|
||||
MockSpec<lk.Room>(),
|
||||
MockSpec<lk.LocalParticipant>(),
|
||||
MockSpec<lk.RemoteParticipant>(),
|
||||
MockSpec<lk.RemoteTrackPublication>(),
|
||||
MockSpec<lk.RemoteVideoTrack>(),
|
||||
MockSpec<lk.TrackPublication>(),
|
||||
MockSpec<lk.VideoTrack>(),
|
||||
])
|
||||
@@ -67,6 +69,33 @@ void main() {
|
||||
expect(VideoCallLiveKitTrackUtils.visibleVideoFromPublication(pub), isNull);
|
||||
});
|
||||
|
||||
test('visibleVideoFromPublication возвращает remote track при paused stream', () {
|
||||
final pub = MockRemoteTrackPublication();
|
||||
final track = MockRemoteVideoTrack();
|
||||
when(pub.muted).thenReturn(false);
|
||||
when(pub.subscribed).thenReturn(true);
|
||||
when(pub.streamState).thenReturn(lk.StreamState.paused);
|
||||
when(pub.track).thenReturn(track);
|
||||
when(track.muted).thenReturn(false);
|
||||
|
||||
expect(VideoCallLiveKitTrackUtils.visibleVideoFromPublication(pub), equals(track));
|
||||
});
|
||||
|
||||
test('visibleVideoFromPublication возвращает null для unsubscribed remote', () {
|
||||
final pub = MockRemoteTrackPublication();
|
||||
when(pub.muted).thenReturn(false);
|
||||
when(pub.subscribed).thenReturn(false);
|
||||
|
||||
expect(VideoCallLiveKitTrackUtils.visibleVideoFromPublication(pub), isNull);
|
||||
});
|
||||
|
||||
test('isSoloInRoom is true when remote participants are empty', () {
|
||||
final room = MockRoom();
|
||||
when(room.remoteParticipants).thenReturn(UnmodifiableMapView(<String, lk.RemoteParticipant>{}));
|
||||
|
||||
expect(VideoCallLiveKitTrackUtils.isSoloInRoom(room), isTrue);
|
||||
});
|
||||
|
||||
test('localCameraVideoTrack возвращает null если localParticipant отсутствует', () {
|
||||
final room = MockRoom();
|
||||
when(room.localParticipant).thenReturn(null);
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'video_call_stage_layout_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<lk.Room>()])
|
||||
@GenerateNiceMocks([MockSpec<lk.Room>(), MockSpec<lk.RemoteParticipant>()])
|
||||
void main() {
|
||||
Widget wrap(Widget child) {
|
||||
return MaterialApp(
|
||||
@@ -27,8 +27,19 @@ void main() {
|
||||
return room;
|
||||
}
|
||||
|
||||
MockRoom buildRoomWithRemote() {
|
||||
final room = MockRoom();
|
||||
final remote = MockRemoteParticipant();
|
||||
when(room.remoteParticipants).thenReturn(
|
||||
UnmodifiableMapView(<String, lk.RemoteParticipant>{'remote': remote}),
|
||||
);
|
||||
when(room.localParticipant).thenReturn(null);
|
||||
when(room.connectionState).thenReturn(lk.ConnectionState.connected);
|
||||
return room;
|
||||
}
|
||||
|
||||
testWidgets('renders split mode participant names and controls', (tester) async {
|
||||
final room = buildRoom();
|
||||
final room = buildRoomWithRemote();
|
||||
var localExpanded = 0;
|
||||
var remoteExpanded = 0;
|
||||
|
||||
@@ -114,7 +125,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('split layout hides connectError on remote pane when connected', (tester) async {
|
||||
final room = buildRoom();
|
||||
final room = buildRoomWithRemote();
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
@@ -150,7 +161,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('fullscreen layout shows remote name when main stage is remote', (tester) async {
|
||||
final room = buildRoom();
|
||||
final room = buildRoomWithRemote();
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
@@ -186,7 +197,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('split layout with video on shows camera switch on local pane', (tester) async {
|
||||
final room = buildRoom();
|
||||
final room = buildRoomWithRemote();
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
@@ -222,7 +233,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('split layout with rear camera and remote main stage variant', (tester) async {
|
||||
final room = buildRoom();
|
||||
final room = buildRoomWithRemote();
|
||||
var stageSwapped = 0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
@@ -299,8 +310,131 @@ void main() {
|
||||
expect(find.text('Leave'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('fullscreen layout with connect error shows it on stage', (tester) async {
|
||||
testWidgets('fullscreen layout shows local PIP when camera is off', (tester) async {
|
||||
final room = buildRoomWithRemote();
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
VideoCallStageLayout(
|
||||
room: room,
|
||||
connectError: null,
|
||||
localDisplayName: 'You',
|
||||
remoteDisplayName: 'Doctor',
|
||||
micEnabled: true,
|
||||
videoEnabled: false,
|
||||
splitParticipantView: false,
|
||||
mainStageShowsLocalVideo: false,
|
||||
useFrontCamera: true,
|
||||
onExpandLocal: () {},
|
||||
onExpandRemote: () {},
|
||||
onShrinkFromFullscreen: () {},
|
||||
onToggleStageSwap: () {},
|
||||
onChat: () {},
|
||||
onToggleMic: () {},
|
||||
onToggleVideo: () {},
|
||||
onHangUp: () {},
|
||||
onSwitchCamera: () {},
|
||||
controlLabelStyle: const TextStyle(fontSize: 14),
|
||||
labels: (chat: 'Chat', sound: 'Sound', video: 'Video', leave: 'Leave'),
|
||||
topInset: 0,
|
||||
bottomInset: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
expect(find.byType(AnimatedContainer), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('fullscreen layout shows only local participant when solo in room', (tester) async {
|
||||
final room = buildRoom();
|
||||
var stageSwapped = 0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
VideoCallStageLayout(
|
||||
room: room,
|
||||
connectError: null,
|
||||
localDisplayName: 'You',
|
||||
remoteDisplayName: 'Doctor',
|
||||
micEnabled: true,
|
||||
videoEnabled: false,
|
||||
splitParticipantView: false,
|
||||
mainStageShowsLocalVideo: false,
|
||||
useFrontCamera: true,
|
||||
onExpandLocal: () {},
|
||||
onExpandRemote: () {},
|
||||
onShrinkFromFullscreen: () {},
|
||||
onToggleStageSwap: () => stageSwapped++,
|
||||
onChat: () {},
|
||||
onToggleMic: () {},
|
||||
onToggleVideo: () {},
|
||||
onHangUp: () {},
|
||||
onSwitchCamera: () {},
|
||||
controlLabelStyle: const TextStyle(fontSize: 14),
|
||||
labels: (chat: 'Chat', sound: 'Sound', video: 'Video', leave: 'Leave'),
|
||||
topInset: 8,
|
||||
bottomInset: 32,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
expect(find.text('You'), findsOneWidget);
|
||||
expect(find.text('Doctor'), findsNothing);
|
||||
expect(find.byType(AnimatedContainer), findsNothing);
|
||||
expect(stageSwapped, 0);
|
||||
});
|
||||
|
||||
testWidgets('split layout places remote above local with 70/30 height ratio', (tester) async {
|
||||
final room = buildRoomWithRemote();
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
VideoCallStageLayout(
|
||||
room: room,
|
||||
connectError: null,
|
||||
localDisplayName: 'You',
|
||||
remoteDisplayName: 'Doctor',
|
||||
micEnabled: true,
|
||||
videoEnabled: false,
|
||||
splitParticipantView: true,
|
||||
mainStageShowsLocalVideo: false,
|
||||
useFrontCamera: true,
|
||||
onExpandLocal: () {},
|
||||
onExpandRemote: () {},
|
||||
onShrinkFromFullscreen: () {},
|
||||
onToggleStageSwap: () {},
|
||||
onChat: () {},
|
||||
onToggleMic: () {},
|
||||
onToggleVideo: () {},
|
||||
onHangUp: () {},
|
||||
onSwitchCamera: () {},
|
||||
controlLabelStyle: const TextStyle(fontSize: 14),
|
||||
labels: (chat: 'Chat', sound: 'Sound', video: 'Video', leave: 'Leave'),
|
||||
topInset: 0,
|
||||
bottomInset: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
|
||||
final remoteTop = tester.getTopLeft(find.text('Doctor')).dy;
|
||||
final localTop = tester.getTopLeft(find.text('You')).dy;
|
||||
expect(remoteTop, lessThan(localTop));
|
||||
|
||||
final remotePane = tester.getSize(
|
||||
find.ancestor(of: find.text('Doctor'), matching: find.byType(Material)).first,
|
||||
);
|
||||
final localPane = tester.getSize(
|
||||
find.ancestor(of: find.text('You'), matching: find.byType(Material)).first,
|
||||
);
|
||||
expect(remotePane.height / localPane.height, closeTo(7 / 3, 0.05));
|
||||
});
|
||||
|
||||
testWidgets('split layout with connect error shows it on stage', (tester) async {
|
||||
final room = buildRoomWithRemote();
|
||||
when(room.connectionState).thenReturn(lk.ConnectionState.disconnected);
|
||||
|
||||
await tester.pumpWidget(
|
||||
|
||||
Reference in New Issue
Block a user