Skip to content

Text Module added in firebase.#174

Merged
anisha-e10 merged 10 commits intomainfrom
anisha/text-module-firebase
Dec 11, 2025
Merged

Text Module added in firebase.#174
anisha-e10 merged 10 commits intomainfrom
anisha/text-module-firebase

Conversation

@anisha-e10
Copy link
Copy Markdown
Collaborator

@anisha-e10 anisha-e10 commented Dec 11, 2025

Summary by CodeRabbit

  • New Features

    • Texts now persist to cloud with live sync, server timestamps, nested descriptions, emoji, ordering, search, parent filtering, and full CRUD via new providers.
    • App-wide snackbars added for user-facing messages.
  • Improvements

    • Bottom sheet scrolling/height behavior adjusted.
    • Query/filtering refined to scope results based on user presence.
    • Centralized Firestore error handling surfaces friendly messages.
  • Chores

    • Added backend constants for texts, emoji, and order index.
  • Tests

    • Expanded tests with fake Firestore, seeded data, and new test utilities.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 11, 2025

Walkthrough

Replaces in-memory text state with a Firestore-backed TextList provider (streaming snapshots and CRUD), adds Firestore-aware JSON (de)serialization to TextModel, introduces Firestore collection/field constants, gates sheet membership filtering on userId, removes isScrollControlled: true from a bottom-sheet, and migrates tests to FakeFirebaseFirestore.

Changes

Cohort / File(s) Summary
Firestore Constants
lib/constants/firestore_collection_constants.dart, lib/constants/firestore_field_constants.dart
Added texts collection constant and emoji, orderIndex field constants.
Text Model Serialization
lib/features/text/models/text_model.dart
Added TextModel.fromJson and toJson with Firestore Timestamp handling and nested description mapping.
Text Provider (Firestore)
lib/features/text/providers/text_providers.dart, lib/features/text/providers/text_providers.g.dart
Reworked TextList to stream from Firestore with a collection getter, _subscription lifecycle, CRUD methods (addText, deleteText, updateText*), new Riverpod helper providers, and updated generated provider hash.
Sheet Provider Query
lib/features/sheet/providers/sheet_providers.dart, lib/features/sheet/providers/sheet_providers.g.dart
Removed unconditional membership filter; apply Filter.or(...) only when userId is non-null and use an explicit else branch for null users; updated generated provider hash.
UI Widget
lib/common/widgets/choose_color_bottom_sheet.dart
Removed isScrollControlled: true from showModalBottomSheet call.
Snackbar & Error Handling
lib/common/providers/common_providers.dart, lib/common/services/snackbar_service.dart, lib/common/utils/firestore_error_handler.dart, lib/main.dart
Added SnackbarService + provider and messengerKey wiring; introduced runFirestoreOperation utility for centralized Firestore error handling and logging; wired MaterialApp scaffoldMessengerKey to the service.
Tests — Firestore Integration
test/features/text/providers/text_providers_test.dart, test/features/text/actions/text_actions_test.dart, test/features/text/screens/text_block_details_screen_test.dart
Migrated tests to FakeFirebaseFirestore, added provider overrides and seeded test data, converted tests to async/await, and adjusted assertions for Firestore-backed streams.
Test Utilities
test/features/text/utils/mock_fakefirestore_text.dart, test/features/text/utils/text_utils.dart
Added mock TextModel fixtures (mockText1, mockText2, ids, test user) and a minor newline fix.
Minor Generated/Docs
lib/features/sheet/providers/sheet_providers.g.dart, lib/features/text/providers/text_providers.g.dart
Updated generated provider hash literals and removed redundant comments.
Small UI tweak
lib/common/widgets/choose_color_bottom_sheet.dart
Removed isScrollControlled: true from bottom-sheet invocation (duplicate cohort entry consolidated).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as UI / Widgets
  participant Provider as TextListProvider (Riverpod)
  participant Firestore as Firestore (Fake/Real)

  UI->>Provider: subscribe/read text list
  Provider->>Firestore: collection(...).snapshots()
  Firestore-->>Provider: QuerySnapshot stream
  Provider->>Provider: map docs -> TextModel list
  Provider-->>UI: emit updated list

  UI->>Provider: addText(TextModel)
  Provider->>Firestore: collection.doc(id).set(text.toJson())
  Firestore-->>Provider: snapshot with new doc
  Provider-->>UI: emit updated list

  UI->>Provider: updateTextTitle(id, title)
  Provider->>Firestore: doc(id).update({title, updatedAt: FieldValue.serverTimestamp()})
  Firestore-->>Provider: snapshot with updated doc
  Provider-->>UI: emit updated item
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Focus areas:
    • lib/features/text/providers/text_providers.dart — stream subscription lifecycle, onDispose, re-subscribe on rebuild, async CRUD correctness, and FieldValue.serverTimestamp usage.
    • lib/features/text/models/text_model.dart — Timestamp ⇄ DateTime conversion and nested description mapping.
    • lib/features/sheet/providers/sheet_providers.dart — conditional Filter logic and query equivalence.
    • Tests — FakeFirebaseFirestore seeding/waits, provider overrides, and timing-sensitive assertions.

Possibly related PRs

Suggested reviewers

  • kumarpalsinh25

Poem

🐇 I hopped through timestamps, carrots in JSON rows,

Streams hummed softly where Firestore wind blows.
Docs found their order, emojis tucked in place,
Subscriptions stitched the list with gentle pace.
A rabbit nibbles code — commit, then celebrate!

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Text Module added in firebase' is vague and overly broad. It doesn't accurately reflect the main scope of changes, which include Firestore backend integration, new constants, test infrastructure updates, snackbar service infrastructure, and error handling utilities across multiple features. Revise the title to be more specific and descriptive of the primary change, such as 'Integrate Text module with Firestore backend and add supporting infrastructure' or 'Add Firestore-backed text operations with error handling and tests'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch anisha/text-module-firebase

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@anisha-e10 anisha-e10 marked this pull request as ready for review December 11, 2025 07:22
Copy link
Copy Markdown

@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: 3

🧹 Nitpick comments (8)
test/features/text/screens/text_block_details_screen_test.dart (2)

37-39: Use FirestoreCollections.texts constant instead of hardcoded string.

The collection name 'texts' is hardcoded here, but a constant FirestoreCollections.texts was added in this PR specifically for this purpose. Using the constant ensures consistency and reduces the risk of typos.

+import 'package:zoe/constants/firestore_collection_constants.dart';
       for (final text in textList) {
-        await fakeFirestore.collection('texts').doc(text.id).set(text.toJson());
+        await fakeFirestore.collection(FirestoreCollections.texts).doc(text.id).set(text.toJson());
       }

41-47: Add assertion after retry loop to fail fast if data is not ready.

The retry loop silently exits after 20 attempts without verifying the data was actually populated. If data propagation takes longer than expected, subsequent tests may fail with confusing errors. Consider adding an assertion or throwing an exception.

       int retries = 0;
       while (container.read(textListProvider).length < textList.length &&
           retries < 20) {
         await Future.delayed(const Duration(milliseconds: 50));
         retries++;
       }
+
+      expect(
+        container.read(textListProvider).length,
+        greaterThanOrEqualTo(textList.length),
+        reason: 'Firestore data did not propagate within timeout',
+      );
     });
test/features/text/actions/text_actions_test.dart (2)

18-45: Missing tearDown to dispose the ProviderContainer.

The ProviderContainer should be disposed after each test to clean up resources and cancel subscriptions. This prevents potential memory leaks and test interference.

Add a tearDown block:

   setUp(() async {
     // ... existing setup code ...
   });
+
+  tearDown(() {
+    container.dispose();
+  });

90-93: Helper verifySnackbarFunctionality provides no meaningful verification.

This helper only checks that showSnackBar is a function (which is always true). It doesn't verify that a snackbar was actually shown. Consider removing these assertions or implementing proper snackbar verification with a mock.

test/features/text/providers/text_providers_test.dart (2)

13-13: Shadowed variable testUser is redundant.

testUser is already imported from mock_fakefirestore_text.dart. This local declaration shadows the import and creates potential confusion.

Remove the local declaration:

-  final testUser = 'test-user';

15-39: Missing tearDown to dispose the ProviderContainer.

Same as the actions test file, add a tearDown to properly clean up resources.

Add disposal:

   setUp(() async {
     // ... existing setup code ...
   });
+
+  tearDown(() {
+    container.dispose();
+  });
lib/features/text/providers/text_providers.dart (2)

28-34: Missing error handling for Firestore stream.

If the Firestore stream encounters an error (network issues, permission denied), it will be unhandled and could crash the app. Consider adding error handling to the stream listener.

Add error handling:

     _subscription = query.snapshots().listen((snapshot) {
       final items = snapshot.docs
           .map((doc) => TextModel.fromJson(doc.data()))
           .whereType<TextModel>()
           .toList();
       state = items;
-    });
+    }, onError: (error) {
+      // Log error or set error state
+      // Consider: state = []; or expose error state
+    });

43-45: Consider using serverTimestamp() for createdAt on new documents.

Unlike the update methods which set updatedAt via serverTimestamp(), addText relies on the caller-provided createdAt. For consistency and server-side accuracy, consider setting createdAt server-side.

   Future<void> addText(TextModel text) async {
-    await collection.doc(text.id).set(text.toJson());
+    final data = text.toJson();
+    data[FirestoreFieldConstants.createdAt] = FieldValue.serverTimestamp();
+    await collection.doc(text.id).set(data);
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3758a3 and 5008310.

📒 Files selected for processing (13)
  • lib/common/widgets/choose_color_bottom_sheet.dart (0 hunks)
  • lib/constants/firestore_collection_constants.dart (1 hunks)
  • lib/constants/firestore_field_constants.dart (1 hunks)
  • lib/features/sheet/providers/sheet_providers.dart (0 hunks)
  • lib/features/sheet/providers/sheet_providers.g.dart (1 hunks)
  • lib/features/text/models/text_model.dart (2 hunks)
  • lib/features/text/providers/text_providers.dart (2 hunks)
  • lib/features/text/providers/text_providers.g.dart (1 hunks)
  • test/features/text/actions/text_actions_test.dart (18 hunks)
  • test/features/text/providers/text_providers_test.dart (1 hunks)
  • test/features/text/screens/text_block_details_screen_test.dart (2 hunks)
  • test/features/text/utils/mock_fakefirestore_text.dart (1 hunks)
  • test/features/text/utils/text_utils.dart (1 hunks)
💤 Files with no reviewable changes (2)
  • lib/common/widgets/choose_color_bottom_sheet.dart
  • lib/features/sheet/providers/sheet_providers.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Test zoe_native plugin
  • GitHub Check: format
  • GitHub Check: macos
  • GitHub Check: android
  • GitHub Check: linux
  • GitHub Check: test
  • GitHub Check: ios
  • GitHub Check: windows
🔇 Additional comments (11)
test/features/text/utils/text_utils.dart (1)

6-13: LGTM!

The function logic is correct with proper bounds checking. The only change is adding a trailing newline, which is a minor formatting improvement.

lib/features/sheet/providers/sheet_providers.g.dart (1)

44-44: LGTM!

Generated code with updated hash reflecting changes in the source provider. No manual modifications needed.

lib/features/text/providers/text_providers.g.dart (1)

44-44: LGTM!

Generated code with updated hash reflecting changes in the source text provider implementation. No manual modifications needed.

lib/constants/firestore_collection_constants.dart (1)

1-4: LGTM!

The new texts collection constant follows the established pattern and provides a centralized reference for Firestore text collection access.

lib/constants/firestore_field_constants.dart (1)

9-10: LGTM!

The new field constants emoji and orderIndex follow the established pattern and support the TextModel's Firestore field operations.

test/features/text/screens/text_block_details_screen_test.dart (1)

28-35: Test setup correctly configures Firestore-backed providers.

The setUp properly initializes FakeFirebaseFirestore, overrides both firestoreProvider and loggedInUserProvider, and seeds test data. This provides a solid foundation for Firestore-backed integration tests.

test/features/text/utils/mock_fakefirestore_text.dart (1)

1-32: LGTM!

Mock data is well-structured with clear identifiers and realistic test values. The mocks appropriately test both scenarios (with and without emoji).

test/features/text/actions/text_actions_test.dart (1)

95-166: Well-structured tests with good edge case coverage.

The tests cover important scenarios including empty descriptions, null content, and various text configurations. The helper functions for clipboard and share content construction are clear and maintainable.

test/features/text/providers/text_providers_test.dart (1)

50-186: Comprehensive test coverage for Firestore-backed providers.

Tests thoroughly cover CRUD operations, filtering, searching, and sorting. The helper methods improve readability, and the async handling with short delays is appropriate for stream-based updates.

lib/features/text/providers/text_providers.dart (2)

47-49: deleteText and update methods may throw if document doesn't exist.

Firestore's update() throws if the document doesn't exist. While delete() is idempotent, callers should be aware that update operations can fail. Consider adding try-catch or documenting this behavior.

Verify that callers handle potential Firestore exceptions appropriately.


83-114: Derived providers are well-implemented.

The providers for single text lookup, parent filtering, search, and sorting follow Riverpod best practices by watching textListProvider and deriving state. Using firstOrNull safely handles missing items.

Comment thread lib/features/text/models/text_model.dart Outdated
Comment thread lib/features/text/models/text_model.dart Outdated
Comment thread lib/features/text/models/text_model.dart Outdated
Copy link
Copy Markdown

@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

♻️ Duplicate comments (1)
lib/features/text/models/text_model.dart (1)

85-86: Handle null timestamps to prevent runtime crashes.

Timestamp.fromDate() expects a non-null DateTime, but createdAt and updatedAt are nullable fields. If a TextModel is constructed with null timestamps (e.g., via the constructor rather than fromJson), calling toJson() will throw a runtime error.

Apply this diff to handle null values:

-      'createdAt': Timestamp.fromDate(createdAt),
-      'updatedAt': Timestamp.fromDate(updatedAt),
+      'createdAt': createdAt != null ? Timestamp.fromDate(createdAt!) : null,
+      'updatedAt': updatedAt != null ? Timestamp.fromDate(updatedAt!) : null,

Alternatively, if timestamps should never be null in Firestore, consider making them required fields in the constructor and ensuring fromJson throws on missing values rather than defaulting to DateTime.now().

🧹 Nitpick comments (2)
lib/features/text/models/text_model.dart (2)

80-83: Consider omitting null description fields.

When description is null, the map still includes the field with null values for both plainText and htmlText. While this works, you could optimize by omitting the field entirely when null to reduce payload size.

Apply this diff if you want to omit null descriptions:

-      'description': {
-        'plainText': description?.plainText,
-        'htmlText': description?.htmlText,
-      },
+      if (description != null)
+        'description': {
+          'plainText': description!.plainText,
+          'htmlText': description!.htmlText,
+        },

63-68: This timestamp fallback pattern is consistent across models; clarify if it's intentional design or requires rethinking.

DateTime.now() as a fallback for missing createdAt and updatedAt fields is used consistently throughout the codebase (also in SheetModel.fromJson at lines 169-174, and in constructors). While this pattern is deliberate and not isolated to this model, consider whether missing timestamps should throw an error or use a different fallback, as the current approach could mask data integrity issues. If this is intentional fallback behavior, document it; otherwise, align all models to handle missing timestamps more explicitly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5008310 and 0e19b1c.

📒 Files selected for processing (1)
  • lib/features/text/models/text_model.dart (2 hunks)
🔇 Additional comments (3)
lib/features/text/models/text_model.dart (3)

1-1: LGTM!

The cloud_firestore import is necessary for Timestamp handling in the new serialization methods.


43-43: LGTM!

The copyWith method now correctly preserves the original updatedAt value when none is provided, addressing the previous concern.


55-60: LGTM!

The null check for the description field correctly handles missing data, addressing the previous critical issue.

Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
lib/features/text/models/text_model.dart (1)

63-68: Add type safety for Timestamp casts in fromJson.

The direct cast (json['createdAt'] as Timestamp) will throw if the value is not null but also not a Timestamp (e.g., from malformed data or non-Firestore sources). Consider adding type checking for robustness.

Apply this diff to add defensive type checking:

       createdAt: json['createdAt'] == null
           ? DateTime.now()
-          : (json['createdAt'] as Timestamp).toDate(),
+          : json['createdAt'] is Timestamp
+              ? (json['createdAt'] as Timestamp).toDate()
+              : DateTime.now(),
       updatedAt: json['updatedAt'] == null
           ? DateTime.now()
-          : (json['updatedAt'] as Timestamp).toDate(),
+          : json['updatedAt'] is Timestamp
+              ? (json['updatedAt'] as Timestamp).toDate()
+              : DateTime.now(),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e19b1c and 9b32924.

📒 Files selected for processing (1)
  • lib/features/text/models/text_model.dart (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: test
  • GitHub Check: windows
  • GitHub Check: Test zoe_native plugin
  • GitHub Check: linux
  • GitHub Check: ios
  • GitHub Check: format
  • GitHub Check: android
  • GitHub Check: macos
🔇 Additional comments (2)
lib/features/text/models/text_model.dart (2)

85-86: Verify nullability of createdAt and updatedAt fields.

Timestamp.fromDate() will throw if createdAt or updatedAt are null. Verify that these fields are guaranteed to be non-null in the ContentModel superclass.


43-43: Non-standard copyWith behavior auto-updates timestamp on any field modification.

The current implementation sets updatedAt: updatedAt ?? DateTime.now(), which updates the timestamp whenever copyWith() is called without explicitly providing updatedAt. This differs from the standard pattern (updatedAt: updatedAt ?? this.updatedAt), which would preserve the original value.

Clarify whether auto-updating the timestamp on any modification is intentional. If not, change to preserve the original timestamp when not explicitly provided.

Copy link
Copy Markdown

@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: 2

🧹 Nitpick comments (1)
lib/features/text/providers/text_providers.dart (1)

62-70: Consider using Description.toJson() for consistency.

The description update manually constructs a map with 'plainText' and 'htmlText' fields. If the Description class has a toJson() method, using it would improve maintainability and reduce the risk of inconsistencies if the model serialization changes.

If Description has toJson(), apply this diff:

 Future<void> updateTextDescription(String textId, Description desc) async {
   await collection.doc(textId).update({
-    FirestoreFieldConstants.description: {
-      'plainText': desc.plainText,
-      'htmlText': desc.htmlText,
-    },
+    FirestoreFieldConstants.description: desc.toJson(),
     FirestoreFieldConstants.updatedAt: FieldValue.serverTimestamp(),
   });
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b32924 and aebd10d.

📒 Files selected for processing (3)
  • lib/features/sheet/providers/sheet_providers.g.dart (1 hunks)
  • lib/features/text/providers/text_providers.dart (2 hunks)
  • lib/features/text/providers/text_providers.g.dart (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/features/sheet/providers/sheet_providers.g.dart
  • lib/features/text/providers/text_providers.g.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: linux
  • GitHub Check: Test zoe_native plugin
  • GitHub Check: test
  • GitHub Check: android
  • GitHub Check: windows
  • GitHub Check: macos
  • GitHub Check: ios

Comment thread lib/features/text/providers/text_providers.dart
Comment thread lib/features/text/providers/text_providers.dart
Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/features/text/actions/text_actions_test.dart (1)

91-94: verifySnackbarFunctionality doesn't verify snackbar behavior.

This only checks that CommonUtils.showSnackBar is a Function, which is always true. It doesn't verify the snackbar was actually called with correct parameters. Either remove this assertion or use a mock to verify invocation.

-  // Helper method to verify snackbar functionality
-  void verifySnackbarFunctionality() {
-    expect(CommonUtils.showSnackBar, isA<Function>());
-  }
+  // TODO: Mock CommonUtils.showSnackBar to verify it's called with expected message
🧹 Nitpick comments (5)
test/features/text/actions/text_actions_test.dart (5)

37-46: Polling wait loop has timeout but missing assertion failure handling.

The retry loop waits up to 1 second (20 × 50ms) for data to load, with an assertion at the end. This is reasonable, but consider extracting this pattern into a reusable helper since it's duplicated across test files.

+// Consider extracting to a shared test utility
+Future<void> waitForProviderData<T>(
+  ProviderContainer container,
+  ProviderListenable<List<T>> provider,
+  int expectedCount, {
+  int maxRetries = 20,
+  Duration delay = const Duration(milliseconds: 50),
+}) async {
+  int retries = 0;
+  while (container.read(provider).length < expectedCount && retries < maxRetries) {
+    await Future.delayed(delay);
+    retries++;
+  }
+}

49-66: Test helper duplicates production logic — tests become tautological.

buildClipboardContent replicates the clipboard formatting logic from production code. If production logic changes, tests pass even when broken because both implementations change together. Instead, assert against expected literal strings or extract the production helper and test it directly.


68-89: Same concern: buildShareMessage duplicates production logic.

This helper mirrors the share message construction. Consider testing against known expected outputs for specific inputs rather than re-implementing the formatting logic.


132-157: Test adds data but doesn't clean up — may affect other tests.

The test adds textWithEmptyDesc to Firestore but doesn't remove it afterward. If tests run in a shared order, this could cause flakiness. Consider using unique IDs per test or cleaning up in tearDown.


356-379: Widget instantiation tests provide limited value.

These tests only verify constructor parameters are stored correctly. Consider adding widget tests with WidgetTester to verify actual rendering and behavior, or remove these if the widget is tested elsewhere.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aebd10d and 257d24e.

📒 Files selected for processing (3)
  • test/features/text/actions/text_actions_test.dart (18 hunks)
  • test/features/text/providers/text_providers_test.dart (1 hunks)
  • test/features/text/screens/text_block_details_screen_test.dart (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/features/text/screens/text_block_details_screen_test.dart
🔇 Additional comments (6)
test/features/text/actions/text_actions_test.dart (2)

97-112: LGTM — test properly arranges, acts, and asserts.

The test correctly fetches text by index, builds clipboard content, and verifies expected substrings.


207-217: Delete test looks correct but relies on pre-existing data.

The test properly awaits deletion and verifies the item is removed. Good async handling.

test/features/text/providers/text_providers_test.dart (4)

42-49: Good helper abstraction for provider reads.

These helper methods improve test readability by encapsulating provider access patterns.


59-74: Async test properly awaits mutations and stream propagation.

The test correctly awaits addText and includes a delay for stream updates. The assertions verify both list length and item presence.


76-83: Delete test correctly handles async flow.

Good pattern: await deletion, delay for stream, then verify removal.


180-187: Sorted texts test assumes alphabetical ordering — verify sort implementation.

The comment notes 'Understanding Sheets' comes before 'Welcome to Zoe!' alphabetically. Ensure sortedTextsProvider actually sorts by title and the mock data titles match this expectation.

Comment thread test/features/text/actions/text_actions_test.dart
Comment thread test/features/text/providers/text_providers_test.dart
Comment thread test/features/text/providers/text_providers_test.dart
Comment thread test/features/text/providers/text_providers_test.dart
Copy link
Copy Markdown

@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: 1

♻️ Duplicate comments (1)
test/features/text/actions/text_actions_test.dart (1)

21-47: Missing tearDown to dispose the ProviderContainer.

ProviderContainer should be disposed in tearDown to prevent resource leaks and ensure test isolation.

     expect(
       container.read(textListProvider).length,
       greaterThanOrEqualTo(2),
       reason: 'Provider failed to load test data within timeout',
     );
   });
+
+  tearDown(() {
+    container.dispose();
+  });
🧹 Nitpick comments (3)
test/features/text/screens/text_block_details_screen_test.dart (1)

30-35: Consider using ProviderContainer.test() for consistency.

The second test file (text_actions_test.dart) uses ProviderContainer.test() while this file uses ProviderContainer(). For consistency and to leverage any test-specific behavior, consider using the .test() constructor here as well.

-      container = ProviderContainer(
+      container = ProviderContainer.test(
         overrides: [
           firestoreProvider.overrideWithValue(fakeFirestore),
           loggedInUserProvider.overrideWithValue(AsyncValue.data(testUser)),
         ],
       );
test/features/text/actions/text_actions_test.dart (2)

37-46: Polling loop for async data loading is reasonable but could be improved.

The retry-based approach to wait for provider data is pragmatic. However, consider extracting this into a reusable test utility function, especially if similar patterns appear in other test files. The 1-second timeout (20 × 50ms) should be sufficient for fake Firestore operations.


91-94: Weak assertion - verifySnackbarFunctionality doesn't verify actual behavior.

This helper only checks that CommonUtils.showSnackBar is a function, not that it was called or with what parameters. Consider either removing these calls (since they don't add test value) or using a mock to verify actual invocations.

-  // Helper method to verify snackbar functionality
-  void verifySnackbarFunctionality() {
-    expect(CommonUtils.showSnackBar, isA<Function>());
-  }

If you need to verify snackbar behavior, consider mocking CommonUtils.showSnackBar and verifying invocations with expected parameters.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 257d24e and 0204d5b.

📒 Files selected for processing (3)
  • lib/features/text/models/text_model.dart (2 hunks)
  • test/features/text/actions/text_actions_test.dart (18 hunks)
  • test/features/text/screens/text_block_details_screen_test.dart (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/features/text/models/text_model.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: test
  • GitHub Check: windows
  • GitHub Check: Test zoe_native plugin
  • GitHub Check: linux
  • GitHub Check: ios
  • GitHub Check: macos
  • GitHub Check: android
  • GitHub Check: format
🔇 Additional comments (8)
test/features/text/screens/text_block_details_screen_test.dart (3)

45-58: LGTM!

The test correctly verifies the empty state behavior when a non-existent text block ID is provided. The shared container setup with Firestore seeding is appropriate for this scenario.


60-78: LGTM!

Comprehensive test validating that valid text block data renders all expected UI components correctly.


104-122: LGTM!

Good coverage of the editing state toggle functionality, verifying both entering and exiting edit mode.

test/features/text/actions/text_actions_test.dart (5)

49-66: LGTM!

The helper methods buildClipboardContent and buildShareMessage are well-structured and clearly document the expected format of clipboard/share content for assertions.

Also applies to: 68-89


132-158: Good async test pattern for adding data before assertions.

The test correctly awaits the addText operation before reading and verifying the data.


207-219: LGTM!

Good test for the delete operation with proper async handling.


260-292: LGTM!

Comprehensive edge case coverage for share message generation with empty descriptions, emoji-only titles, and null descriptions.

Also applies to: 294-327, 329-357


360-384: LGTM!

Simple instantiation tests for ShareItemsBottomSheet correctly verify widget properties.

Comment thread test/features/text/screens/text_block_details_screen_test.dart
Copy link
Copy Markdown

@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: 2

♻️ Duplicate comments (3)
lib/features/text/providers/text_providers.dart (3)

18-19: User-based access control still missing.

The past review comment about adding user-based access control remains unaddressed. The collection reference does not filter by user, and line 28 calls collection.snapshots() without user-scoped filtering.


48-70: Handle null return from runFirestoreOperation.

Similar to addText, these methods don't check the return value from runFirestoreOperation. The same fix should be applied to propagate success/failure status.


85-93: Handle null return from runFirestoreOperation.

Same issue as the other CRUD methods - no null handling.

🧹 Nitpick comments (3)
lib/common/services/snackbar_service.dart (1)

7-9: Consider adding customization options for SnackBar.

The show method currently only accepts a message string. For better UX flexibility, consider adding optional parameters for duration, action buttons, or background color.

Example enhancement:

void show(
  String message, {
  Duration duration = const Duration(seconds: 4),
  SnackBarAction? action,
  Color? backgroundColor,
}) {
  messengerKey.currentState?.showSnackBar(
    SnackBar(
      content: Text(message),
      duration: duration,
      action: action,
      backgroundColor: backgroundColor,
    ),
  );
}
lib/features/text/providers/text_providers.dart (1)

72-83: Use Description.toJson() for consistency.

The description object is manually constructed as a map, which is inconsistent with how TextModel uses toJson(). If Description has a toJson() method, use it to ensure consistency and maintainability.

 Future<void> updateTextDescription(String textId, Description desc) async {
   await runFirestoreOperation(
     ref,
     () => collection.doc(textId).update({
-      FirestoreFieldConstants.description: {
-        'plainText': desc.plainText,
-        'htmlText': desc.htmlText,
-      },
+      FirestoreFieldConstants.description: desc.toJson(),
       FirestoreFieldConstants.updatedAt: FieldValue.serverTimestamp(),
     }),
   );
 }
lib/common/utils/firestore_error_handler.dart (1)

9-45: Extend error code coverage for better UX.

The error handler only maps three Firestore error codes (permission-denied, not-found, unavailable). Consider adding more common codes like aborted, already-exists, cancelled, data-loss, deadline-exceeded, failed-precondition, resource-exhausted, and unauthenticated for more specific user feedback.

Example additions:

 switch (e.code) {
   case 'permission-denied':
     snackbar.show('You do not have permission.');
     break;
   case 'not-found':
     snackbar.show('Document not found.');
     break;
   case 'unavailable':
     snackbar.show('Network unavailable.');
     break;
+  case 'unauthenticated':
+    snackbar.show('Please sign in to continue.');
+    break;
+  case 'already-exists':
+    snackbar.show('This item already exists.');
+    break;
+  case 'deadline-exceeded':
+    snackbar.show('Request timed out. Please try again.');
+    break;
   default:
     snackbar.show('Unexpected Firestore error.');
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0204d5b and 48ec365.

📒 Files selected for processing (5)
  • lib/common/providers/common_providers.dart (2 hunks)
  • lib/common/services/snackbar_service.dart (1 hunks)
  • lib/common/utils/firestore_error_handler.dart (1 hunks)
  • lib/features/text/providers/text_providers.dart (2 hunks)
  • lib/main.dart (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: linux
  • GitHub Check: test
  • GitHub Check: format
  • GitHub Check: ios
  • GitHub Check: Test zoe_native plugin
  • GitHub Check: windows
  • GitHub Check: macos
  • GitHub Check: android
🔇 Additional comments (3)
lib/features/text/providers/text_providers.dart (1)

96-127: LGTM!

The query and filtering providers are well-structured and provide good data access patterns. The sorting and filtering logic is clear and correct.

lib/main.dart (1)

11-11: LGTM!

The snackbar service integration is correctly wired into the MaterialApp. The scaffoldMessengerKey will enable global snackbar functionality across the app.

Also applies to: 62-62

lib/common/providers/common_providers.dart (1)

6-6: LGTM!

The snackbar service provider is correctly implemented. Using Provider (non-disposing) ensures the service remains available throughout the app lifecycle, which is appropriate for a global service.

Also applies to: 25-28

Comment on lines +24 to +39
List<TextModel> build() {
_subscription?.cancel();
_subscription = null;

_subscription = collection.snapshots().listen((snapshot) {
state = snapshot.docs
.map((doc) => TextModel.fromJson(doc.data()))
.toList();
});

void addText(TextModel text) {
state = [...state, text];
ref.onDispose(() {
_subscription?.cancel();
});

return [];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Review subscription lifecycle management.

The build() method recreates the Firestore subscription every time it's called, which can happen during hot-reload, provider refresh, or dependency changes. While lines 25-26 cancel the previous subscription, this reactive approach may still cause brief periods with multiple active listeners or missed events.

Additionally, ref.onDispose() is registered on every build() call, which could accumulate multiple identical disposal callbacks.

Consider restructuring to ensure subscription is created once:

 @override
 List<TextModel> build() {
-  _subscription?.cancel();
-  _subscription = null;
-
-  _subscription = collection.snapshots().listen((snapshot) {
-    state = snapshot.docs
-        .map((doc) => TextModel.fromJson(doc.data()))
-        .toList();
-  });
-
-  ref.onDispose(() {
+  if (_subscription == null) {
+    _subscription = collection.snapshots().listen((snapshot) {
+      state = snapshot.docs
+          .map((doc) => TextModel.fromJson(doc.data()))
+          .toList();
+    });
+    
+    ref.onDispose(() {
+      _subscription?.cancel();
+    });
+  }
-    _subscription?.cancel();
-  });
-
   return [];
 }
📝 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
List<TextModel> build() {
_subscription?.cancel();
_subscription = null;
_subscription = collection.snapshots().listen((snapshot) {
state = snapshot.docs
.map((doc) => TextModel.fromJson(doc.data()))
.toList();
});
void addText(TextModel text) {
state = [...state, text];
ref.onDispose(() {
_subscription?.cancel();
});
return [];
}
List<TextModel> build() {
if (_subscription == null) {
_subscription = collection.snapshots().listen((snapshot) {
state = snapshot.docs
.map((doc) => TextModel.fromJson(doc.data()))
.toList();
});
ref.onDispose(() {
_subscription?.cancel();
});
}
return [];
}
🤖 Prompt for AI Agents
In lib/features/text/providers/text_providers.dart around lines 24 to 39, the
build() method is recreating the Firestore subscription and re-registering
ref.onDispose() on every call; change it so the subscription and the onDispose
callback are created only once: guard creation with a check like if
(_subscription == null) { _subscription = collection.snapshots().listen(...);
ref.onDispose(() { _subscription?.cancel(); _subscription = null; }); } and
remove the unconditional cancel/reset at the top of build so you don't briefly
create multiple listeners or accumulate disposal callbacks.

Comment on lines +41 to 46
Future<void> addText(TextModel text) async {
await runFirestoreOperation(
ref,
() => collection.doc(text.id).set(text.toJson()),
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle null return from runFirestoreOperation.

runFirestoreOperation returns null on error, but this method doesn't check the return value. Callers of addText may assume success when the operation actually failed.

Consider propagating errors or returning a boolean success indicator:

-Future<void> addText(TextModel text) async {
-  await runFirestoreOperation(
+Future<bool> addText(TextModel text) async {
+  final result = await runFirestoreOperation(
     ref,
     () => collection.doc(text.id).set(text.toJson()),
   );
+  return result != null;
 }
📝 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
Future<void> addText(TextModel text) async {
await runFirestoreOperation(
ref,
() => collection.doc(text.id).set(text.toJson()),
);
}
Future<bool> addText(TextModel text) async {
final result = await runFirestoreOperation(
ref,
() => collection.doc(text.id).set(text.toJson()),
);
return result != null;
}
🤖 Prompt for AI Agents
In lib/features/text/providers/text_providers.dart around lines 41–46, addText
calls runFirestoreOperation but ignores its return which is null on error;
change addText to handle that by capturing the result, and either propagate the
failure (throw a descriptive exception) or return a boolean success indicator:
update the method signature to Future<bool> (or keep Future<void> and rethrow),
await and store the runFirestoreOperation result, if result == null then return
false (or throw a specific exception), otherwise return true (or complete
normally), and update any callers to handle the new return/exception behavior.

@anisha-e10 anisha-e10 merged commit f08bcd9 into main Dec 11, 2025
7 of 9 checks passed
@anisha-e10 anisha-e10 deleted the anisha/text-module-firebase branch December 11, 2025 13:12
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