-
Notifications
You must be signed in to change notification settings - Fork 0
Add comprehensive unit test suite for widgets, pages, blocs, services, repositories, entities, and core components #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Copilot
wants to merge
15
commits into
master
Choose a base branch
from
copilot/add-unit-tests-for-widgets-pages
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 9 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
2d78cab
Initial plan
Copilot f921823
Add comprehensive unit tests for widgets, blocs, services, and reposi…
Copilot f987924
Add more unit tests for services, repositories and widgets
Copilot 6173ce9
Add utility tests and test documentation
Copilot 43f14f9
Remove placeholder widget test
Copilot e30da54
Improve admin config service tests to avoid hardcoding real email add…
Copilot 6389ae4
Improve test documentation and clarity based on code review feedback
Copilot 765ee97
Add tests for additional pages: LoginPage, AdminSettingsPage, PublicU…
Copilot 29cbf5b
Add tests for remaining pages: UserListPage, PrivacyPolicyPage, and T…
Copilot 861d15b
Address code review feedback: improve test brittleness and documentation
Copilot def0f8a
Add 5 new widget tests to achieve 80% widget coverage
Copilot 8d6f194
Add comprehensive test coverage report
Copilot 29a218d
Add test coverage report and update documentation
Copilot 2a11a3d
Merge origin/master - resolve widget_test.dart conflict and integrate…
Copilot 2b9613f
Resolve merge conflicts with origin/master - integrate quality improv…
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| # Running Tests | ||
|
|
||
| This directory contains unit tests for the AngryRaphi Flutter application. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| Before running tests, you need to generate mock files for the tests. The tests use Mockito for mocking dependencies. | ||
|
|
||
| ## Generating Mock Files | ||
|
|
||
| Run the following command from the project root to generate mock files: | ||
|
|
||
| ```bash | ||
| flutter pub run build_runner build --delete-conflicting-outputs | ||
| ``` | ||
|
|
||
| Or use the watch mode for continuous generation during development: | ||
|
|
||
| ```bash | ||
| flutter pub run build_runner watch --delete-conflicting-outputs | ||
| ``` | ||
|
|
||
| This will generate `*.mocks.dart` files next to each test file that uses `@GenerateMocks` annotations. | ||
|
|
||
| ## Running Tests | ||
|
|
||
| Once mocks are generated, you can run tests using: | ||
|
|
||
| ### Run all tests | ||
| ```bash | ||
| flutter test | ||
| ``` | ||
|
|
||
| ### Run a specific test file | ||
| ```bash | ||
| flutter test test/features/authentication/presentation/bloc/auth_bloc_test.dart | ||
| ``` | ||
|
|
||
| ### Run tests with coverage | ||
| ```bash | ||
| flutter test --coverage | ||
| ``` | ||
|
|
||
| ## Test Structure | ||
|
|
||
| The test directory mirrors the `lib` directory structure: | ||
|
|
||
| - `test/core/` - Tests for core functionality (widgets, utils, network) | ||
| - `test/features/` - Tests for feature-specific code (blocs, repositories, pages) | ||
| - `test/services/` - Tests for services | ||
| - `test/shared/` - Tests for shared widgets and utilities | ||
|
|
||
| ## Test Categories | ||
|
|
||
| ### Widget Tests | ||
| - `test/core/widgets/` - Core widget tests | ||
| - `test/shared/widgets/` - Shared widget tests | ||
| - Tests for custom widgets and UI components | ||
|
|
||
| ### Bloc Tests | ||
| - `test/features/*/presentation/bloc/` - BLoC tests for each feature | ||
| - Uses `bloc_test` package for testing BLoC state changes | ||
|
|
||
| ### Repository Tests | ||
| - `test/features/*/data/repositories/` - Repository implementation tests | ||
| - Tests data layer logic and error handling | ||
|
|
||
| ### Service Tests | ||
| - `test/services/` - Service layer tests | ||
| - Tests business logic and external service interactions | ||
|
|
||
| ### Page Tests | ||
| - `test/features/*/presentation/pages/` - Page widget tests | ||
| - Tests for complete page widgets and their interactions | ||
|
|
||
| ## Test Coverage | ||
|
|
||
| To view test coverage: | ||
|
|
||
| 1. Generate coverage: | ||
| ```bash | ||
| flutter test --coverage | ||
| ``` | ||
|
|
||
| 2. View coverage in browser (requires `lcov` tool): | ||
| ```bash | ||
| genhtml coverage/lcov.info -o coverage/html | ||
| open coverage/html/index.html | ||
| ``` | ||
|
|
||
| ## Writing New Tests | ||
|
|
||
| When adding new tests: | ||
|
|
||
| 1. Follow the existing test structure | ||
| 2. Add `@GenerateMocks` annotation for dependencies you want to mock | ||
| 3. Generate mocks using build_runner | ||
| 4. Write comprehensive test cases covering: | ||
| - Happy path scenarios | ||
| - Error cases | ||
| - Edge cases | ||
| - State changes (for BLoCs) | ||
|
|
||
| ## Common Issues | ||
|
|
||
| ### Mock files not found | ||
| Run `flutter pub run build_runner build --delete-conflicting-outputs` to generate mock files. | ||
|
|
||
| ### Test failures due to Firebase | ||
| Some tests may require Firebase initialization. Mock Firebase dependencies appropriately. | ||
|
|
||
| ### Asset loading errors | ||
| Widget tests that load assets may need additional setup. Use `TestWidgetsFlutterBinding` for widget tests. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| import 'package:flutter_test/flutter_test.dart'; | ||
| import 'package:mockito/mockito.dart'; | ||
| import 'package:mockito/annotations.dart'; | ||
| import 'package:connectivity_plus/connectivity_plus.dart'; | ||
| import 'package:angry_raphi/core/network/network_info.dart'; | ||
|
|
||
| @GenerateMocks([Connectivity]) | ||
| import 'network_info_test.mocks.dart'; | ||
|
|
||
| void main() { | ||
| late NetworkInfoImpl networkInfo; | ||
| late MockConnectivity mockConnectivity; | ||
|
|
||
| setUp(() { | ||
| mockConnectivity = MockConnectivity(); | ||
| networkInfo = NetworkInfoImpl(mockConnectivity); | ||
| }); | ||
|
|
||
| group('NetworkInfoImpl', () { | ||
| group('isConnected', () { | ||
| test('should return true when device is connected to wifi', () async { | ||
| // arrange | ||
| when(mockConnectivity.checkConnectivity()).thenAnswer( | ||
| (_) async => [ConnectivityResult.wifi], | ||
| ); | ||
|
|
||
| // act | ||
| final result = await networkInfo.isConnected; | ||
|
|
||
| // assert | ||
| expect(result, true); | ||
| verify(mockConnectivity.checkConnectivity()); | ||
| }); | ||
|
|
||
| test('should return true when device is connected to mobile data', | ||
| () async { | ||
| // arrange | ||
| when(mockConnectivity.checkConnectivity()).thenAnswer( | ||
| (_) async => [ConnectivityResult.mobile], | ||
| ); | ||
|
|
||
| // act | ||
| final result = await networkInfo.isConnected; | ||
|
|
||
| // assert | ||
| expect(result, true); | ||
| verify(mockConnectivity.checkConnectivity()); | ||
| }); | ||
|
|
||
| test('should return true when device is connected to ethernet', () async { | ||
| // arrange | ||
| when(mockConnectivity.checkConnectivity()).thenAnswer( | ||
| (_) async => [ConnectivityResult.ethernet], | ||
| ); | ||
|
|
||
| // act | ||
| final result = await networkInfo.isConnected; | ||
|
|
||
| // assert | ||
| expect(result, true); | ||
| }); | ||
|
|
||
| test('should return false when device is not connected', () async { | ||
| // arrange | ||
| when(mockConnectivity.checkConnectivity()).thenAnswer( | ||
| (_) async => [ConnectivityResult.none], | ||
| ); | ||
|
|
||
| // act | ||
| final result = await networkInfo.isConnected; | ||
|
|
||
| // assert | ||
| expect(result, false); | ||
| verify(mockConnectivity.checkConnectivity()); | ||
| }); | ||
|
|
||
| test('should return true when device has multiple connections', () async { | ||
| // arrange | ||
| when(mockConnectivity.checkConnectivity()).thenAnswer( | ||
| (_) async => [ConnectivityResult.wifi, ConnectivityResult.mobile], | ||
| ); | ||
|
|
||
| // act | ||
| final result = await networkInfo.isConnected; | ||
|
|
||
| // assert | ||
| expect(result, true); | ||
| }); | ||
|
|
||
| test('should return false when connectivity results contain only none', | ||
| () async { | ||
| // arrange | ||
| when(mockConnectivity.checkConnectivity()).thenAnswer( | ||
| (_) async => [ConnectivityResult.none], | ||
| ); | ||
|
|
||
| // act | ||
| final result = await networkInfo.isConnected; | ||
|
|
||
| // assert | ||
| expect(result, false); | ||
| }); | ||
|
|
||
| test('should return true when connected to VPN', () async { | ||
| // arrange | ||
| when(mockConnectivity.checkConnectivity()).thenAnswer( | ||
| (_) async => [ConnectivityResult.vpn], | ||
| ); | ||
|
|
||
| // act | ||
| final result = await networkInfo.isConnected; | ||
|
|
||
| // assert | ||
| expect(result, true); | ||
| }); | ||
|
|
||
| test('should return true when connected to bluetooth', () async { | ||
| // arrange | ||
| when(mockConnectivity.checkConnectivity()).thenAnswer( | ||
| (_) async => [ConnectivityResult.bluetooth], | ||
| ); | ||
|
|
||
| // act | ||
| final result = await networkInfo.isConnected; | ||
|
|
||
| // assert | ||
| expect(result, true); | ||
| }); | ||
| }); | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| import 'package:flutter_test/flutter_test.dart'; | ||
| import 'package:angry_raphi/core/utils/validators.dart'; | ||
|
|
||
| void main() { | ||
| group('Validators', () { | ||
| group('validateEmail', () { | ||
| test('should return null for valid email', () { | ||
| expect(Validators.validateEmail('test@example.com'), isNull); | ||
| expect(Validators.validateEmail('user.name@domain.co.uk'), isNull); | ||
| expect(Validators.validateEmail('john_doe@company.org'), isNull); | ||
| }); | ||
|
|
||
| test('should return error message for empty email', () { | ||
| expect(Validators.validateEmail(''), equals('Email is required')); | ||
| expect(Validators.validateEmail(null), equals('Email is required')); | ||
| }); | ||
|
|
||
| test('should return error message for invalid email format', () { | ||
| expect( | ||
| Validators.validateEmail('invalid'), | ||
| equals('Please enter a valid email'), | ||
| ); | ||
| expect( | ||
| Validators.validateEmail('test@'), | ||
| equals('Please enter a valid email'), | ||
| ); | ||
| expect( | ||
| Validators.validateEmail('@example.com'), | ||
| equals('Please enter a valid email'), | ||
| ); | ||
| expect( | ||
| Validators.validateEmail('test@.com'), | ||
| equals('Please enter a valid email'), | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| group('validateName', () { | ||
| test('should return null for valid name', () { | ||
| expect(Validators.validateName('John'), isNull); | ||
| expect(Validators.validateName('Jane Doe'), isNull); | ||
| expect(Validators.validateName('A' * 50), isNull); // Max length | ||
| }); | ||
|
|
||
| test('should return error message for empty name', () { | ||
| expect(Validators.validateName(''), equals('Name is required')); | ||
| expect(Validators.validateName(null), equals('Name is required')); | ||
| }); | ||
|
|
||
| test('should return error message for name too short', () { | ||
| expect( | ||
| Validators.validateName('A'), | ||
| equals('Name must be at least 2 characters'), | ||
| ); | ||
| }); | ||
|
|
||
| test('should return error message for name too long', () { | ||
| expect( | ||
| Validators.validateName('A' * 51), | ||
| equals('Name cannot exceed 50 characters'), | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| group('validateDescription', () { | ||
| test('should return null for valid description', () { | ||
| expect(Validators.validateDescription('Short description'), isNull); | ||
| expect(Validators.validateDescription(''), isNull); | ||
| expect(Validators.validateDescription(null), isNull); | ||
| expect(Validators.validateDescription('A' * 500), isNull); // Max length | ||
| }); | ||
|
|
||
| test('should return error message for description too long', () { | ||
| expect( | ||
| Validators.validateDescription('A' * 501), | ||
| equals('Description cannot exceed 500 characters'), | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| group('validateRequired', () { | ||
| test('should return null for non-empty value', () { | ||
| expect(Validators.validateRequired('Some value', 'Field'), isNull); | ||
| expect(Validators.validateRequired('123', 'Number'), isNull); | ||
| }); | ||
|
|
||
| test('should return error message with field name for empty value', () { | ||
| expect( | ||
| Validators.validateRequired('', 'Username'), | ||
| equals('Username is required'), | ||
| ); | ||
| expect( | ||
| Validators.validateRequired(null, 'Password'), | ||
| equals('Password is required'), | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| group('isValidImageType', () { | ||
| test('should return true for valid image extensions', () { | ||
| expect(Validators.isValidImageType('photo.jpg'), isTrue); | ||
| expect(Validators.isValidImageType('image.jpeg'), isTrue); | ||
| expect(Validators.isValidImageType('picture.png'), isTrue); | ||
| expect(Validators.isValidImageType('graphic.webp'), isTrue); | ||
| }); | ||
|
|
||
| test('should return true for valid extensions regardless of case', () { | ||
| expect(Validators.isValidImageType('photo.JPG'), isTrue); | ||
| expect(Validators.isValidImageType('image.JPEG'), isTrue); | ||
| expect(Validators.isValidImageType('picture.PNG'), isTrue); | ||
| expect(Validators.isValidImageType('graphic.WEBP'), isTrue); | ||
| }); | ||
|
|
||
| test('should return false for invalid image extensions', () { | ||
| expect(Validators.isValidImageType('document.pdf'), isFalse); | ||
| expect(Validators.isValidImageType('video.mp4'), isFalse); | ||
| expect(Validators.isValidImageType('file.txt'), isFalse); | ||
| expect(Validators.isValidImageType('archive.zip'), isFalse); | ||
| }); | ||
|
|
||
| test('should return false for files without extension', () { | ||
| expect(Validators.isValidImageType('filename'), isFalse); | ||
| }); | ||
| }); | ||
|
|
||
| group('isValidImageSize', () { | ||
| test('should return true for valid image sizes', () { | ||
| expect(Validators.isValidImageSize(1024), isTrue); // 1KB | ||
| expect(Validators.isValidImageSize(1024 * 1024), isTrue); // 1MB | ||
| expect(Validators.isValidImageSize(5 * 1024 * 1024), isTrue); // 5MB (max) | ||
| }); | ||
|
|
||
| test('should return false for image sizes exceeding limit', () { | ||
| expect( | ||
| Validators.isValidImageSize(5 * 1024 * 1024 + 1), | ||
| isFalse, | ||
| ); // 5MB + 1 byte | ||
| expect( | ||
| Validators.isValidImageSize(10 * 1024 * 1024), | ||
| isFalse, | ||
| ); // 10MB | ||
| }); | ||
|
|
||
| test('should return true for zero size', () { | ||
| expect(Validators.isValidImageSize(0), isTrue); | ||
| }); | ||
| }); | ||
| }); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.