fix: auth token handling, add tests
- Read bearer token from set-auth-token header - Add mounted checks to prevent setState after dispose - Add mocktail for testing - Add widget tests for login, clients, events screens - Add unit tests for auth provider, API client - 110 tests passing
This commit is contained in:
217
test/features/clients/clients_screen_test.dart
Normal file
217
test/features/clients/clients_screen_test.dart
Normal file
@@ -0,0 +1,217 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:network_app/features/clients/presentation/clients_screen.dart';
|
||||
import 'package:network_app/shared/providers/auth_provider.dart';
|
||||
import 'package:network_app/shared/services/api_client.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockApiClient extends Mock implements ApiClient {}
|
||||
|
||||
void main() {
|
||||
late MockApiClient mockApiClient;
|
||||
|
||||
setUp(() {
|
||||
mockApiClient = MockApiClient();
|
||||
});
|
||||
|
||||
Widget createTestWidget() {
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
apiClientProvider.overrideWithValue(mockApiClient),
|
||||
],
|
||||
child: MaterialApp(
|
||||
home: const ClientsScreen(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final testClients = [
|
||||
{
|
||||
'id': '1',
|
||||
'firstName': 'John',
|
||||
'lastName': 'Doe',
|
||||
'email': 'john@example.com',
|
||||
'company': 'Acme Corp',
|
||||
'tags': ['vip', 'active'],
|
||||
},
|
||||
{
|
||||
'id': '2',
|
||||
'firstName': 'Jane',
|
||||
'lastName': 'Smith',
|
||||
'email': 'jane@example.com',
|
||||
'company': 'Tech Inc',
|
||||
'tags': ['new'],
|
||||
},
|
||||
];
|
||||
|
||||
group('ClientsScreen Widget Tests', () {
|
||||
testWidgets('shows loading indicator initially', (tester) async {
|
||||
final completer = Completer<List<Map<String, dynamic>>>();
|
||||
when(() => mockApiClient.getClients(search: any(named: 'search')))
|
||||
.thenAnswer((_) => completer.future);
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
||||
|
||||
// Complete the future to cleanup
|
||||
completer.complete(testClients);
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('displays client list', (tester) async {
|
||||
when(() => mockApiClient.getClients(search: any(named: 'search')))
|
||||
.thenAnswer((_) async => testClients);
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('John Doe'), findsOneWidget);
|
||||
expect(find.text('Jane Smith'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('displays company names', (tester) async {
|
||||
when(() => mockApiClient.getClients(search: any(named: 'search')))
|
||||
.thenAnswer((_) async => testClients);
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Acme Corp'), findsOneWidget);
|
||||
expect(find.text('Tech Inc'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('displays client tags', (tester) async {
|
||||
when(() => mockApiClient.getClients(search: any(named: 'search')))
|
||||
.thenAnswer((_) async => testClients);
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('vip'), findsOneWidget);
|
||||
expect(find.text('active'), findsOneWidget);
|
||||
expect(find.text('new'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows empty state when no clients', (tester) async {
|
||||
when(() => mockApiClient.getClients(search: any(named: 'search')))
|
||||
.thenAnswer((_) async => []);
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('No clients yet'), findsOneWidget);
|
||||
expect(find.text('Add Client'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows search bar', (tester) async {
|
||||
when(() => mockApiClient.getClients(search: any(named: 'search')))
|
||||
.thenAnswer((_) async => testClients);
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(TextField), findsOneWidget);
|
||||
expect(find.text('Search clients...'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('has floating action button', (tester) async {
|
||||
when(() => mockApiClient.getClients(search: any(named: 'search')))
|
||||
.thenAnswer((_) async => testClients);
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(FloatingActionButton), findsOneWidget);
|
||||
expect(find.byIcon(Icons.add), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows error state on API failure', (tester) async {
|
||||
when(() => mockApiClient.getClients(search: any(named: 'search')))
|
||||
.thenThrow(Exception('Network error'));
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Failed to load clients'), findsOneWidget);
|
||||
expect(find.text('Retry'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('displays client initials in avatar', (tester) async {
|
||||
when(() => mockApiClient.getClients(search: any(named: 'search')))
|
||||
.thenAnswer((_) async => testClients);
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('JD'), findsOneWidget); // John Doe
|
||||
expect(find.text('JS'), findsOneWidget); // Jane Smith
|
||||
});
|
||||
|
||||
testWidgets('search filters results', (tester) async {
|
||||
when(() => mockApiClient.getClients(search: null))
|
||||
.thenAnswer((_) async => testClients);
|
||||
when(() => mockApiClient.getClients(search: 'John'))
|
||||
.thenAnswer((_) async => [testClients[0]]);
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Initial state shows all clients
|
||||
expect(find.text('John Doe'), findsOneWidget);
|
||||
expect(find.text('Jane Smith'), findsOneWidget);
|
||||
|
||||
// Enter search
|
||||
await tester.enterText(find.byType(TextField), 'John');
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should only show John
|
||||
expect(find.text('John Doe'), findsOneWidget);
|
||||
expect(find.text('Jane Smith'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('shows no results message for search', (tester) async {
|
||||
when(() => mockApiClient.getClients(search: null))
|
||||
.thenAnswer((_) async => testClients);
|
||||
when(() => mockApiClient.getClients(search: 'xyz'))
|
||||
.thenAnswer((_) async => []);
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(find.byType(TextField), 'xyz');
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('No clients found'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('limits displayed tags to 3', (tester) async {
|
||||
final clientWithManyTags = [
|
||||
{
|
||||
'id': '1',
|
||||
'firstName': 'John',
|
||||
'lastName': 'Doe',
|
||||
'email': 'john@example.com',
|
||||
'company': 'Acme',
|
||||
'tags': ['tag1', 'tag2', 'tag3', 'tag4', 'tag5'],
|
||||
},
|
||||
];
|
||||
|
||||
when(() => mockApiClient.getClients(search: any(named: 'search')))
|
||||
.thenAnswer((_) async => clientWithManyTags);
|
||||
|
||||
await tester.pumpWidget(createTestWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('tag1'), findsOneWidget);
|
||||
expect(find.text('tag2'), findsOneWidget);
|
||||
expect(find.text('tag3'), findsOneWidget);
|
||||
expect(find.text('tag4'), findsNothing); // Should not show 4th tag
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -103,9 +103,8 @@ void main() {
|
||||
expect(clients.isEmpty, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
group('Client Form Validation', () {
|
||||
group('Client Form Validation', () {
|
||||
test('first name is required', () {
|
||||
final firstName = '';
|
||||
final isValid = firstName.isNotEmpty;
|
||||
@@ -156,4 +155,5 @@ group('Client Form Validation', () {
|
||||
expect(phone.isNotEmpty, isTrue);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user