import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:tasq/models/office.dart'; import 'package:tasq/models/profile.dart'; import 'package:tasq/models/team.dart'; import 'package:tasq/models/team_member.dart'; import 'package:tasq/providers/teams_provider.dart'; import 'package:tasq/providers/profile_provider.dart'; import 'package:tasq/providers/tickets_provider.dart'; import 'package:tasq/screens/teams/teams_screen.dart'; import 'package:tasq/providers/supabase_provider.dart'; import 'package:tasq/widgets/multi_select_picker.dart'; SupabaseClient _fakeSupabaseClient() => SupabaseClient('http://localhost', 'test-key', authOptions: const AuthClientOptions(autoRefreshToken: false)); void main() { final office = Office(id: 'office-1', name: 'HQ'); final admin = Profile(id: 'user-1', role: 'admin', fullName: 'Alex Admin'); final tech = Profile(id: 'user-2', role: 'it_staff', fullName: 'Jamie Tech'); List baseOverrides() { return [ supabaseClientProvider.overrideWithValue(_fakeSupabaseClient()), currentProfileProvider.overrideWith((ref) => Stream.value(admin)), profilesProvider.overrideWith((ref) => Stream.value([admin, tech])), officesProvider.overrideWith((ref) => Stream.value([office])), teamsProvider.overrideWith((ref) => Stream.value(const [])), teamMembersProvider.overrideWith( (ref) => Stream.value(const []), ), ]; } testWidgets('Add Team dialog: leader dropdown shows only it_staff', ( WidgetTester tester, ) async { await tester.binding.setSurfaceSize(const Size(600, 960)); addTearDown(() async => await tester.binding.setSurfaceSize(null)); await tester.pumpWidget( ProviderScope( overrides: baseOverrides(), child: const MaterialApp(home: Scaffold(body: TeamsScreen())), ), ); // Let M3Fab scale animation complete before tapping await tester.pumpAndSettle(); // Open Add Team dialog await tester.tap(find.byType(FloatingActionButton)); await tester.pumpAndSettle(); // Open the dropdown overlay and assert only the it_staff option is shown. final leaderDropdown = find.widgetWithText( DropdownButtonFormField, 'Team Leader', ); expect(leaderDropdown, findsOneWidget); await tester.tap(leaderDropdown); await tester.pumpAndSettle(); // The dropdown overlay should show the it_staff option and not the admin. expect(find.text('Jamie Tech'), findsOneWidget); expect(find.text('Alex Admin'), findsNothing); }); testWidgets('Add Team dialog: Team Members picker shows only it_staff', ( WidgetTester tester, ) async { await tester.binding.setSurfaceSize(const Size(600, 960)); addTearDown(() async => await tester.binding.setSurfaceSize(null)); await tester.pumpWidget( ProviderScope( overrides: baseOverrides(), child: const MaterialApp(home: Scaffold(body: TeamsScreen())), ), ); // Let M3Fab scale animation complete before tapping await tester.pumpAndSettle(); // Open Add Team dialog await tester.tap(find.byType(FloatingActionButton)); await tester.pumpAndSettle(); // Inspect the MultiSelectPicker widget for 'Team Members' directly final pickerFinder = find.byWidgetPredicate( (w) => w is MultiSelectPicker && w.label == 'Team Members', ); expect(pickerFinder, findsOneWidget); final picker = tester.widget>(pickerFinder); final labels = picker.items.map(picker.getLabel).toList(); expect(labels, contains('Jamie Tech')); expect(labels, isNot(contains('Alex Admin'))); }); testWidgets( 'Add Team dialog uses fixed width on desktop and bottom-sheet on mobile', (WidgetTester tester) async { // Desktop -> AlertDialog constrained to max width await tester.binding.setSurfaceSize(const Size(1280, 900)); addTearDown(() async => await tester.binding.setSurfaceSize(null)); await tester.pumpWidget( ProviderScope( overrides: baseOverrides(), child: const MaterialApp(home: Scaffold(body: TeamsScreen())), ), ); await tester.pumpAndSettle(); await tester.tap(find.byType(FloatingActionButton)); await tester.pumpAndSettle(); expect(find.byType(AlertDialog), findsOneWidget); final dialogSize = tester.getSize(find.byType(AlertDialog)); expect(dialogSize.width, lessThanOrEqualTo(720)); // Close desktop dialog await tester.tap(find.text('Cancel')); await tester.pumpAndSettle(); // Mobile -> bottom sheet or dialog presentation (phone-sized screen) await tester.binding.setSurfaceSize(const Size(480, 960)); await tester.pumpWidget( ProviderScope( overrides: baseOverrides(), child: const MaterialApp(home: Scaffold(body: TeamsScreen())), ), ); await tester.pumpAndSettle(); await tester.tap(find.byType(FloatingActionButton)); await tester.pumpAndSettle(); // On narrow widths the dialog renders as a BottomSheet; on wider ones as // an AlertDialog. Either way the form fields must be visible. expect(find.text('Team Name'), findsOneWidget); }, ); testWidgets('Edit Team dialog: Save button present and Cancel closes', ( WidgetTester tester, ) async { await tester.binding.setSurfaceSize(const Size(1280, 900)); addTearDown(() async => await tester.binding.setSurfaceSize(null)); final team = Team( id: 'team-1', name: 'Support', leaderId: 'user-2', officeIds: ['office-1'], createdAt: DateTime.now(), ); await tester.pumpWidget( ProviderScope( overrides: [ ...baseOverrides(), teamsProvider.overrideWith((ref) => Stream.value([team])), ], child: const MaterialApp(home: Scaffold(body: TeamsScreen())), ), ); // Wait for teams list to render before tapping the edit icon. await tester.pumpAndSettle(); // Tap edit and verify dialog shows Save button await tester.tap(find.byIcon(Icons.edit)); await tester.pumpAndSettle(); expect(find.text('Save'), findsOneWidget); // Cancel closes dialog await tester.tap(find.text('Cancel')); await tester.pumpAndSettle(); expect(find.text('Save'), findsNothing); }); }