Pagination
This commit is contained in:
@@ -11,14 +11,39 @@ import 'package:tasq/models/ticket.dart';
|
||||
import 'package:tasq/models/user_office.dart';
|
||||
import 'package:tasq/providers/notifications_provider.dart';
|
||||
import 'package:tasq/providers/profile_provider.dart';
|
||||
|
||||
import 'package:tasq/providers/tasks_provider.dart';
|
||||
import 'package:tasq/providers/tickets_provider.dart';
|
||||
import 'package:tasq/providers/typing_provider.dart';
|
||||
import 'package:tasq/providers/user_offices_provider.dart';
|
||||
import 'package:tasq/providers/supabase_provider.dart';
|
||||
import 'package:tasq/screens/admin/offices_screen.dart';
|
||||
import 'package:tasq/screens/admin/user_management_screen.dart';
|
||||
import 'package:tasq/screens/tasks/tasks_list_screen.dart';
|
||||
import 'package:tasq/screens/tickets/tickets_list_screen.dart';
|
||||
import 'package:tasq/screens/tickets/ticket_detail_screen.dart';
|
||||
|
||||
// Test double for NotificationsController so widget tests don't initialize
|
||||
// a real Supabase client.
|
||||
class FakeNotificationsController implements NotificationsController {
|
||||
@override
|
||||
Future<void> createMentionNotifications({
|
||||
required List<String> userIds,
|
||||
required String actorId,
|
||||
required int messageId,
|
||||
String? ticketId,
|
||||
String? taskId,
|
||||
}) async {}
|
||||
|
||||
@override
|
||||
Future<void> markRead(String id) async {}
|
||||
|
||||
@override
|
||||
Future<void> markReadForTicket(String ticketId) async {}
|
||||
|
||||
@override
|
||||
Future<void> markReadForTask(String taskId) async {}
|
||||
}
|
||||
|
||||
SupabaseClient _fakeSupabaseClient() {
|
||||
return SupabaseClient('http://localhost', 'test-key');
|
||||
@@ -69,8 +94,10 @@ void main() {
|
||||
|
||||
List<Override> baseOverrides() {
|
||||
return [
|
||||
supabaseClientProvider.overrideWithValue(_fakeSupabaseClient()),
|
||||
currentProfileProvider.overrideWith((ref) => Stream.value(admin)),
|
||||
profilesProvider.overrideWith((ref) => Stream.value([admin, tech])),
|
||||
|
||||
officesProvider.overrideWith((ref) => Stream.value([office])),
|
||||
notificationsProvider.overrideWith((ref) => Stream.value([notification])),
|
||||
ticketsProvider.overrideWith((ref) => Stream.value([ticket])),
|
||||
@@ -81,6 +108,9 @@ void main() {
|
||||
),
|
||||
ticketMessagesAllProvider.overrideWith((ref) => const Stream.empty()),
|
||||
isAdminProvider.overrideWith((ref) => true),
|
||||
notificationsControllerProvider.overrideWithValue(
|
||||
FakeNotificationsController(),
|
||||
),
|
||||
typingIndicatorProvider.overrideWithProvider(
|
||||
AutoDisposeStateNotifierProvider.family<
|
||||
TypingIndicatorController,
|
||||
@@ -114,8 +144,8 @@ void main() {
|
||||
const TicketsListScreen(),
|
||||
overrides: baseOverrides(),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 16));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 16));
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
@@ -126,8 +156,8 @@ void main() {
|
||||
const TasksListScreen(),
|
||||
overrides: baseOverrides(),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 16));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 16));
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
@@ -140,8 +170,8 @@ void main() {
|
||||
const OfficesScreen(),
|
||||
overrides: baseOverrides(),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 16));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 16));
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
@@ -154,8 +184,38 @@ void main() {
|
||||
const UserManagementScreen(),
|
||||
overrides: userManagementOverrides(),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 16));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 16));
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('Typing indicator: no post-dispose state mutation', (
|
||||
tester,
|
||||
) async {
|
||||
await _setSurfaceSize(tester, const Size(600, 800));
|
||||
|
||||
// Show TicketDetailScreen with the base overrides (includes typing controller).
|
||||
await _pumpScreen(
|
||||
tester,
|
||||
const TicketDetailScreen(ticketId: 'TCK-1'),
|
||||
overrides: baseOverrides(),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
|
||||
// Find message TextField and simulate typing.
|
||||
final finder = find.byType(TextField);
|
||||
expect(finder, findsWidgets);
|
||||
await tester.enterText(finder.first, 'Hello');
|
||||
|
||||
// Immediately remove the screen (navigate away / dispose).
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
// Let pending timers (typing stop, remote timeouts) run.
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// No unhandled exceptions should have been thrown.
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:tasq/providers/typing_provider.dart';
|
||||
|
||||
void main() {
|
||||
test(
|
||||
'TypingIndicatorController ignores late remote events after dispose',
|
||||
() async {
|
||||
final client = SupabaseClient('http://localhost', 'test-key');
|
||||
final controller = TypingIndicatorController(client, 'ticket-1');
|
||||
|
||||
// initial state should be empty
|
||||
expect(controller.state.userIds, isEmpty);
|
||||
|
||||
// Simulate a remote "start typing" arriving while alive
|
||||
controller.debugSimulateRemoteTyping('user-2');
|
||||
expect(controller.state.userIds, contains('user-2'));
|
||||
|
||||
// Dispose the controller
|
||||
controller.dispose();
|
||||
|
||||
// Calling the remote handlers after dispose must NOT throw and must be no-ops
|
||||
expect(
|
||||
() => controller.debugSimulateRemoteTyping('user-3'),
|
||||
returnsNormally,
|
||||
);
|
||||
expect(
|
||||
() => controller.debugSimulateRemoteTyping('user-2', stop: true),
|
||||
returnsNormally,
|
||||
);
|
||||
|
||||
// Ensure state was not modified after dispose
|
||||
expect(controller.mounted, isFalse);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user