import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:plezy/i18n/strings.g.dart'; import 'package:plezy/watch_together/models/watch_session.dart'; import 'package:plezy/watch_together/providers/watch_together_provider.dart'; import 'package:plezy/watch_together/widgets/watch_together_overlay.dart'; import 'package:plezy/widgets/overlay_sheet.dart'; import 'package:provider/provider.dart'; void main() { setUpAll(() => LocaleSettings.setLocaleSync(AppLocale.en)); for (final isHost in [false, true]) { final role = isHost ? 'host' : 'guest'; testWidgets('$role confirmation survives the session sheet closing', (tester) async { final harness = _OverlayHarness(isHost: isHost); addTearDown(harness.dispose); await tester.pumpWidget(harness.build()); await _openLeaveConfirmation(tester, harness); harness.sheetController.close(); await tester.pumpAndSettle(); expect(find.text(t.watchTogether.title), findsNothing); expect( find.text(isHost ? t.watchTogether.endSessionQuestion : t.watchTogether.leaveSessionQuestion), findsOneWidget, ); await tester.tap(find.text(isHost ? t.watchTogether.endSession : t.watchTogether.leave)); await tester.pumpAndSettle(); expect(harness.provider.leaveCalls, 1); expect(harness.onLeaveSessionCalls, 1); }); testWidgets('$role cancellation remains a no-op after the session sheet closes', (tester) async { final harness = _OverlayHarness(isHost: isHost); addTearDown(harness.dispose); await tester.pumpWidget(harness.build()); await _openLeaveConfirmation(tester, harness); harness.sheetController.close(); await tester.pumpAndSettle(); expect( find.text(isHost ? t.watchTogether.endSessionQuestion : t.watchTogether.leaveSessionQuestion), findsOneWidget, ); await tester.tap(find.text(t.common.cancel)); await tester.pumpAndSettle(); expect(harness.provider.leaveCalls, 0); expect(harness.onLeaveSessionCalls, 0); }); } testWidgets('best-effort leave failure is handled by the overlay', (tester) async { final harness = _OverlayHarness(isHost: false, leaveError: StateError('release failed')); addTearDown(harness.dispose); await tester.pumpWidget(harness.build()); await _openLeaveConfirmation(tester, harness); await tester.tap(find.text(t.watchTogether.leave)); await tester.pumpAndSettle(); expect(harness.provider.leaveCalls, 1); expect(harness.onLeaveSessionCalls, 1); expect(tester.takeException(), isNull); }); } Future _openLeaveConfirmation(WidgetTester tester, _OverlayHarness harness) async { await tester.tap(find.byKey(_OverlayHarness.indicatorKey)); await tester.pumpAndSettle(); expect(harness.sheetController.isOpen, isTrue); await tester.tap(find.text(harness.provider.isHost ? t.watchTogether.endSession : t.watchTogether.leaveSession)); await tester.pumpAndSettle(); expect(find.byType(AlertDialog), findsOneWidget); } class _OverlayHarness { _OverlayHarness({required bool isHost, Object? leaveError}) : provider = _FakeWatchTogetherProvider(isHostValue: isHost, leaveError: leaveError); static const indicatorKey = Key('watch-together-session-indicator'); final _FakeWatchTogetherProvider provider; late OverlaySheetController sheetController; int onLeaveSessionCalls = 0; Widget build() { return ChangeNotifierProvider.value( value: provider, child: MaterialApp( home: OverlaySheetHost( child: Builder( builder: (context) { sheetController = OverlaySheetController.of(context); return Scaffold( body: Center( child: WatchTogetherSessionIndicator(key: indicatorKey, onLeaveSession: () => onLeaveSessionCalls++), ), ); }, ), ), ), ); } void dispose() => provider.dispose(); } class _FakeWatchTogetherProvider extends WatchTogetherProvider { _FakeWatchTogetherProvider({required this.isHostValue, this.leaveError}); final bool isHostValue; final Object? leaveError; var leaveCalls = 0; var _isDisposing = false; @override bool get isHost => isHostValue; @override String? get sessionId => 'ROOM42'; @override ControlMode get controlMode => ControlMode.hostOnly; @override List get participants => [ Participant(peerId: 'local', displayName: 'Local viewer', isHost: isHostValue), ]; @override int get participantCount => participants.length; @override Future leaveSession() async { if (!_isDisposing) leaveCalls++; final error = leaveError; if (error != null) throw error; } @override void dispose() { _isDisposing = true; super.dispose(); } }