Skip to content
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

feat: fix dependencies and support unit tests for app #619 #1131 #1233

Draft
wants to merge 54 commits into
base: main
Choose a base branch
from

Conversation

vincentkoc
Copy link
Contributor

@vincentkoc vincentkoc commented Nov 1, 2024

Had major issues with app build, want to introduce proper testing to validate builds before i start with apple watch stuff as ill introduce tests for apple watch changes as i go.

Summary by CodeRabbit

  • New Features

    • Introduced a new Makefile for streamlined testing, building, and cleaning operations.
    • Added a new integration test for the onboarding flow of the application.
    • Implemented mock setups for Firebase authentication and core functionalities to enhance testing capabilities.
    • Updated the .env.template with example values for environment variables to assist users.
  • Bug Fixes

    • Improved layout responsiveness for the onboarding and error widgets.
  • Tests

    • Added comprehensive unit tests for various providers, services, and components, ensuring robust coverage.
    • Introduced mock classes for testing to simulate behavior without actual implementations.
  • Chores

    • Updated .gitignore to include additional file patterns.
    • Refined asset naming conventions in pubspec.yaml.
    • Updated version and dependencies in pubspec.yaml.
    • Modified configuration files for conditional inclusion in Debug.xcconfig and Release.xcconfig.
    • Enhanced the testing script for improved error handling and logging.

Copy link
Contributor

coderabbitai bot commented Nov 1, 2024

Walkthrough

The pull request introduces several updates across various files in the Flutter application. Key changes include updates to the .gitignore file to exclude specific Google Services and coverage reports. A new Makefile is added to streamline testing, building, and cleaning operations. Integration and unit tests are established for various components, including the onboarding flow and multiple providers. Additionally, mock classes are generated for testing purposes, and the Podfile is modified to download necessary XCFrameworks. Updates to the pubspec.yaml file reflect versioning and dependency management changes.

Changes

File Change Summary
.gitignore Added entries for **/GoogleService-Info.plist, **/google-services.json, **/ios/ogg.xcframework, **/ios/opus.xcframework, and app/coverage. Removed entries for **/android/app/google-services.json, **/android/app/src/dev/google-services.json, and **/android/app/src/prod/google-services.json.
app/Makefile Introduced targets: test, build, and clean for managing project operations.
app/integration_test/app_test.dart Added integration test for onboarding flow with a test group titled "Full App Tests".
app/ios/Podfile Added download_xcframework method to download and configure XCFrameworks for opus and ogg.
app/lib/main.dart Modified DeciderWidget and CustomErrorWidget for improved layout and error handling.
app/pubspec.yaml Updated version to 1.0.44+163, added firebase_analytics: 11.3.3 and mockito: ^5.4.4 dependencies, and adjusted font asset names for consistency.
app/test.sh Created script to automate testing, including coverage report generation.
app/test/app_test.dart Established tests for various components, including providers and widgets.
app/test/app_test.mocks.dart Generated mock classes for testing providers and utilities.
app/test/backend/schema/message_test.dart Introduced unit tests for ServerMessage class focusing on message creation and JSON serialization.
app/test/basic_test.dart Added foundational tests for basic functionality and error handling.
app/test/helpers/auth_mock_helper.dart Provided mock implementations for Firebase authentication.
app/test/helpers/mock_helper.dart Introduced mock setup for Firebase functionalities.
app/test/providers/auth_provider_test.dart Created unit tests for AuthenticationProvider class.
app/test/providers/capture_provider_test.dart Established unit tests for CaptureProvider class.
app/test/providers/memory_provider_test.dart Introduced unit tests for MemoryProvider class.
app/test/services/device_connection_test.dart Added unit tests for DeviceService class.
app/test/services/notifications_test.dart Introduced unit tests for NotificationService class.
app/test/utils/firebase_mock.dart Created mock setup for Firebase Core functionalities.
app/.env.template Updated environment variable placeholders with example values for better guidance.
app/ios/Flutter/Debug.xcconfig Changed inclusion directives from #include to #include?.
app/ios/Flutter/Release.xcconfig Changed inclusion directives from #include to #include?.
app/ios/Runner/AppDelegate.swift Integrated Firebase initialization in the application(_:didFinishLaunchingWithOptions:) method.

Possibly related PRs

  • Code Cleanup #1125: The changes in the .gitignore file regarding the removal and addition of entries for Google Services and coverage reports may relate to the overall cleanup and organization of project files, which is a focus of the code cleanup PR.

"In the code, we hop and play,
New tests and mocks brighten the day.
With frameworks tucked away from sight,
Our app will flourish, oh what a delight!
From .gitignore to tests galore,
We build a future, and so much more!" 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 2ac7c82 and 0c9dd2b.

📒 Files selected for processing (1)
  • app/test/providers/capture_provider_test.mocks.dart (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/test/providers/capture_provider_test.mocks.dart

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 43

🧹 Outside diff range and nitpick comments (18)
app/Makefile (1)

1-19: Consider adding more development-friendly targets.

The Makefile provides good basic targets, but consider adding these commonly needed targets:

  • format: to run dart format
  • lint: to run static analysis
  • coverage: to generate and open coverage reports
  • generate: to run only code generation without tests

Example additions:

format:
	@echo "🎨 Formatting code..."
	@dart format .

lint:
	@echo "🔍 Running static analysis..."
	@flutter analyze

coverage:
	@echo "📊 Generating coverage report..."
	@flutter test --coverage
	@genhtml coverage/lcov.info -o coverage/html
	@open coverage/html/index.html

generate:
	@echo "🏗️ Generating code..."
	@dart run build_runner build --delete-conflicting-outputs
app/integration_test/app_test.dart (1)

1-1: Add file documentation.

Consider adding a header comment to document the purpose and scope of these integration tests.

Add this documentation at the start of the file:

+/// Integration tests for the main application flow.
+/// 
+/// These tests verify the application's core functionality using real dependencies,
+/// focusing on the onboarding experience and user journey.
+
 import 'package:integration_test/integration_test.dart';
app/test/providers/memory_provider_test.dart (2)

7-7: Unused mock generation.

The @GenerateMocks([MemoryProvider]) annotation generates a mock, but it's not being utilized in any test cases. Consider either removing it if not needed or adding tests that leverage the mock for testing components that depend on MemoryProvider.


9-12: Document ServiceManager initialization requirement and add cleanup.

The setup initializes ServiceManager but lacks documentation explaining why this is necessary for the tests. Additionally, consider adding a tearDown to clean up any state.

 setUp(() {
-  // Initialize service manager before tests
+  // ServiceManager initialization is required for <explain why>
   ServiceManager.init();
 });
+tearDown(() {
+  // Clean up ServiceManager state
+  ServiceManager.reset(); // Add if such method exists
+});
app/test/helpers/auth_mock_helper.dart (1)

17-35: Add cleanup for method handlers.

To prevent test interference, consider adding a cleanup function to reset the method handlers after tests.

Add this cleanup function:

void cleanupAuthMocks() {
  TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
      .setMockMethodCallHandler(
          const MethodChannel('plugins.flutter.io/firebase_auth'), null);
}
app/test/backend/schema/message_test.dart (1)

6-9: Consider enhancing the failed message test coverage.

While the test verifies the error message text, it would be more robust to also verify other properties of the failed message (e.g., sender, type, created_at).

Consider expanding the test:

 test('Create failed message', () {
   final message = ServerMessage.failedMessage();
   expect(message.text, 'Looks like we are having issues with the server. Please try again later.');
+  expect(message.sender, 'system');  // Verify sender
+  expect(message.type, 'error');     // Verify message type
+  expect(message.createdAt, isNotNull);  // Verify timestamp exists
 });
app/test/providers/auth_provider_test.dart (1)

12-15: Add error handling for Firebase initialization.

The Firebase initialization could fail, especially in a test environment. Consider adding try-catch block to handle potential initialization errors.

 setUpAll(() async {
   setupFirebaseMocks();
-  await Firebase.initializeApp();
+  try {
+    await Firebase.initializeApp();
+  } catch (e) {
+    fail('Failed to initialize Firebase: $e');
+  }
 });
app/test/utils/firebase_mock.dart (1)

7-7: Add documentation for the mock setup utility.

Consider adding documentation that explains:

  • The purpose of this utility
  • When and how to use it in tests
  • Example usage with custom handlers
+/// Sets up Firebase Core mocks for testing environments.
+///
+/// This utility mocks the method channel calls for Firebase Core initialization.
+/// It handles both core initialization and app initialization with default mock values.
+///
+/// Example usage:
+/// ```dart
+/// void setUp() {
+///   setupFirebaseCoreMocks((call) {
+///     // Custom handling for specific method calls
+///   });
+/// }
+/// ```
 void setupFirebaseCoreMocks([Callback? customHandlers]) {
app/test/helpers/mock_helper.dart (1)

40-43: Remove unused constant assignment.

The pigeonChannel constant is assigned but never used. This can be simplified to just the method call.

-  const pigeonChannel = BinaryMessenger.setMockMessageHandler(
+  BinaryMessenger.setMockMessageHandler(
app/test/app_test.dart (4)

33-64: Enhance provider test coverage.

The current tests only verify initial states. Consider adding tests for:

  • State changes after sign-in/sign-out
  • Error handling scenarios
  • Method call verification
  • Edge cases (e.g., network timeouts for ConnectivityProvider)

Example additions for AuthProvider:

test('sign in updates state', () async {
  when(authProvider.loading).thenReturn(true);
  when(authProvider.signIn()).thenAnswer((_) async => true);
  
  expect(authProvider.loading, isTrue);
  await authProvider.signIn();
  
  verify(authProvider.signIn()).called(1);
});

test('handles sign in failure', () async {
  when(authProvider.signIn()).thenThrow(Exception('Auth failed'));
  
  expect(() => authProvider.signIn(), throwsException);
});

66-108: Add more widget test scenarios.

While the current tests cover basic functionality, consider adding:

  • Theme-related tests (dark/light mode)
  • Accessibility tests (semantic labels, contrast)
  • Interaction tests (tap handlers if any)
  • Different screen size tests

Example additions:

testWidgets('Error widget respects theme', (tester) async {
  await tester.pumpWidget(
    MaterialApp(
      theme: ThemeData.dark(),
      home: const Material(
        child: CustomErrorWidget(
          errorMessage: 'Test error',
        ),
      ),
    ),
  );
  
  final textWidget = tester.widget<Text>(find.text('Test error'));
  expect(textWidget.style?.color, isNotNull);
});

testWidgets('Error widget is accessible', (tester) async {
  await tester.pumpWidget(
    const MaterialApp(
      home: Material(
        child: CustomErrorWidget(
          errorMessage: 'Test error',
        ),
      ),
    ),
  );
  
  expect(find.bySemanticsLabel('Error: Test error'), findsOneWidget);
});

110-127: Expand utility test coverage.

The current utility tests are minimal. Consider adding:

  • Tests for setting/updating preferences
  • Persistence verification
  • Error cases for preferences
  • Complete environment configuration validation

Example additions:

test('preferences can be updated', () async {
  when(prefsUtil.setOnboardingCompleted(any))
      .thenAnswer((_) => Future.value(true));
  
  await prefsUtil.setOnboardingCompleted(true);
  verify(prefsUtil.setOnboardingCompleted(true)).called(1);
});

test('complete environment configuration', () {
  expect(F.env.apiUrl, isNotNull);
  expect(F.env.apiKey, isNotNull);
  // Add other critical environment variables
});

129-135: Enhance navigation test coverage.

The current navigation tests only verify the navigator key. Consider adding:

  • Route push/pop tests
  • Navigation state preservation
  • Deep linking tests if applicable
  • Navigation error handling

Example additions:

testWidgets('Navigation works correctly', (tester) async {
  await tester.pumpWidget(const MyApp());
  
  final NavigatorState navigator = MyApp.navigatorKey.currentState!;
  navigator.pushNamed('/some-route');
  await tester.pumpAndSettle();
  
  expect(navigator.canPop(), isTrue);
});
app/pubspec.yaml (1)

Dependencies with any version constraint found - update recommended

The review comment is accurate. The codebase inspection confirms that both awesome_notifications and path_provider are using the any version constraint in app/pubspec.yaml:

  • awesome_notifications: any
  • path_provider: any

Using any version constraint is not recommended as it may lead to unexpected breaking changes when dependencies are updated. Consider specifying explicit version constraints for these packages.

🔗 Analysis chain

Line range hint 52-53: Consider specifying version constraints for dependencies.

Some dependencies are using any version constraint which is not recommended as it may lead to unexpected breaking changes:

  • awesome_notifications: any
  • path_provider: any

Consider specifying explicit version constraints for better dependency management.

Also applies to: 67-68


Let me try to verify the dependencies mentioned in the review comment with a different script.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check latest stable versions of these packages
echo "Checking latest stable versions..."
for pkg in "awesome_notifications" "path_provider"; do
  echo "Package: $pkg"
  curl -s "https://pub.dev/api/packages/$pkg" | jq -r '.latest.version'
done

Length of output: 625


Script:

#!/bin/bash
# Search for dependency declarations in pubspec.yaml
echo "Checking pubspec.yaml for dependency declarations..."
rg -A1 "^\s*(awesome_notifications|path_provider):" app/pubspec.yaml

Length of output: 299

app/test/services/device_connection_test.mocks.dart (1)

26-32: Consider enhancing mock documentation.

The mock is configured to throw on missing stubs, which is good for catching incomplete mocks early. Consider adding a documentation comment explaining this behavior to help other developers understand why their tests might fail.

 /// A class which mocks [DeviceService].
 ///
 /// See the documentation for Mockito's code generation for more information.
+/// Note: This mock is configured to throw on missing stubs to ensure all
+/// required mock behaviors are explicitly defined in tests.
 class MockDeviceService extends _i1.Mock implements _i2.DeviceService {
app/lib/main.dart (1)

328-402: LGTM! Enhanced error presentation with improved UX.

The UI improvements provide better error visualization and user interaction. The scrollable error message with copy functionality makes it easier for users to report issues.

Consider integrating with an error reporting service by adding an additional button:

 SizedBox(
   width: 210,
   child: Column(
     children: [
       ElevatedButton(
         style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
         onPressed: () {
           Clipboard.setData(ClipboardData(text: errorMessage));
           ScaffoldMessenger.of(context).showSnackBar(
             const SnackBar(
               content: Text('Error message copied to clipboard'),
             ),
           );
         },
         child: const Padding(
           padding: EdgeInsets.symmetric(vertical: 12.0),
           child: Row(
             mainAxisSize: MainAxisSize.min,
             mainAxisAlignment: MainAxisAlignment.center,
             children: [
               Flexible(
                 child: Text(
                   'Copy error message',
                   overflow: TextOverflow.ellipsis,
                 ),
               ),
               SizedBox(width: 8),
               Icon(Icons.copy_rounded, size: 20),
             ],
           ),
         ),
       ),
+      const SizedBox(height: 8),
+      ElevatedButton(
+        style: ElevatedButton.styleFrom(backgroundColor: Colors.blue),
+        onPressed: () {
+          // TODO: Integrate with your error reporting service
+          // Example: Instabug.reportError(errorMessage);
+        },
+        child: const Padding(
+          padding: EdgeInsets.symmetric(vertical: 12.0),
+          child: Row(
+            mainAxisSize: MainAxisSize.min,
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              Flexible(
+                child: Text(
+                  'Report issue',
+                  overflow: TextOverflow.ellipsis,
+                ),
+              ),
+              SizedBox(width: 8),
+              Icon(Icons.bug_report_rounded, size: 20),
+            ],
+          ),
+        ),
+      ),
     ],
   ),
 ),
app/test/providers/memory_provider_test.mocks.dart (1)

28-36: Consider adding error handling for null parameters.

While the mock implementation looks good, consider adding parameter validation in the constructor to prevent potential issues with null values during testing.

 MockMemoryProvider() {
+    if (!Platform.environment.containsKey('MOCKITO_DISABLE_NULL_SAFETY_WARNING')) {
+      print('Warning: This mock may throw null errors if used with null values.');
+    }
     _i1.throwOnMissingStub(this);
   }

Also applies to: 41-44

app/test/providers/capture_provider_test.mocks.dart (1)

539-545: Consider adding documentation for error handling methods.

The error handling methods (onError, notifyError, notifyInfo) lack documentation. Consider adding documentation in the actual implementation class to clarify their usage and expected behavior.

Also applies to: 671-677, 689-695

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 99f5d58 and ec09cd2.

⛔ Files ignored due to path filters (8)
  • app/assets/fonts/SFProDisplayBlackItalic.otf is excluded by !**/*.otf
  • app/assets/fonts/SFProDisplayBold.otf is excluded by !**/*.otf
  • app/assets/fonts/SFProDisplayHeavyItalic.otf is excluded by !**/*.otf
  • app/assets/fonts/SFProDisplayLightItalic.otf is excluded by !**/*.otf
  • app/assets/fonts/SFProDisplayMedium.otf is excluded by !**/*.otf
  • app/assets/fonts/SFProDisplayRegular.otf is excluded by !**/*.otf
  • app/assets/fonts/SFProDisplaySemiboldItalic.otf is excluded by !**/*.otf
  • app/assets/fonts/SFProDisplayThinItalic.otf is excluded by !**/*.otf
📒 Files selected for processing (24)
  • .gitignore (3 hunks)
  • app/Makefile (1 hunks)
  • app/integration_test/app_test.dart (1 hunks)
  • app/ios/Podfile (4 hunks)
  • app/lib/main.dart (2 hunks)
  • app/pubspec.yaml (4 hunks)
  • app/test.sh (1 hunks)
  • app/test/app_test.dart (1 hunks)
  • app/test/app_test.mocks.dart (1 hunks)
  • app/test/backend/schema/message_test.dart (1 hunks)
  • app/test/basic_test.dart (1 hunks)
  • app/test/helpers/auth_mock_helper.dart (1 hunks)
  • app/test/helpers/mock_helper.dart (1 hunks)
  • app/test/providers/auth_provider_test.dart (1 hunks)
  • app/test/providers/auth_provider_test.mocks.dart (1 hunks)
  • app/test/providers/capture_provider_test.dart (1 hunks)
  • app/test/providers/capture_provider_test.mocks.dart (1 hunks)
  • app/test/providers/memory_provider_test.dart (1 hunks)
  • app/test/providers/memory_provider_test.mocks.dart (1 hunks)
  • app/test/services/device_connection_test.dart (1 hunks)
  • app/test/services/device_connection_test.mocks.dart (1 hunks)
  • app/test/services/notifications_test.dart (1 hunks)
  • app/test/services/notifications_test.mocks.dart (1 hunks)
  • app/test/utils/firebase_mock.dart (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • .gitignore
  • app/test/providers/auth_provider_test.mocks.dart
  • app/test/services/notifications_test.mocks.dart
🔇 Additional comments (35)
app/Makefile (1)

1-1: LGTM! Proper use of .PHONY declaration.

Correctly declares all targets as PHONY to prevent conflicts with similarly named files.

app/integration_test/app_test.dart (1)

6-9: LGTM! Proper test initialization and group structure.

The integration test is correctly initialized with IntegrationTestWidgetsFlutterBinding and organized within a well-named test group.

app/test/providers/capture_provider_test.dart (2)

1-6: LGTM: Proper test setup with necessary imports.

The imports and mock generation setup follow Flutter testing best practices.


7-9: LGTM: Correct test initialization.

The TestWidgetsFlutterBinding.ensureInitialized() call is properly placed to set up the Flutter testing environment.

app/test/services/notifications_test.dart (2)

1-7: LGTM! All necessary imports are present.

The imports cover all required packages for testing, mocking, and Firebase initialization.


9-15: LGTM! Proper test initialization setup.

The test environment is correctly configured with Flutter widget testing binding and Firebase mocks.

app/test/basic_test.dart (1)

1-37: Enhance test coverage and structure.

While this file provides a good foundation for testing setup, consider:

  1. Organizing tests into more specific groups based on features/components
  2. Adding setup/teardown methods using setUp() and tearDown()
  3. Using test fixtures for common test data
  4. Implementing mocks for external dependencies
#!/bin/bash
# Check current test coverage structure
echo "Analyzing test structure..."
# Look for other test files to understand coverage
fd -e dart -p "test/" --exec echo "Found test file: {}"
# Check for existing mocks
fd -e dart -p "test/" -e "mock" --exec echo "Found mock: {}"
app/test/backend/schema/message_test.dart (1)

1-5: LGTM! Well-structured test file.

The file follows Flutter testing conventions with proper imports and test organization.

app/test/providers/auth_provider_test.dart (2)

1-8: LGTM! Imports and mock setup are well-structured.

The necessary testing packages are imported and the mock generation is properly configured.


1-38: Verify test coverage configuration.

Ensure that this test file is included in the test coverage reports.

#!/bin/bash
# Check if coverage is properly configured
cat pubspec.yaml | grep -A 5 "coverage"
cat .gitignore | grep "coverage"
app/test/utils/firebase_mock.dart (1)

1-5: LGTM! Imports and type definition are appropriate.

The imports cover all necessary dependencies for Firebase mocking, and the Callback type alias is well-defined.

app/test/helpers/mock_helper.dart (2)

69-72: LGTM!

The TestFirebaseAppPlatform implementation is clean and sufficient for basic testing needs.


1-72: Verify test coverage for the mock implementations.

Let's ensure the mock implementations are properly tested themselves.

app/ios/Podfile (1)

Line range hint 72-85: Verify required permissions against app's functionality.

The configuration includes several sensitive permissions (location, calendar, etc.). Please ensure:

  1. Each permission is actually required for app functionality
  2. Privacy policy covers all these data access points
  3. Appropriate usage description strings are present in Info.plist
#!/bin/bash
# Verify permission usage and documentation

# Check for usage description strings in Info.plist
echo "Checking Info.plist for permission usage descriptions..."
find app/ios -name "Info.plist" -exec grep -l "UsageDescription" {} \;

# Check for permission usage in code
echo "Checking permission usage in code..."
rg -t dart "requestPermission|checkPermission" app/lib
app/pubspec.yaml (4)

Line range hint 7-9: LGTM: Version and SDK constraints are appropriate.

The version bump and SDK constraints are well-defined and follow semantic versioning.


129-129: LGTM: Mockito addition supports unit testing.

The addition of mockito aligns well with the PR objective to support unit tests.


159-177: LGTM: Font configuration is well-structured.

The font asset renaming improves consistency and follows better naming conventions. The weight and style attributes are properly specified for each font variant.


Line range hint 108-122: Review dependency override stability.

Two potential concerns in dependency overrides:

  1. opus_flutter_android points to a fork - ensure this fork is actively maintained
  2. manage_calendar_events uses the master branch which could be unstable

Consider:

  • Using released versions when possible
  • Pinning to specific commits instead of master branch
#!/bin/bash
# Check if the repos are actively maintained
echo "Checking repository activity..."
for repo in "mdmohsin7/opus_flutter" "beastoin/manage_calendar_events"; do
  echo "Repository: $repo"
  gh api "repos/$repo/commits?per_page=1" --jq '.[0].commit.author.date'
done
app/test/services/device_connection_test.mocks.dart (3)

41-47: Review nullable mutex setter in the original interface.

The setter accepts a nullable boolean (bool?) but the getter returns a non-nullable boolean (bool). This inconsistency in the original interface might lead to runtime issues.

#!/bin/bash
# Description: Check the original interface definition

# Test: Look for the original DeviceService interface definition
ast-grep --pattern 'class DeviceService {
  $$$
  set mutex($_) $_
  $$$
}'

157-168: ⚠️ Potential issue

Fix parameter list in ensureConnection mock.

The mock implementation doesn't correctly match the method signature for argument verification. The invocation should include all parameters in the same order as the method signature.

       Invocation.method(
         #ensureConnection,
-        [deviceId],
-        {#force: force},
+        [deviceId],
+        {#force: force},
         ),
         returnValue: _i4.Future<_i5.DeviceConnection?>.value(),

Likely invalid or redundant comment.


1-2: Verify the test file location.

The generated comment indicates this mock was generated from friend_private/test/services/device_connection_test.dart, but the file is located in app/test/services/. This path inconsistency might cause issues when regenerating mocks.

app/lib/main.dart (1)

301-318: LGTM! Improved layout responsiveness with scrolling support.

The new layout structure effectively handles content overflow and ensures consistent rendering across different screen sizes. However, be mindful that IntrinsicHeight might impact performance with complex layouts.

Let's verify the layout behavior:

app/test/providers/memory_provider_test.mocks.dart (6)

1-4: LGTM! Generated file notice is clear and accurate.

The file header correctly indicates this is auto-generated code and warns against manual edits.


5-13: LGTM! Import statements are properly organized.

The imports are well-structured, using appropriate prefixes to avoid naming conflicts.


15-26: LGTM! Comprehensive list of ignore directives.

The ignore directives are appropriate for generated code and cover necessary cases to prevent irrelevant linter warnings.


705-731: LGTM! Listener implementation follows ChangeNotifier pattern.

The implementation of addListener, removeListener, and notifyListeners correctly follows the ChangeNotifier pattern.


46-50: Verify the default return values for boolean getters.

The mock returns false as default for boolean getters like isLoadingMemories and hasNonDiscardedMemories. Ensure this aligns with the actual implementation's initial state.

#!/bin/bash
# Search for the actual implementation to verify default values
ast-grep --pattern 'class MemoryProvider {
  $$$
  bool get isLoadingMemories {
    $$$
  }
  $$$
}'

Also applies to: 79-83, 94-98


404-421: Verify tuple return value implementation.

The implementation of addMemoryWithDateGrouped returns a tuple with hardcoded values (0, FakeDateTime). Ensure this matches the expected behavior in tests.

✅ Verification successful

The previous ast-grep pattern might have failed due to syntax. Let's try a different approach to locate the implementation and verify the return value structure.


Looking at the output, I can now generate the final response since we found both the actual implementation in memory_provider.dart and its usage in the codebase. The mock implementation matches the expected behavior as it:

  1. Returns a tuple of (int, DateTime) as per the actual implementation
  2. Is used in the context where both values are needed (as seen in ai_message.dart)
  3. The mock's default return of (0, FakeDateTime) is appropriate for testing purposes

The mock implementation correctly matches the expected tuple return value structure

The mock's return value of (0, FakeDateTime) aligns with the actual implementation in memory_provider.dart which returns an index and date. This is verified by its usage in ai_message.dart where both values from the tuple are properly destructured and utilized.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for actual implementation to verify return value structure
ast-grep --pattern 'class MemoryProvider {
  $$$
  (int, DateTime) addMemoryWithDateGrouped($_) {
    $$$
  }
  $$$
}'

Length of output: 120


Script:

#!/bin/bash
# Let's try a broader search for the implementation using ripgrep
rg -l "class MemoryProvider" 

# Then search for the method signature
rg "addMemoryWithDateGrouped" -A 5

Length of output: 2473

app/test/providers/capture_provider_test.mocks.dart (3)

1-34: LGTM! Well-structured file header.

The file header contains appropriate warning about being auto-generated and comprehensive ignore directives for linting rules.


35-44: LGTM! Correctly implemented fake service.

The _FakeSdCardSocketService_0 class properly extends SmartFake and follows Mockito's patterns for fake implementations.


1-705: Overall implementation is robust and well-structured.

The mock implementation is comprehensive and follows Mockito's best practices. It provides good coverage for testing the CaptureProvider functionality.

app/test/app_test.mocks.dart (4)

1-37: LGTM! Well-structured file header and imports.

The file header clearly indicates this is auto-generated code, and the import statements are well-organized. The linting rules are appropriately disabled for generated code.


38-1803: LGTM! Comprehensive mock implementations for testing.

The mock classes are well-structured with:

  • Proper extension of Mock class and interface implementation
  • Consistent use of noSuchMethod with appropriate return values
  • Type-safe default values for all methods

These mocks will provide good test coverage for:

  • Authentication flows
  • Connectivity states
  • App state management
  • Shared preferences operations

53-55: Good test coverage setup with strict mock behavior.

The use of throwOnMissingStub in all mock constructors is excellent as it:

  1. Ensures all required mock behaviors are explicitly defined
  2. Prevents silent failures in tests due to missing stubs
  3. Helps maintain high-quality test coverage

Also applies to: 196-198, 278-280, 599-601


687-694: Ensure sensitive data handling in tests.

The mock implementations include sensitive fields (openAIApiKey, authToken, gcpCredentials). While these are mock values for testing, ensure that:

  1. Test data doesn't contain actual credentials
  2. CI/CD pipelines don't expose these mock values in logs

Also applies to: 1461-1468, 735-742

✅ Verification successful

Based on the analysis of the shell script outputs, I can now provide a final response:

Mock values are safely implemented using dummyValue

The sensitive fields (openAIApiKey, authToken, gcpCredentials) are properly handled using Mockito's dummyValue mechanism, which generates safe mock values at runtime. The only hardcoded test values found are clearly marked test placeholders ('test-api-key', '123') in test helper files.

  • All sensitive fields in app_test.mocks.dart use _i11.dummyValue<String>() for mock generation
  • Test helper files use obvious placeholder values ('test-api-key', '123') that don't expose real credentials
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potential hardcoded credentials in test files
echo "Searching for potential credential patterns in test files..."
rg -i '(key|token|credential|secret).*=.*[A-Za-z0-9/+]{32,}' app/test/

Length of output: 203


Script:

#!/bin/bash
# Let's check the actual implementation of these mock values
echo "Checking mock implementation for sensitive fields..."
rg -A 5 "dummyValue" app/test/

# Also check for any hardcoded test values with different patterns
echo -e "\nChecking for potential hardcoded values..."
rg -i "(api_?key|auth_?token|credentials).*['\"].*['\"]" app/test/

# Look at the actual mock file content
echo -e "\nChecking the mock file content..."
cat app/test/app_test.mocks.dart

Length of output: 55801

app/Makefile Outdated Show resolved Hide resolved
app/Makefile Outdated Show resolved Hide resolved
app/Makefile Outdated Show resolved Hide resolved
Comment on lines +13 to +19
group('DeviceService Tests', () {
test('Initial state', () {
final service = DeviceService();
expect(service.devices, isEmpty);
// Only test the devices property for now
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Expand test coverage for DeviceService.

The current test suite only verifies the initial state. Given that this is a device connection service, we should test core functionality such as:

  • Device discovery
  • Connection management
  • Error handling
  • State transitions
  • Event subscriptions

Add these essential test cases:

test('Device discovery', () async {
  final service = DeviceService();
  await service.startDiscovery();
  // Verify discovery started
  verify(service.isDiscovering).called(1);
  // Add assertions for discovery events
});

test('Device connection', () async {
  final service = DeviceService();
  final mockDevice = MockDevice();
  await service.connect(mockDevice);
  // Verify connection state
  expect(service.isConnected(mockDevice), isTrue);
});

test('Connection error handling', () async {
  final service = DeviceService();
  final mockDevice = MockDevice();
  when(mockDevice.connect()).thenThrow(Exception('Connection failed'));
  
  expect(
    () => service.connect(mockDevice),
    throwsException,
  );
});

test('Device events subscription', () async {
  final service = DeviceService();
  final mockDevice = MockDevice();
  final events = <DeviceEvent>[];
  
  service.deviceEvents.listen(events.add);
  await service.connect(mockDevice);
  // Verify events are properly emitted
});

Comment on lines +1 to +5
import 'package:flutter_test/flutter_test.dart';
import 'package:friend_private/services/devices.dart';
import 'package:friend_private/services/services.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using fake_async for testing time-dependent operations.

Since device connections often involve timeouts and retries, consider adding the fake_async package for testing time-dependent operations.

 import 'package:flutter_test/flutter_test.dart';
 import 'package:friend_private/services/devices.dart';
 import 'package:friend_private/services/services.dart';
 import 'package:mockito/mockito.dart';
 import 'package:mockito/annotations.dart';
+import 'package:fake_async/fake_async.dart';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import 'package:flutter_test/flutter_test.dart';
import 'package:friend_private/services/devices.dart';
import 'package:friend_private/services/services.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:friend_private/services/devices.dart';
import 'package:friend_private/services/services.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
import 'package:fake_async/fake_async.dart';

app/ios/Podfile Outdated Show resolved Hide resolved
app/ios/Podfile Outdated Show resolved Hide resolved
Comment on lines +322 to +329
@override
_i6.Future<dynamic> getInitialMemories() => (super.noSuchMethod(
Invocation.method(
#getInitialMemories,
[],
),
returnValue: _i6.Future<dynamic>.value(),
) as _i6.Future<dynamic>);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding delay simulation for async methods.

The mock implementation of async methods like getInitialMemories, getMemoriesFromServer, and syncWals returns immediately. Consider adding configurable delays to simulate real-world scenarios.

 @override
 _i6.Future<dynamic> getInitialMemories() => (super.noSuchMethod(
       Invocation.method(
         #getInitialMemories,
         [],
       ),
-      returnValue: _i6.Future<dynamic>.value(),
+      returnValue: _i6.Future<dynamic>.delayed(
+        const Duration(milliseconds: 100),
+      ),
     ) as _i6.Future<dynamic>);

Also applies to: 349-356, 548-555

app/test/providers/capture_provider_test.mocks.dart Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
app/lib/main.dart (1)

Line range hint 335-358: Consider adjusting the layout structure for better flexibility.

While the current implementation works, wrapping SingleChildScrollView with Expanded inside a Column might cause layout issues in certain scenarios. Consider using Flexible instead of Expanded to allow for more flexible content sizing.

Here's a suggested improvement:

  return Center(
    child: Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Icon(
            Icons.error_outline,
            color: Colors.red,
            size: 50.0,
          ),
          const SizedBox(height: 16.0),
          const Text(
            'Something went wrong! Please try again later.',
            textAlign: TextAlign.center,
            style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 16.0),
-         Expanded(
+         Flexible(
            child: SingleChildScrollView(
              key: const Key('error_message_scroll_view'),
              child: Text(
                errorMessage,
                style: const TextStyle(fontSize: 16.0),
              ),
            ),
          ),
        ],
      ),
    ),
  );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ec09cd2 and f84e3e8.

📒 Files selected for processing (1)
  • app/lib/main.dart (3 hunks)
🔇 Additional comments (2)
app/lib/main.dart (2)

301-318: LGTM! Improved layout handling for onboarding.

The changes enhance the onboarding experience by:

  • Adding responsive layout support
  • Enabling content scrolling when needed
  • Ensuring consistent styling
  • Maintaining proper layout constraints

328-331: LGTM! Improved constructor formatting.

The constructor formatting changes enhance code readability while maintaining proper parameter annotations.

@beastoin
Copy link
Collaborator

beastoin commented Nov 2, 2024

nice @vincentkoc , just 01 question:
1/ why did we need changes in lib/main.dart ?

@vincentkoc
Copy link
Contributor Author

@beastoin tests are picking up issues with main.dart with overflows in UX - I'm still having issues getting firebase/auth working on xcode and once i can test and verify would be good, unless you can check. I'll get the hints sorted next. There are number of issues in the dart code but i wont touch that for now, ill just focus on whats needed to get watch working. Sorry im used to TDD (test driven development) esp with hardware/complex code its hard for me to verify its all working.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (5)
app/ios/Runner/AppDelegate.swift (2)

4-4: Firebase integration looks good, but needs error handling.

The Firebase import and configuration are correctly placed. However, consider adding error handling around the Firebase initialization to gracefully handle configuration failures.

Consider wrapping the Firebase configuration with error handling:

-FirebaseApp.configure()
+do {
+    guard let filePath = Bundle.main.path(forResource: "GoogleService-Info", ofType: "plist") else {
+        fatalError("GoogleService-Info.plist not found")
+    }
+    FirebaseApp.configure()
+} catch let error {
+    print("Error configuring Firebase: \(error.localizedDescription)")
+}

Also applies to: 17-17


Line range hint 1-85: Consider implementing Firebase Auth state handling.

Given the PR context about Firebase authentication issues, consider implementing proper auth state handling in the AppDelegate.

Add Firebase Auth state listener:

import FirebaseAuth

// Add inside didFinishLaunchingWithOptions after configure()
Auth.auth().addStateDidChangeListener { (auth, user) in
    if let user = user {
        print("User is signed in with UID: \(user.uid)")
    } else {
        print("No user is signed in")
    }
}
.gitignore (2)

53-54: LGTM! Consider documenting framework setup.

The exclusion of audio codec XCFrameworks is correct as they should be managed through dependency management. Consider adding setup instructions to the README.

#!/bin/bash
# Check if setup instructions exist
if ! rg -q "xcframework|ogg|opus" README.md 2>/dev/null; then
  echo "No setup instructions found for XCFrameworks in README.md"
fi

118-118: LGTM! Consider CI integration for coverage reports.

The addition of coverage reports to .gitignore is appropriate. Since the PR focuses on implementing proper testing mechanisms, consider configuring CI to track coverage trends.

Would you like assistance in setting up coverage reporting in CI?

app/test.sh (1)

83-83: Consider Dynamically Calculating Total Tests

Currently, the script hardcodes the total number of tests attempted as 8. To improve maintainability when tests are added or removed, consider dynamically calculating the total number of tests.

Here's a suggestion to calculate the total tests dynamically:

-echo "- Total tests attempted: 8"
+TOTAL_TESTS=$(grep -c '^ *run_test ' "$0")
+echo "- Total tests attempted: $TOTAL_TESTS"
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f84e3e8 and 48e422e.

📒 Files selected for processing (9)
  • .gitignore (4 hunks)
  • app/.env.template (1 hunks)
  • app/Makefile (1 hunks)
  • app/ios/Flutter/Debug.xcconfig (1 hunks)
  • app/ios/Flutter/Release.xcconfig (1 hunks)
  • app/ios/Podfile (2 hunks)
  • app/ios/Runner/AppDelegate.swift (2 hunks)
  • app/pubspec.yaml (6 hunks)
  • app/test.sh (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • app/.env.template
  • app/ios/Flutter/Debug.xcconfig
  • app/ios/Flutter/Release.xcconfig
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/pubspec.yaml
🧰 Additional context used
🪛 rubocop
app/ios/Podfile

[warning] 17-17: URI::regexp is obsolete and should not be used. Instead, use URI::DEFAULT_PARSER.make_regexp.

(Lint/UriRegexp)

🔇 Additional comments (12)
app/Makefile (2)

1-2: LGTM! Proper use of .PHONY

The declaration correctly marks all targets as PHONY, preventing conflicts with similarly named files/directories.


11-14: LGTM! Well-implemented build target

The build target includes proper error handling and success feedback.

app/ios/Runner/AppDelegate.swift (1)

Line range hint 17-29: Verify Firebase initialization timing impact.

The Firebase configuration is placed at the start of the app lifecycle. While this is generally correct, it could impact app launch time. Consider moving non-critical Firebase service initialization to a background thread if startup performance becomes an issue.

To monitor the impact, you can add performance logging:

+let startTime = CFAbsoluteTimeGetCurrent()
 FirebaseApp.configure()
+let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
+print("Firebase configuration time: \(timeElapsed) seconds")
🧰 Tools
🪛 SwiftLint

[Warning] 19-19: Prefer at least one space after slashes for comments

(comment_spacing)

.gitignore (2)

68-68: LGTM! Consistent with cross-platform Firebase setup.

The addition of macOS GoogleService-Info.plist pattern is correctly placed in the macOS-specific section.


26-29: Verify impact on environment-specific Firebase configurations.

The consolidation of Google Services patterns might affect the ability to maintain separate Firebase configurations for development and production environments.

✅ Verification successful

The .gitignore changes are safe for environment-specific configurations

The codebase properly handles separate Firebase configurations for development and production environments through:

  • Distinct configuration files (firebase_options_dev.dart and firebase_options_prod.dart)
  • Separate output directories for Android (android/app/src/dev/ and android/app/src/prod/)
  • Separate output directories for iOS (ios/Config/Dev/ and ios/Config/Prod/)
  • Well-documented setup process in Setup.mdx

The generic patterns in .gitignore will correctly ignore the Google Services files in all environment-specific locations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for environment-specific Firebase configurations
echo "Checking for environment-specific Firebase configurations..."

# Search for environment-specific imports or configurations
rg -l "firebase.*(_dev|_prod)" 

# Search for Firebase environment setup in build configurations
fd -e yaml -e gradle -e pbxproj -e plist | xargs rg -l "firebase.*(_dev|_prod|development|production)"

Length of output: 348


Script:

#!/bin/bash
# Let's examine the specific Firebase configuration patterns in the identified files
echo "=== Checking Firebase configuration in main.dart ==="
rg -A 5 "firebase.*(_dev|_prod)" app/lib/main.dart

echo -e "\n=== Checking Firebase setup in codemagic.yaml ==="
rg -A 5 "firebase.*(_dev|_prod|development|production)" codemagic.yaml

echo -e "\n=== Checking Firebase setup documentation ==="
rg -A 5 "firebase.*(_dev|_prod)" docs/docs/get_started/Setup.mdx

Length of output: 6080

app/ios/Podfile (3)

4-6: LGTM: Appropriate top-level configurations.

The static framework linkage and modular headers configuration is correct for Firebase integration and optimal app size.


45-54: LGTM: Robust Flutter root configuration.

Good error handling with descriptive messages and proper validation of Flutter configuration.


76-118: LGTM: Comprehensive post-install configuration.

The post-install configuration is well-structured with:

  • Proper framework distribution settings
  • Correct framework search paths
  • Well-documented permission configurations
app/test.sh (4)

8-10: Command Availability Checks Implemented Correctly

The script appropriately checks for the required commands dart and flutter before proceeding, ensuring that necessary dependencies are installed.


13-31: run_test Function is Well-Structured

The run_test function effectively handles test execution, output formatting, and error handling. It checks for the existence of the test file and updates the EXIT_CODE when tests fail, which is crucial for accurate test result reporting.


33-35: Mock Generation Step is Correct

The script correctly regenerates mocks using dart run build_runner build --delete-conflicting-outputs, ensuring that any conflicting outputs are resolved and the mocks are up-to-date.


37-61: Tests Are Organized and Executed Properly

The script organizes tests into categories (core, providers, services, schema, integration) and runs them sequentially with clear and informative log messages, enhancing readability and maintainability.

app/Makefile Outdated Show resolved Hide resolved
app/Makefile Outdated Show resolved Hide resolved
app/ios/Podfile Show resolved Hide resolved
app/ios/Podfile Show resolved Hide resolved
vincentkoc and others added 4 commits November 3, 2024 12:40
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@vincentkoc
Copy link
Contributor Author

@beastoin ready for review

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@vincentkoc vincentkoc marked this pull request as draft November 4, 2024 07:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants