import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:provider/provider.dart'; import 'package:yumi/app/constants/sc_room_msg_type.dart'; import 'package:yumi/modules/room/seat/sc_seat_item.dart'; import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/shared/business_logic/models/res/join_room_res.dart'; import 'package:yumi/shared/business_logic/models/res/login_res.dart'; import 'package:yumi/shared/business_logic/models/res/mic_res.dart'; import 'package:yumi/shared/business_logic/models/res/room_res.dart'; import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart'; import 'package:yumi/ui_kit/widgets/room/seat/room_seat_widget.dart'; void main() { test('starPlayEmoji creates a new event for repeated same emoji path', () { final rtcProvider = RealTimeCommunicationManager() ..roomWheatMap = { 1: MicRes(micIndex: 1, user: SocialChatUserProfile(id: 'user-1')), }; final msg = Msg( groupId: 'room-1', msg: 'sc_images/room/emoji/fluent_emoji_01.webp', type: SCRoomMsgType.emoticons, number: 1, ); rtcProvider.starPlayEmoji(msg); final firstVersion = rtcProvider.emojiEventVersionForSeat(1); expect(firstVersion, greaterThan(0)); expect(rtcProvider.roomWheatMap[1]?.emojiPath, msg.msg); rtcProvider.consumeSeatEmojiEvent(1, firstVersion); expect(rtcProvider.roomWheatMap[1]?.emojiPath, isNull); rtcProvider.starPlayEmoji(msg); final secondVersion = rtcProvider.emojiEventVersionForSeat(1); expect(secondVersion, greaterThan(firstVersion)); expect(rtcProvider.roomWheatMap[1]?.emojiPath, msg.msg); }); testWidgets( 'RoomSeatWidget falls back to configured mikeSize when mic list is empty', (tester) async { await tester.binding.setSurfaceSize(const Size(1600, 1200)); addTearDown(() async { await tester.binding.setSurfaceSize(null); }); final rtcProvider = RealTimeCommunicationManager() ..currenRoom = JoinRoomRes( roomProfile: RoomProfile(roomSetting: RoomSetting(mikeSize: 10)), ); await tester.pumpWidget( ScreenUtilInit( designSize: const Size(375, 812), builder: (_, __) => ChangeNotifierProvider.value( value: rtcProvider, child: MaterialApp(home: Scaffold(body: RoomSeatWidget())), ), ), ); expect(find.byType(RoomSeatWidget), findsOneWidget); expect(find.byType(SCSeatItem), findsNWidgets(10)); }, ); testWidgets( 'RoomSeatWidget uses configured mikeSize after expanding the room model', (tester) async { await tester.binding.setSurfaceSize(const Size(1600, 1200)); addTearDown(() async { await tester.binding.setSurfaceSize(null); }); final rtcProvider = RealTimeCommunicationManager() ..currenRoom = JoinRoomRes( roomProfile: RoomProfile(roomSetting: RoomSetting(mikeSize: 20)), ) ..roomWheatMap = { 0: MicRes(micIndex: 0, user: SocialChatUserProfile(id: 'user-1')), 1: MicRes(micIndex: 1, user: SocialChatUserProfile(id: 'user-2')), 2: MicRes(micIndex: 2), 3: MicRes(micIndex: 3), 4: MicRes(micIndex: 4), }; await tester.pumpWidget( ScreenUtilInit( designSize: const Size(375, 812), builder: (_, __) => ChangeNotifierProvider.value( value: rtcProvider, child: MaterialApp(home: Scaffold(body: RoomSeatWidget())), ), ), ); expect(find.byType(SCSeatItem), findsNWidgets(20)); }, ); test( 'updateRoomSetting expands seat placeholders without clearing users', () { final rtcProvider = RealTimeCommunicationManager() ..currenRoom = JoinRoomRes( roomProfile: RoomProfile( roomSetting: RoomSetting(mikeSize: 5), roomProfile: RoomProfile2(id: 'room-1'), ), ) ..roomWheatMap = { 0: MicRes(micIndex: 0, user: SocialChatUserProfile(id: 'user-1')), 1: MicRes(micIndex: 1, user: SocialChatUserProfile(id: 'user-2')), }; rtcProvider.updateRoomSetting(RoomSetting(mikeSize: 20)); expect(rtcProvider.roomWheatMap[0]?.user?.id, 'user-1'); expect(rtcProvider.roomWheatMap[1]?.user?.id, 'user-2'); expect(rtcProvider.roomWheatMap[19]?.micIndex, 19); }, ); test('micChange keeps a recent up-mic snapshot over stale mic list data', () { final rtcProvider = RealTimeCommunicationManager(); final user = SocialChatUserProfile(id: 'user-1', userNickname: 'Alice'); rtcProvider.micChange([MicRes(micIndex: 0, user: user)]); expect(rtcProvider.roomWheatMap[0]?.user?.id, 'user-1'); rtcProvider.debugApplyMicListSnapshot([ MicRes(micIndex: 0), ], notifyIfUnchanged: false); expect(rtcProvider.roomWheatMap[0]?.user?.id, 'user-1'); }); test( 'micChange keeps a recent down-mic snapshot over stale mic list data', () { final rtcProvider = RealTimeCommunicationManager() ..roomWheatMap = { 0: MicRes( micIndex: 0, user: SocialChatUserProfile( id: 'user-1', userNickname: 'Alice', ), ), }; rtcProvider.micChange([MicRes(micIndex: 0)]); expect(rtcProvider.roomWheatMap[0]?.user, isNull); rtcProvider.debugApplyMicListSnapshot([ MicRes( micIndex: 0, user: SocialChatUserProfile(id: 'user-1', userNickname: 'Alice'), ), ], notifyIfUnchanged: false); expect(rtcProvider.roomWheatMap[0]?.user, isNull); }, ); testWidgets('RoomSeatWidget derives seat count from highest mic index', ( tester, ) async { await tester.binding.setSurfaceSize(const Size(1600, 1200)); addTearDown(() async { await tester.binding.setSurfaceSize(null); }); final rtcProvider = RealTimeCommunicationManager() ..currenRoom = JoinRoomRes(roomProfile: RoomProfile()) ..roomWheatMap = {19: MicRes(micIndex: 19)}; await tester.pumpWidget( ScreenUtilInit( designSize: const Size(375, 812), builder: (_, __) => ChangeNotifierProvider.value( value: rtcProvider, child: MaterialApp(home: Scaffold(body: RoomSeatWidget())), ), ), ); expect(find.byType(SCSeatItem), findsNWidgets(20)); }); testWidgets( 'RoomSeatWidget keeps default room seats visible while room data catches up', (tester) async { await tester.binding.setSurfaceSize(const Size(1600, 1200)); addTearDown(() async { await tester.binding.setSurfaceSize(null); }); final rtcProvider = RealTimeCommunicationManager() ..currenRoom = JoinRoomRes(roomProfile: RoomProfile()); await tester.pumpWidget( ScreenUtilInit( designSize: const Size(375, 812), builder: (_, __) => ChangeNotifierProvider.value( value: rtcProvider, child: MaterialApp(home: Scaffold(body: RoomSeatWidget())), ), ), ); expect(find.byType(SCSeatItem), findsNWidgets(10)); }, ); testWidgets('SCSeatItem keeps height stable when an empty seat gets a user', ( tester, ) async { await tester.binding.setSurfaceSize(const Size(1600, 1200)); addTearDown(() async { await tester.binding.setSurfaceSize(null); }); final rtcProvider = RealTimeCommunicationManager() ..currenRoom = JoinRoomRes( roomProfile: RoomProfile(roomSetting: RoomSetting(mikeSize: 10)), ) ..roomWheatMap = {0: MicRes(micIndex: 0)}; Future pumpSeat() { return tester.pumpWidget( ScreenUtilInit( designSize: const Size(375, 812), builder: (_, __) => ChangeNotifierProvider.value( value: rtcProvider, child: const MaterialApp( home: Scaffold(body: Center(child: SCSeatItem(index: 0))), ), ), ), ); } await pumpSeat(); final emptySeatSize = tester.getSize(find.byType(SCSeatItem)); rtcProvider.roomWheatMap = { 0: MicRes( micIndex: 0, user: SocialChatUserProfile( id: 'user-1', userAvatar: '', userNickname: 'Alice', heartbeatVal: 1234, ), ), }; await pumpSeat(); expect(tester.getSize(find.byType(SCSeatItem)), emptySeatSize); }); }