Documentation Index
Fetch the complete documentation index at: https://grantmaster.dev/llms.txt
Use this file to discover all available pages before exploring further.
Testability Refactor - Multi-Component Implementation
Overview
This document tracks the testability refactoring of the 5 largest components in the GrantMaster codebase, applying a proven pattern to reduce complexity, improve testability, and enable maintainability.Status Summary
| Component | Original LOC | Status | New LOC | Reduction | Testable Units |
|---|---|---|---|---|---|
| ✅ GrantApplicationPage | 910 | COMPLETE | 137 | 85% | 20+ |
| ✅ Expenses.tsx | 1,263 | COMPLETE | 349 | 72% | 10+ |
| ✅ Projects.tsx | 1,411 | COMPLETE | 299 | 79% | 32+ |
| ✅ WorkflowPage.tsx | 2,528 | WIRED | 1,989 | 21% | 50+ |
| ✅ EventBusPage.tsx | 1,491 | WIRED | 503 | 66% | 42+ |
| ⏳ ExtensionsPage.tsx | 1,800 | PENDING | - | - | - |
| ✅ geminiService.ts | 752 | COMPLETE | 506 | 33% | 31+ |
| ⏳ AuditorReviewPanelPage.tsx | 1,500 | PENDING | - | - | - |
| ⏳ DocumentBrainContext.tsx | 1,400 | PENDING | - | - | - |
| ⏳ Billing.tsx | 1,400 | PENDING | - | - | - |
| ⏳ Grantors.tsx | 1,400 | PENDING | - | - | - |
| ⏳ Users.tsx | 1,134 | PENDING | - | - | - |
| ⏳ ContractorsDashboard.tsx | 1,183 | PENDING | - | - | - |
| ⏳ GrantTrackingPage.tsx | 943 | PENDING | - | - | - |
| ⏳ notificationService.ts | 1,100 | PENDING | - | - | - |
| ⏳ Webhooks.tsx | 1,150 | PENDING | - | - | - |
| TOTAL | ~24,000 | ~20% DONE | TBD | TBD | 185+ |
The Pattern: 7-Phase Refactoring
This proven pattern has been successfully applied to 2 components (GrantApplicationPage, Expenses.tsx) with excellent results.Phase 1: Extract Types & Constants (1 day)
Goal: Create type-safe foundation with Zod validation Files to create:Phase 2: Extract Business Logic Hooks (3-4 days)
Goal: Separate business logic into testable hooks with DI Hooks to create (typical):use[Feature]Filters- Pure filtering logicuse[Feature]State- State managementuse[Feature]Actions- CRUD operations with DIuse[Feature]Validation- Validation logic (if complex)
Phase 3: Verify Service DI Support (1 day)
Goal: Ensure existing services support dependency injection Check that services accept injectable dependencies in constructor:Phase 4: Extract Presentational Components (4-5 days)
Goal: Create pure UI components with no hooks Components to extract (typical 5-8):[Feature]StatsCard- Stats display[Feature]FiltersBar- Search/filter UI[Feature]Table- Data table[Feature]Card- Card view item[Feature]DetailView- Detail modal/panel- Plus 3-5 more specific to feature
Phase 5: Create Container Components (2-3 days)
Goal: Orchestrate hooks and pass data to pure components Optional: Can skip if orchestrating directly in page componentPhase 6: Refactor Page Component (1-2 days)
Goal: Reduce to ~200 lines of orchestration Structure:Phase 7: Add Tests & Documentation (2-3 days)
Files to create:Component-Specific Guidance
Projects.tsx (1,411 lines) - COMPLETE
- Before: 1,411 lines, 0 testable units, all logic inline (form state, budget CRUD, team CRUD, modal state, routing)
- After: 299 page lines + ~1,200 extracted hook/component lines, 32 tests passing
- Reduction: 1,112 lines removed from page (79%). All business logic now testable in isolation.
- Extracted units:
useProjectForm(245 LOC — form state, budget line CRUD, team member CRUD, submit validation, computed budget values)useProjectModal(147 LOC, DI: IDeadlineService, ISubscriptionLimits — modal state, deadline fetching, subscription limits, delete confirmation)usePortfolioRouting(67 LOC — URL-based portfolio tabs, legacy redirect, tab definitions)ProjectEditModal(250 LOC — modal shell, tab navigation, routes to all 8 tab components)ProjectSettingsTab(419 LOC — funding source, project identity, tier, phase, accounting, budget health, budget lines)ProjectTeamTab(321 LOC — PM selector, team member form, member list with role badges, team stats)
- Tests: 32 passing (15 useProjectForm, 10 useProjectModal, 7 usePortfolioRouting)
- Status: Complete — hooks wired into page, inline logic replaced.
GeminiServiceImpl.ts (752 lines) - COMPLETE
- Before: 752 lines, 0 testable units, all logic inline (prompts, API calls, JSON parsing, usage tracking, cloud function routing)
- After: 506 impl lines + ~550 extracted lines (9 prompt builders, 2 utilities, 3 adapters, 1 DI interface), 31 tests passing
- Reduction: 246 lines removed from impl (33%). All business logic now testable in isolation via DI.
- Key improvements:
- DI interfaces (
IAIClient,ICloudFunctionCaller,IUsageTracker) — no real API calls in tests parseJsonResponseutility — replaced 10x duplicated code-fence strippingcallWithRetryutility — extracted from private method to standalone, fully tested- 9 prompt builders — pure functions, trivially testable (journal, compliance, report, forecast, receipt, eligibility, assistant, proposal, M&E)
- 3 production adapters —
GoogleGenAIAdapter,FirebaseCloudFunctionAdapter,UsageTrackingAdapter
- DI interfaces (
- Tests: 31 passing (8 parseJsonResponse, 6 callWithRetry, 7 journalPrompts, 10 GeminiServiceImpl)
- Status: Complete — DI wired, facade unchanged, 0 consumer changes needed.
Users.tsx (1,134 lines) - PENDING
Key concerns to extract:- User CRUD operations
- Role assignment (RBAC)
- Department management
- Invitation workflow
- Contractor vs employee distinction
useUserFilters(search/role/department filtering)useUserRoles(role assignment logic with DI)useUserInvitations(invite workflow)useUserValidation(email/permission validation)
UserStatsCard(user count by role/department)UserFiltersBar(search/role/department filters)UserTable(user list)UserCard(card view)RoleAssignmentModal(role editor)
ContractorsDashboard.tsx (1,183 lines) - PENDING
Key concerns to extract:- Contractor management
- Payment tracking
- Contract management
- Journal approval
- Performance metrics
useContractorFilters(search/status filtering)useContractorPayments(payment tracking)useContractorContracts(contract lifecycle)useContractorMetrics(performance calculations)
ContractorStatsCard(payments, hours, contracts)ContractorFiltersBar(search/status filters)ContractorTable(contractor list)ContractorCard(card view with quick actions)PaymentHistoryView(payment timeline)
GrantTrackingPage.tsx (943 lines) - PENDING
Key concerns to extract:- Grant pipeline management
- Stage transitions
- Probability scoring
- Opportunity tracking
- Forecast calculations
useGrantFilters(search/stage/probability filtering)useGrantPipeline(stage transitions with DI)useGrantMetrics(forecast calculations)useGrantDragDrop(kanban logic)
GrantStatsCard(pipeline metrics, forecast)GrantFiltersBar(search/stage/probability filters)GrantKanbanBoard(pipeline board)GrantCard(opportunity card)GrantForecastChart(revenue forecast)
Execution Plan
Week 1-3: Projects.tsx ✅ COMPLETE
- ✅ Phase 1-7: All extraction, wiring, and testing complete
- 1,411 → 299 lines (79% reduction), 32 tests
Week 4-6: Users.tsx
- Apply 7-phase pattern
- Leverage learnings from Projects
Week 7-8: ContractorsDashboard.tsx
- Apply 7-phase pattern
- Similar structure to Users
Week 9-10: GrantTrackingPage.tsx
- Apply 7-phase pattern
- Reuse kanban patterns from Projects
Quick Start Checklist (Per Component)
Use this checklist when starting a new refactor:Benefits Realized (Completed Components)
GrantApplicationPage
- Before: 910 lines, 0 testable units
- After: 137 lines, 20+ testable units
- Result: 85% reduction, full DI support, comprehensive tests
Expenses.tsx
- Before: 1,263 lines, 0 testable units
- After: 349 lines, 10+ testable units
- Result: 72% reduction, 4 business logic hooks, 3 pure components
WorkflowPage.tsx (Logic Extracted + Wired)
- Before: 2,528 lines, 0 testable units, all logic inline in 5 inner arrow functions
- After: 1,989 page lines + 1,117 extracted hook/component lines, 50 tests passing
- Reduction: 539 lines removed from page (21%). All business logic now testable in isolation.
- Extracted units:
useTaskData(54 LOC, DI: IDeadlineService)useTaskFilters(30 LOC, pure memo — search + status normalization)useTaskStats(73 LOC, pure memo — stats, focus items, weekly balance)useNotificationFilters(172 LOC, pure memo — category, search, sort, counts + 5 helpers)useMilestoneAnalysis(196 LOC, pure memo — risk score, impact, distribution, timeline)useGoogleCalendarSync(141 LOC, DI: IGoogleCalendarService)RejectionModal(66 LOC),SnoozeModal(59 LOC) — pure presentationalworkflowServiceAdapters.ts(39 LOC),workflowDefaults.ts(86 LOC),workflow.types.ts(150 LOC)workflowFactory.ts(75 LOC) — mock builders
- Status: Complete — hooks wired into page, inline logic replaced, dead imports removed.
- Next step: Extract 5 inner tab components into their own files for further page LOC reduction.
EventBusPage.tsx (Logic Extracted + Wired)
- Before: 1,491 lines, 0 testable units, all logic inline (fetching, filtering, metrics, utils, mock data, modal)
- After: 503 page lines + ~900 extracted feature lines, 42 tests passing
- Reduction: 988 lines removed from page (66%). All business logic now testable in isolation.
- Extracted units:
useEventFetcher(59 LOC, DI: IEventBusDataSource)useEventFilters(44 LOC, pure memo — org/type/severity/date/search filtering)useEventMetrics(46 LOC, pure memo — severityCounts, topEventTypes, activeOrgCount, distinctTypeCount)useAutoRefresh(22 LOC, timer management)usePayloadExplorer(42 LOC — redact/copy/search state)SeverityPill(22 LOC),EventRow(98 LOC, React.memo),EventDetailModal(176 LOC) — pure presentationalfirestoreEventSource.ts(28 LOC) — DI adaptereventbusDefaults.ts(219 LOC),eventbusMockData.ts(258 LOC),eventbusUtils.ts(194 LOC)eventbus.types.ts(56 LOC) — types + DI interfaceeventbusFactory.ts(52 LOC) — test mock builders
- Status: Complete — hooks wired into page, inline logic replaced, 42 tests passing.
Combined Impact
- Total LOC removed: 4,033 lines + 3,550+ lines of logic extracted to testable hooks/modules
- Testable units created: 185+
- Test coverage: 0% → 70%+ potential
- Maintainability: Dramatically improved
- Onboarding time: Estimated 50% reduction
Key Learnings
What Works Well
- ✅ Dependency Injection interfaces - Makes hooks fully testable
- ✅ Pure calculation hooks - Easy to test, no mocking needed
- ✅ Props-only components - Simplifies component testing
- ✅ Zod schemas - Catches data issues early
- ✅ Test factories - Speeds up test writing
Common Pitfalls
- ⚠️ Skipping DI - Makes hooks hard to test
- ⚠️ Over-extracting - Don’t create hooks for trivial logic
- ⚠️ Under-testing - Write tests as you extract, not after
- ⚠️ Breaking changes - Keep original interface intact
- ⚠️ No migration path - Always create
.new.tsxfirst
Best Practices
- 💡 Start with filters - Easy win, builds confidence
- 💡 DI from the start - Don’t retrofit later
- 💡 Test as you go - Write tests immediately after extraction
- 💡 Document patterns - Update this file with learnings
- 💡 Visual regression - Take screenshots before/after
References
- GrantApplicationPage refactor:
src/features/grants/application/README.md - Expenses refactor:
src/features/expenses/README.md - BaseService pattern:
docs/engineering/architecture/base-service-and-eventbus.md - Testing guide:
docs/engineering/testing/testing-strategy.md