mobile bluesky app made with flutter lazurite.stormlightlabs.org/
mobile bluesky flutter
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: add testing strategy & refactoring plans

+312
+155
docs/specs/testing.md
··· 1 + # Testing Strategy 2 + 3 + Audit of the presentation layer and evaluation of visual testing tooling to 4 + improve test coverage, reduce code duplication, and establish a golden testing 5 + baseline. 6 + 7 + ## Presentation Layer Audit 8 + 9 + A full audit of `lib/features/*/presentation/` identified significant 10 + duplication across 40+ files. The findings group into ten categories. 11 + 12 + ### 1. Duplicated Utility Functions 13 + 14 + | Function | Pattern | Occurrences | 15 + | ----------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | 16 + | `_initials(String)` | Generates initials from display name | 10 files (post_card, grid_post_card, post_embed_view, notification items, suggested_follows, labeler_detail, moderation_settings, search, hashtag) | 17 + | `_formatCount(int)` | Formats numbers with K/M suffixes | 5 files (post_action_bar, post_card_footer, starter_pack_card, starter_pack_detail, profile_screen) | 18 + | `_formatTime(DateTime)` | Relative time strings ("2h ago") | 4 files (notification items, search, hashtag) — overlaps with `formatPostTime()` in post_card_footer | 19 + 20 + **Target**: Extract to `lib/shared/utils/format_utils.dart`. 21 + 22 + ### 2. Duplicated Widgets 23 + 24 + **Avatar display** — 14 files repeat `CircleAvatar` / `ModeratedAvatar` with 25 + identical styling. Extract to a configurable `ProfileAvatar` widget. 26 + 27 + **Author name + handle** — Repeated two-line text widget (displayName / @handle) 28 + in post_card, grid_post_card, post_embed_view, convo_list_item. Extract to 29 + `ActorNameWidget`. 30 + 31 + **Greyscale color filter** — Identical 4x5 color matrix defined in 32 + grid_post_card and profile_screen. Extract to `lib/core/theme/color_filters.dart`. 33 + 34 + **Notification reason icon** — Large switch statement mapping notification 35 + reasons to icons/colors duplicated identically in notification_list_item and 36 + grouped_notification_list_item. Extract to `NotificationIconMapper`. 37 + 38 + ### 3. Bottom Sheets & Dialogs 39 + 40 + **Modal bottom sheets** — 9 files repeat `showModalBottomSheet` with ListTile 41 + option lists. Create `OptionsSheet` builder. 42 + 43 + **Confirmation dialogs** — 26 occurrences of AlertDialog with 44 + title/content/cancel/confirm. Create `ConfirmationDialog(title, content, 45 + confirmLabel, onConfirm)`. 46 + 47 + ### 4. State Handling Patterns 48 + 49 + **Loading** — `Center(child: CircularProgressIndicator())` in 8+ screens. 50 + **Error with retry** — Center + error message + retry button in 8+ screens. 51 + **Empty state** — Center + message + optional action in multiple screens. 52 + 53 + Create `LoadingState`, `ErrorState(message, onRetry)`, `EmptyState(message, 54 + icon, action)` widgets in `lib/shared/presentation/widgets/`. 55 + 56 + ### 5. SnackBar Display 57 + 58 + `ScaffoldMessenger.of(context).showSnackBar(SnackBar(..., behavior: 59 + floating))` appears in 14 files. Create `showAppSnackBar(context, message, 60 + {isError})` helper. 61 + 62 + ### 6. Theme Access Boilerplate 63 + 64 + `Theme.of(context).colorScheme.*` appears 142 times across 27 files. 65 + `Border.all(color: colorScheme.outlineVariant)` is the most common repeated 66 + decoration. Consider a `BuildContext` extension: 67 + 68 + ```dart 69 + extension ThemeX on BuildContext { 70 + ColorScheme get colorScheme => Theme.of(this).colorScheme; 71 + } 72 + ``` 73 + 74 + ### 7. Spacing Constants 75 + 76 + `EdgeInsets.symmetric(horizontal: 16)` appears 34+ times. `SizedBox(height: 8)` 77 + and variants repeat throughout. Define constants in 78 + `lib/core/theme/spacing.dart`. 79 + 80 + ### 8. Navigation Helpers 81 + 82 + Profile navigation (`GoRouter.maybeOf(context)?.push('/profile/view?actor=...')`) 83 + repeated across post cards and notification items. Extract to route helper 84 + functions. 85 + 86 + ### 9. Haptic Feedback 87 + 88 + 17 occurrences of `HapticFeedback.mediumImpact()` across 6 files. Minor, but a 89 + `HapticHelper` would centralize. 90 + 91 + ### 10. List Item Tiles 92 + 93 + 23 occurrences of ListTile with avatar/title/subtitle across features. A 94 + `BaseListItemTile` could reduce this, though diversity of layouts may limit 95 + reuse. 96 + 97 + ## Visual Testing Evaluation 98 + 99 + ### Current State 100 + 101 + - 122 test files across 16 feature modules 102 + - Stack: `flutter_test`, `bloc_test`, `mocktail` 103 + - Test types: unit (cubits, repos, services) + widget (screens, widgets) 104 + - **No golden tests, no integration tests, no visual regression testing** 105 + 106 + ### Widgetbook 107 + 108 + Component catalog + visual testing platform for Flutter (v3.22.0 stable, 109 + v4.0.0-beta.3). 110 + 111 + | Capability | Detail | 112 + | ----------------- | --------------------------------------------------------- | 113 + | Component catalog | Render widgets in isolation with configurable knobs | 114 + | Visual regression | `widgetbook_golden_test` generates goldens from use cases | 115 + | Widgetbook Cloud | CI visual diffs, platform-independent rendering (paid) | 116 + | Addons | Multi-theme, locale, text scale, device frame testing | 117 + 118 + **Verdict: Not recommended for Lazurite at this time.** 119 + 120 + Reasons: 121 + 122 + - Requires building/maintaining a separate Widgetbook app with use-case 123 + definitions for every widget — high overhead for a small team 124 + - The project's gap is golden tests and integration tests, not a design catalog 125 + - No dedicated designer reviewing components, so collaborative review value is 126 + unrealized 127 + - Converting 122 existing widget tests into Widgetbook use cases is low ROI 128 + 129 + **When to reconsider**: If a shared design system emerges, a designer joins, or 130 + PR visual review becomes a bottleneck. 131 + 132 + ### Recommended Approach 133 + 134 + **Golden Toolkit** (`golden_toolkit` package) — add visual regression to 135 + existing widget tests with minimal overhead: 136 + 137 + - One or two lines per existing test to capture golden snapshots 138 + - No separate app to maintain 139 + - Works with existing `pumpWidget` patterns 140 + - `multiScreenGolden` for multi-device-size snapshots 141 + 142 + **Patrol** — consider later if native feature testing (permissions, deep links) 143 + becomes important. 144 + 145 + **Built-in integration tests** — add end-to-end flow tests using 146 + `integration_test` package for critical paths (compose, auth, navigation). 147 + 148 + ### Platform Rendering Note 149 + 150 + Golden tests produce platform-dependent pixel output (macOS dev vs Linux CI). 151 + Mitigations: 152 + 153 + - Tolerance thresholds in comparisons 154 + - CI-only golden generation with committed baselines 155 + - Or Widgetbook Cloud (if budget allows) for platform-independent rendering
+145
docs/tasks/testing.md
··· 1 + # Testing Milestones 2 + 3 + ## M0 — Shared Utilities Extraction 4 + 5 + - [ ] Create `lib/shared/utils/format_utils.dart` with `formatInitials`, `formatCount`, `formatRelativeTime` 6 + - [ ] Replace `_initials` in: 7 + - `lib/features/feed/presentation/widgets/post_card.dart` 8 + - `lib/features/feed/presentation/widgets/grid_post_card.dart` 9 + - `lib/features/feed/presentation/widgets/post_embed_view.dart` 10 + - `lib/features/notifications/presentation/widgets/notification_list_item.dart` 11 + - `lib/features/notifications/presentation/widgets/grouped_notification_list_item.dart` 12 + - `lib/features/profile/presentation/widgets/suggested_follows_list.dart` 13 + - `lib/features/moderation/presentation/screens/labeler_detail_screen.dart` 14 + - `lib/features/moderation/presentation/screens/moderation_settings_screen.dart` 15 + - `lib/features/search/presentation/search_screen.dart` 16 + - `lib/features/search/presentation/hashtag_screen.dart` 17 + - [ ] Replace `_formatCount` in: 18 + - `lib/features/feed/presentation/widgets/post_action_bar.dart` 19 + - `lib/features/feed/presentation/widgets/post_card_footer.dart` 20 + - `lib/features/starter_packs/presentation/widgets/starter_pack_card.dart` 21 + - `lib/features/starter_packs/presentation/starter_pack_detail_screen.dart` 22 + - `lib/features/profile/presentation/profile_screen.dart` 23 + - [ ] Consolidate `_formatTime` with existing `formatPostTime` in `post_card_footer.dart`: 24 + - `lib/features/notifications/presentation/widgets/notification_list_item.dart` 25 + - `lib/features/notifications/presentation/widgets/grouped_notification_list_item.dart` 26 + - `lib/features/search/presentation/search_screen.dart` 27 + - `lib/features/search/presentation/hashtag_screen.dart` 28 + - [ ] Unit tests for all format functions (edge cases: empty string, zero, negative, boundary values) 29 + 30 + ## M1 — Shared State Widgets 31 + 32 + - [ ] Create `lib/shared/presentation/widgets/loading_state.dart` 33 + - [ ] Create `lib/shared/presentation/widgets/error_state.dart` — `ErrorState(message, onRetry)` 34 + - [ ] Create `lib/shared/presentation/widgets/empty_state.dart` — `EmptyState(message, icon, action)` 35 + - [ ] Replace loading/error/empty states in: 36 + - `lib/features/feed/presentation/home_feed_screen.dart` 37 + - `lib/features/feed/presentation/feed_management_screen.dart` 38 + - `lib/features/feed/presentation/saved_posts_screen.dart` 39 + - `lib/features/lists/presentation/my_lists_screen.dart` 40 + - `lib/features/profile/presentation/widgets/suggested_follows_list.dart` 41 + - `lib/features/logs/presentation/logs_screen.dart` 42 + - `lib/features/messages/presentation/messages_screen.dart` 43 + - `lib/features/notifications/presentation/notifications_screen.dart` 44 + - [ ] Widget tests for each state widget 45 + 46 + ## M2 — Dialog & Sheet Consolidation 47 + 48 + - [ ] Create `lib/shared/presentation/widgets/confirmation_dialog.dart` 49 + - [ ] Create `lib/shared/presentation/widgets/options_sheet.dart` 50 + - [ ] Create `lib/shared/presentation/helpers/snackbar_helper.dart` — `showAppSnackBar` 51 + - [ ] Replace confirmation dialogs in: 52 + - `lib/features/profile/presentation/widgets/profile_action_buttons.dart` (5 dialogs) 53 + - `lib/features/compose/presentation/compose_screen.dart` (6 dialogs) 54 + - `lib/features/search/presentation/search_screen.dart` (2 dialogs) 55 + - `lib/features/search/presentation/hashtag_screen.dart` 56 + - `lib/features/feed/presentation/post_thread_screen.dart` 57 + - `lib/features/feed/presentation/feed_management_screen.dart` 58 + - `lib/features/lists/presentation/list_detail_screen.dart` 59 + - `lib/features/settings/presentation/settings_screen.dart` 60 + - `lib/features/moderation/presentation/screens/moderation_settings_screen.dart` 61 + - `lib/features/account/presentation/account_switcher_sheet.dart` 62 + - [ ] Replace modal bottom sheets in: 63 + - `lib/features/feed/presentation/widgets/post_action_bar.dart` 64 + - `lib/features/feed/presentation/widgets/post_card_footer.dart` 65 + - `lib/features/feed/presentation/post_thread_screen.dart` 66 + - `lib/features/search/presentation/hashtag_screen.dart` 67 + - `lib/features/profile/presentation/profile_screen.dart` 68 + - [ ] Replace SnackBar patterns in: 69 + - `lib/features/feed/presentation/widgets/post_card_with_actions.dart` 70 + - `lib/features/feed/presentation/feed_management_screen.dart` 71 + - `lib/features/compose/presentation/compose_screen.dart` 72 + - `lib/features/profile/presentation/profile_screen.dart` 73 + - `lib/features/lists/presentation/list_detail_screen.dart` 74 + - `lib/features/settings/presentation/settings_screen.dart` 75 + - [ ] Tests for dialog/sheet/snackbar helpers 76 + 77 + ## M3 — Theme & Spacing Constants 78 + 79 + - [ ] Create `lib/core/theme/theme_extensions.dart` — `BuildContext` extension for `colorScheme` access 80 + - [ ] Create `lib/core/theme/spacing.dart` with padding/margin constants 81 + - [ ] Create `lib/core/theme/color_filters.dart` — extract greyscale matrix from: 82 + - `lib/features/feed/presentation/widgets/grid_post_card.dart` 83 + - `lib/features/profile/presentation/profile_screen.dart` 84 + - [ ] Refactor files to use new constants/extensions (27 files use `Theme.of(context).colorScheme`) 85 + - [ ] Tests for theme extension 86 + 87 + ## M4 — Widget Extraction 88 + 89 + - [ ] Create `lib/shared/presentation/widgets/profile_avatar.dart` (configurable size, shape, fallback) 90 + - [ ] Create `lib/shared/presentation/widgets/actor_name_widget.dart` (displayName + handle) 91 + - [ ] Create `lib/shared/presentation/helpers/notification_icon_mapper.dart` 92 + - [ ] Replace avatar patterns in: 93 + - `lib/features/messages/presentation/widgets/convo_list_item.dart` 94 + - `lib/features/lists/presentation/widgets/list_row_tile.dart` 95 + - `lib/features/settings/presentation/settings_screen.dart` 96 + - `lib/features/starter_packs/presentation/widgets/starter_pack_card.dart` 97 + - `lib/features/starter_packs/presentation/create_edit_starter_pack_screen.dart` 98 + - `lib/features/account/presentation/account_switcher_sheet.dart` 99 + - `lib/features/profile/presentation/widgets/suggested_follows_list.dart` 100 + - `lib/features/feed/presentation/widgets/post_card.dart` 101 + - `lib/features/feed/presentation/widgets/grid_post_card.dart` 102 + - `lib/features/feed/presentation/widgets/post_embed_view.dart` 103 + - `lib/features/notifications/presentation/widgets/notification_list_item.dart` 104 + - `lib/features/notifications/presentation/widgets/grouped_notification_list_item.dart` 105 + - `lib/features/search/presentation/search_screen.dart` 106 + - `lib/features/search/presentation/hashtag_screen.dart` 107 + - [ ] Replace author name patterns in: 108 + - `lib/features/feed/presentation/widgets/post_card.dart` 109 + - `lib/features/feed/presentation/widgets/grid_post_card.dart` 110 + - `lib/features/feed/presentation/widgets/post_embed_view.dart` 111 + - `lib/features/messages/presentation/widgets/convo_list_item.dart` 112 + - [ ] Replace notification icon switch in: 113 + - `lib/features/notifications/presentation/widgets/notification_list_item.dart` 114 + - `lib/features/notifications/presentation/widgets/grouped_notification_list_item.dart` 115 + - [ ] Widget tests for extracted widgets 116 + 117 + ## M5 — Navigation & Haptics Helpers 118 + 119 + - [ ] Create `lib/shared/presentation/helpers/navigation_helpers.dart` (`navigateToProfile`, `navigateToPost`) 120 + - [ ] Create `lib/shared/presentation/helpers/haptic_helper.dart` 121 + - [ ] Replace navigation patterns in: 122 + - `lib/features/feed/presentation/widgets/post_card.dart` 123 + - `lib/features/feed/presentation/widgets/grid_post_card.dart` 124 + - `lib/features/notifications/presentation/widgets/grouped_notification_list_item.dart` 125 + - [ ] Replace haptic feedback call sites (17 occurrences across 6 files) 126 + - [ ] Tests for navigation helpers 127 + 128 + ## M6 — Golden Testing Setup 129 + 130 + - [ ] Add `golden_toolkit` to dev_dependencies 131 + - [ ] Configure golden test threshold for CI tolerance 132 + - [ ] Add golden tests for shared widgets (M1-M4 extractions) 133 + - [ ] Add golden tests for post card variants (linear, grid) 134 + - [ ] Add golden tests for profile screen states 135 + - [ ] Add multi-device-size goldens for key screens 136 + - [ ] CI pipeline step for golden test comparison 137 + - [ ] Document golden update workflow (`flutter test --update-goldens`) 138 + 139 + ## M7 — Integration Tests 140 + 141 + - [ ] Add `integration_test` to dev_dependencies 142 + - [ ] Auth flow end-to-end test 143 + - [ ] Compose + post flow test 144 + - [ ] Navigation flow test (tab switching, drawer, profile) 145 + - [ ] CI pipeline step for integration tests
+12
ios/Runner.xcodeproj/project.pbxproj
··· 487 487 buildSettings = { 488 488 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 489 489 CLANG_ENABLE_MODULES = YES; 490 + CODE_SIGN_IDENTITY = "Apple Development"; 491 + CODE_SIGN_STYLE = Automatic; 490 492 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 493 + DEVELOPMENT_TEAM = 8TVR4TPL9Y; 491 494 ENABLE_BITCODE = NO; 492 495 EXCLUDED_ARCHS = ""; 493 496 INFOPLIST_FILE = Runner/Info.plist; ··· 497 500 ); 498 501 PRODUCT_BUNDLE_IDENTIFIER = org.stormlightlabs.lazurite; 499 502 PRODUCT_NAME = "$(TARGET_NAME)"; 503 + PROVISIONING_PROFILE_SPECIFIER = ""; 500 504 SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 501 505 SWIFT_VERSION = 5.0; 502 506 VERSIONING_SYSTEM = "apple-generic"; ··· 670 674 buildSettings = { 671 675 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 672 676 CLANG_ENABLE_MODULES = YES; 677 + CODE_SIGN_IDENTITY = "Apple Development"; 678 + CODE_SIGN_STYLE = Automatic; 673 679 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 680 + DEVELOPMENT_TEAM = 8TVR4TPL9Y; 674 681 ENABLE_BITCODE = NO; 675 682 EXCLUDED_ARCHS = ""; 676 683 INFOPLIST_FILE = Runner/Info.plist; ··· 680 687 ); 681 688 PRODUCT_BUNDLE_IDENTIFIER = org.stormlightlabs.lazurite; 682 689 PRODUCT_NAME = "$(TARGET_NAME)"; 690 + PROVISIONING_PROFILE_SPECIFIER = ""; 683 691 SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 684 692 SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 685 693 SWIFT_VERSION = 5.0; ··· 693 701 buildSettings = { 694 702 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 695 703 CLANG_ENABLE_MODULES = YES; 704 + CODE_SIGN_IDENTITY = "Apple Development"; 705 + CODE_SIGN_STYLE = Automatic; 696 706 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 707 + DEVELOPMENT_TEAM = 8TVR4TPL9Y; 697 708 ENABLE_BITCODE = NO; 698 709 EXCLUDED_ARCHS = ""; 699 710 INFOPLIST_FILE = Runner/Info.plist; ··· 703 714 ); 704 715 PRODUCT_BUNDLE_IDENTIFIER = org.stormlightlabs.lazurite; 705 716 PRODUCT_NAME = "$(TARGET_NAME)"; 717 + PROVISIONING_PROFILE_SPECIFIER = ""; 706 718 SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 707 719 SWIFT_VERSION = 5.0; 708 720 VERSIONING_SYSTEM = "apple-generic";