@jmruthers/pace-core 0.5.87 → 0.5.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AuthService-Df3IozMG.d.ts → AuthService-DcTI5Ov4.d.ts} +9 -0
- package/dist/{DataTable-FA6EUX5M.js → DataTable-PWBMKMOG.js} +7 -7
- package/dist/{PublicLoadingSpinner-DecuJBX0.d.ts → PublicLoadingSpinner-BQXD1fbO.d.ts} +160 -130
- package/dist/{UnifiedAuthProvider-K2IZAY5F.js → UnifiedAuthProvider-5D3HEQND.js} +4 -4
- package/dist/{UnifiedAuthProvider-B391Aqum.d.ts → UnifiedAuthProvider-BVKmQd9u.d.ts} +4 -0
- package/dist/auth-DReDSLq9.d.ts +16 -0
- package/dist/{chunk-CBSD3BZ3.js → chunk-3RZBKQ5Y.js} +2 -6
- package/dist/{chunk-CBSD3BZ3.js.map → chunk-3RZBKQ5Y.js.map} +1 -1
- package/dist/{chunk-NTW3KGS4.js → chunk-6UHXQH7P.js} +5 -5
- package/dist/{chunk-YVUZWLQG.js → chunk-AQGF5OG7.js} +3 -3
- package/dist/{chunk-CVMVPYAL.js → chunk-BDZUMRBD.js} +3 -5
- package/dist/chunk-BDZUMRBD.js.map +1 -0
- package/dist/{chunk-KAY3K5TP.js → chunk-BNXBJOGL.js} +4 -4
- package/dist/{chunk-I7O3RSMN.js → chunk-CJIZS3UE.js} +1298 -769
- package/dist/chunk-CJIZS3UE.js.map +1 -0
- package/dist/{chunk-S3JKDMD5.js → chunk-CXKMRKRF.js} +4 -4
- package/dist/{chunk-5BN3YGNK.js → chunk-DP5X5ORK.js} +217 -27
- package/dist/chunk-DP5X5ORK.js.map +1 -0
- package/dist/{chunk-ZFLOV3OM.js → chunk-H3P2RGKZ.js} +352 -16
- package/dist/chunk-H3P2RGKZ.js.map +1 -0
- package/dist/{chunk-RIXPZJUB.js → chunk-KTPG5VCH.js} +2 -2
- package/dist/{chunk-WUXCWRL6.js → chunk-XJ2HZOBU.js} +6 -1
- package/dist/chunk-XJ2HZOBU.js.map +1 -0
- package/dist/{chunk-2FQEQUJT.js → chunk-XXVM53P4.js} +4 -4
- package/dist/{chunk-I2VVV5PQ.js → chunk-YY4YYM3E.js} +2 -2
- package/dist/components.d.ts +6 -55
- package/dist/components.js +24 -205
- package/dist/components.js.map +1 -1
- package/dist/{file-reference-9xUOnwyt.d.ts → file-reference-C9isKNPn.d.ts} +67 -2
- package/dist/hooks.js +9 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +152 -26
- package/dist/index.js +64 -194
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +5 -3
- package/dist/providers.js +3 -3
- package/dist/rbac/index.js +8 -8
- package/dist/types.d.ts +2 -1
- package/dist/types.js +3 -3
- package/dist/utils.js +2 -2
- package/docs/DOCUMENTATION_AUDIT.md +6 -6
- package/docs/DOCUMENTATION_STANDARD.md +137 -0
- package/docs/README.md +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +83 -40
- package/docs/api/enums/FileCategory.md +56 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +11 -11
- package/docs/api/interfaces/FileDisplayProps.md +10 -10
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +8 -8
- package/docs/api/interfaces/FileUploadProps.md +137 -42
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +83 -50
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
- package/docs/api/interfaces/UseEventLogoOptions.md +74 -0
- package/docs/api/interfaces/UseEventLogoReturn.md +81 -0
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +6 -6
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +290 -95
- package/docs/api-reference/components.md +1 -18
- package/docs/api-reference/hooks.md +1 -4
- package/docs/best-practices/testing.md +2 -0
- package/docs/documentation-index.md +1 -1
- package/docs/getting-started/faq.md +1 -1
- package/docs/implementation-guides/file-reference-system.md +592 -58
- package/docs/implementation-guides/file-upload-storage.md +137 -73
- package/docs/rbac/super-admin-guide.md +18 -70
- package/docs/testing/README.md +2 -0
- package/package.json +1 -1
- package/src/__tests__/TEST_STANDARD.md +674 -0
- package/src/__tests__/helpers/test-utils.tsx +3 -2
- package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx.skip → DataTable.comprehensive.test.tsx} +17 -18
- package/src/components/DataTable/__tests__/{DataTable.test.tsx.skip → DataTable.test.tsx} +14 -22
- package/src/components/DataTable/__tests__/{ssr.strict-mode.test.tsx.skip → ssr.strict-mode.test.tsx} +42 -47
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +1 -1
- package/src/components/DataTable/examples/__tests__/PerformanceExample.test.tsx +13 -4
- package/src/components/DataTable/utils/__tests__/COVERAGE_NOTE.md +1 -1
- package/src/components/DataTable/utils/__tests__/performanceUtils.test.ts +10 -6
- package/src/components/FileDisplay/FileDisplay.test.tsx +257 -0
- package/src/components/{FileDisplay.tsx → FileDisplay/FileDisplay.tsx} +111 -10
- package/src/components/FileDisplay/index.tsx +4 -0
- package/src/components/FileUpload/FileUpload.test.tsx +171 -621
- package/src/components/FileUpload/FileUpload.tsx +512 -168
- package/src/components/FileUpload/index.tsx +4 -0
- package/src/components/Progress/Progress.test.tsx +38 -0
- package/src/components/PublicLayout/EventLogo.tsx +6 -4
- package/src/components/Select/Select.test.tsx +1 -1
- package/src/components/SessionRestorationLoader.tsx +48 -0
- package/src/components/Toast/Toast.tsx +13 -8
- package/src/components/index.ts +16 -16
- package/src/hooks/__tests__/ServiceHooks.test.tsx +615 -0
- package/src/hooks/public/usePublicEventLogo.ts +16 -20
- package/src/hooks/useEventLogo.ts +316 -0
- package/src/hooks/useEvents.ts +0 -5
- package/src/hooks/useFileReference.test.ts +659 -0
- package/src/hooks/useFileReference.ts +207 -3
- package/src/hooks/useSessionRestoration.ts +64 -0
- package/src/index.ts +17 -5
- package/src/providers/{UnifiedAuthProvider.test.simple.tsx → UnifiedAuthProvider.smoke.test.tsx} +81 -60
- package/src/providers/services/AuthServiceProvider.tsx +27 -3
- package/src/providers/services/UnifiedAuthProvider.tsx +34 -5
- package/src/rbac/{engine.test.simple.ts → RBACEngine.smoke.test.ts} +17 -12
- package/src/services/AuthService.ts +142 -20
- package/src/services/EventService.ts +0 -4
- package/src/types/auth.ts +15 -0
- package/src/types/file-reference.ts +73 -1
- package/src/types/index.ts +1 -0
- package/src/utils/__tests__/organisationContext.unit.test.ts +2 -4
- package/src/utils/appNameResolver.simple.test.ts +99 -29
- package/src/utils/file-reference.test.ts +535 -0
- package/src/utils/file-reference.ts +200 -30
- package/src/utils/organisationContext.test.ts +5 -19
- package/src/utils/organisationContext.ts +3 -5
- package/src/utils/storage/README.md +269 -262
- package/src/utils/storage/config.ts +9 -0
- package/src/utils/storage/helpers.test.ts +631 -0
- package/src/utils/storage/helpers.ts +112 -14
- package/src/utils/storage/index.ts +3 -0
- package/src/validation/__tests__/sanitization.unit.test.ts +1 -1
- package/src/validation/__tests__/schemaUtils.unit.test.ts +1 -1
- package/src/validation/__tests__/user.unit.test.ts +1 -1
- package/dist/chunk-5BN3YGNK.js.map +0 -1
- package/dist/chunk-CVMVPYAL.js.map +0 -1
- package/dist/chunk-I7O3RSMN.js.map +0 -1
- package/dist/chunk-WUXCWRL6.js.map +0 -1
- package/dist/chunk-ZFLOV3OM.js.map +0 -1
- package/docs/CONTENT_AUDIT_REPORT.md +0 -253
- package/docs/STYLE_GUIDE.md +0 -37
- package/examples/RBAC/__tests__/PermissionExample.test.tsx +0 -150
- package/examples/public-pages/__tests__/PublicPageUsageExample.test.tsx +0 -159
- package/src/__tests__/TEST_GUIDE_CURSOR.md +0 -1605
- package/src/__tests__/TEST_GUIDE_HUMAN.md +0 -103
- package/src/components/FileUpload/FileUpload.example.tsx +0 -218
- package/src/components/FileUpload/index.ts +0 -6
- package/src/components/FileUpload.tsx +0 -176
- package/src/components/Progress/index.ts +0 -3
- package/src/components/PublicLayout/__tests__/EventLogo.test.tsx +0 -666
- package/src/components/SuperAdminGuard.tsx +0 -116
- package/src/components/__tests__/FileDisplay.test.tsx +0 -575
- package/src/components/__tests__/FileUpload.test.tsx +0 -446
- package/src/components/__tests__/SuperAdminGuard.test.tsx +0 -627
- package/src/components/examples/PermissionExample.tsx +0 -173
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +0 -583
- package/src/hooks/__tests__/usePublicEventLogo.unit.test.ts +0 -640
- package/src/types/__tests__/file-reference.test.ts +0 -447
- package/src/utils/__tests__/file-reference.test.ts +0 -383
- /package/dist/{DataTable-FA6EUX5M.js.map → DataTable-PWBMKMOG.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-K2IZAY5F.js.map → UnifiedAuthProvider-5D3HEQND.js.map} +0 -0
- /package/dist/{chunk-NTW3KGS4.js.map → chunk-6UHXQH7P.js.map} +0 -0
- /package/dist/{chunk-YVUZWLQG.js.map → chunk-AQGF5OG7.js.map} +0 -0
- /package/dist/{chunk-KAY3K5TP.js.map → chunk-BNXBJOGL.js.map} +0 -0
- /package/dist/{chunk-S3JKDMD5.js.map → chunk-CXKMRKRF.js.map} +0 -0
- /package/dist/{chunk-RIXPZJUB.js.map → chunk-KTPG5VCH.js.map} +0 -0
- /package/dist/{chunk-2FQEQUJT.js.map → chunk-XXVM53P4.js.map} +0 -0
- /package/dist/{chunk-I2VVV5PQ.js.map → chunk-YY4YYM3E.js.map} +0 -0
- /package/src/providers/{OrganisationProvider.test.simple.tsx → OrganisationProvider.context.test.tsx} +0 -0
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
# Testing Standard
|
|
2
|
+
|
|
3
|
+
The authoritative guide for writing world-class tests in the pace-core project. Every test contribution should follow these standards to ensure reliability, maintainability, and comprehensive coverage.
|
|
4
|
+
|
|
5
|
+
## 🎯 Testing Philosophy
|
|
6
|
+
|
|
7
|
+
### Core Principles
|
|
8
|
+
|
|
9
|
+
1. **Test Behavior, Not Implementation** - Focus on what users see and do, not internal code structure
|
|
10
|
+
2. **Fast & Reliable** - Tests should run quickly and produce consistent results
|
|
11
|
+
3. **Independent & Isolated** - Tests should not depend on each other or external state
|
|
12
|
+
4. **Self-Documenting** - Tests should clearly express intent and expected behavior
|
|
13
|
+
5. **Maintainable** - Tests should be easy to update when requirements change
|
|
14
|
+
|
|
15
|
+
### The Testing Pyramid
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
/\
|
|
19
|
+
/ \ E2E Tests (Few)
|
|
20
|
+
/____\
|
|
21
|
+
/ \ Integration Tests (Some)
|
|
22
|
+
/________\
|
|
23
|
+
Unit Tests (Many)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Distribution Guidelines:**
|
|
27
|
+
- **Unit Tests (80%)**: Fast, isolated tests for individual functions/components
|
|
28
|
+
- **Integration Tests (15%)**: Test component interactions and data flow
|
|
29
|
+
- **E2E Tests (5%)**: Full user journey validation
|
|
30
|
+
|
|
31
|
+
## 🏗️ Test Organization
|
|
32
|
+
|
|
33
|
+
### File Structure
|
|
34
|
+
|
|
35
|
+
**✅ Preferred: Colocated Tests**
|
|
36
|
+
```
|
|
37
|
+
src/
|
|
38
|
+
├── components/
|
|
39
|
+
│ ├── Button/
|
|
40
|
+
│ │ ├── Button.tsx
|
|
41
|
+
│ │ └── Button.test.tsx ✅ Next to source
|
|
42
|
+
│ └── DataTable/
|
|
43
|
+
│ ├── DataTable.tsx
|
|
44
|
+
│ ├── DataTable.test.tsx ✅ Main component tests
|
|
45
|
+
│ └── __tests__/ ✅ For multiple test files
|
|
46
|
+
│ └── DataTable.integration.test.tsx
|
|
47
|
+
├── hooks/
|
|
48
|
+
│ ├── useCounter.ts
|
|
49
|
+
│ └── useCounter.test.ts ✅ Colocated
|
|
50
|
+
└── services/
|
|
51
|
+
├── AuthService.ts
|
|
52
|
+
└── AuthService.test.ts ✅ Colocated
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**When to Use `__tests__/` Directory:**
|
|
56
|
+
- Multiple test files for one source file (unit + integration)
|
|
57
|
+
- Shared test utilities within a module
|
|
58
|
+
- Integration tests spanning multiple components
|
|
59
|
+
|
|
60
|
+
**Central `src/__tests__/` Directory - ONLY for:**
|
|
61
|
+
- Cross-module integration tests
|
|
62
|
+
- Shared test utilities and fixtures
|
|
63
|
+
- Test configuration files
|
|
64
|
+
|
|
65
|
+
### File Naming Conventions
|
|
66
|
+
|
|
67
|
+
- `ComponentName.test.tsx` - Component tests
|
|
68
|
+
- `hookName.test.ts` - Hook tests
|
|
69
|
+
- `serviceName.test.ts` - Service tests
|
|
70
|
+
- `feature.integration.test.tsx` - Integration tests
|
|
71
|
+
- `utils.unit.test.ts` - Utility function tests
|
|
72
|
+
|
|
73
|
+
## 📝 Test Structure & Naming
|
|
74
|
+
|
|
75
|
+
### Describe Block Organization
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
describe('ComponentName', () => {
|
|
79
|
+
describe('Rendering', () => {
|
|
80
|
+
it('renders with default props', () => {});
|
|
81
|
+
it('renders with custom content', () => {});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('User Interactions', () => {
|
|
85
|
+
it('handles click events', () => {});
|
|
86
|
+
it('handles keyboard navigation', () => {});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('State Management', () => {
|
|
90
|
+
it('updates state on user input', () => {});
|
|
91
|
+
it('resets state on form submission', () => {});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('Error Handling', () => {
|
|
95
|
+
it('displays error message on validation failure', () => {});
|
|
96
|
+
it('recovers from network errors', () => {});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Test Naming Best Practices
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// ✅ Good - Descriptive and behavior-focused
|
|
105
|
+
it('renders submit button when form is valid', () => {});
|
|
106
|
+
it('disables submit button when required fields are empty', () => {});
|
|
107
|
+
it('shows loading spinner during form submission', () => {});
|
|
108
|
+
|
|
109
|
+
// ❌ Bad - Vague or implementation-focused
|
|
110
|
+
it('works correctly', () => {});
|
|
111
|
+
it('test button component', () => {});
|
|
112
|
+
it('should call onClick prop', () => {});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 🧪 Testing Patterns
|
|
116
|
+
|
|
117
|
+
### 1. Component Testing (AAA Pattern)
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
describe('Button Component', () => {
|
|
121
|
+
it('handles click events with custom handler', async () => {
|
|
122
|
+
// Arrange
|
|
123
|
+
const handleClick = vi.fn();
|
|
124
|
+
const user = userEvent.setup();
|
|
125
|
+
|
|
126
|
+
render(<Button onClick={handleClick}>Click me</Button>);
|
|
127
|
+
|
|
128
|
+
// Act
|
|
129
|
+
await user.click(screen.getByRole('button', { name: 'Click me' }));
|
|
130
|
+
|
|
131
|
+
// Assert
|
|
132
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 2. Hook Testing
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
describe('useCounter Hook', () => {
|
|
141
|
+
it('initializes with provided value', () => {
|
|
142
|
+
const { result } = renderHook(() => useCounter(5));
|
|
143
|
+
|
|
144
|
+
expect(result.current.count).toBe(5);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('increments count when increment is called', () => {
|
|
148
|
+
const { result } = renderHook(() => useCounter(0));
|
|
149
|
+
|
|
150
|
+
act(() => {
|
|
151
|
+
result.current.increment();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
expect(result.current.count).toBe(1);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 3. Service Testing
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
describe('AuthService', () => {
|
|
163
|
+
let mockSupabase: ReturnType<typeof createMockSupabaseClient>;
|
|
164
|
+
let authService: AuthService;
|
|
165
|
+
|
|
166
|
+
beforeEach(() => {
|
|
167
|
+
mockSupabase = createMockSupabaseClient();
|
|
168
|
+
authService = new AuthService(mockSupabase);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('signs in user with valid credentials', async () => {
|
|
172
|
+
const mockUser = { id: '123', email: 'user@example.com' };
|
|
173
|
+
|
|
174
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
175
|
+
data: { user: mockUser, session: {} },
|
|
176
|
+
error: null
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const result = await authService.signIn({
|
|
180
|
+
email: 'user@example.com',
|
|
181
|
+
password: 'password'
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(result.user).toEqual(mockUser);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### 4. Integration Testing
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
describe('User Registration Flow', () => {
|
|
193
|
+
it('completes full registration workflow', async () => {
|
|
194
|
+
const user = userEvent.setup();
|
|
195
|
+
|
|
196
|
+
render(<RegistrationForm />);
|
|
197
|
+
|
|
198
|
+
// Fill form
|
|
199
|
+
await user.type(screen.getByLabelText('Email'), 'user@example.com');
|
|
200
|
+
await user.type(screen.getByLabelText('Password'), 'password123');
|
|
201
|
+
|
|
202
|
+
// Submit
|
|
203
|
+
await user.click(screen.getByRole('button', { name: 'Register' }));
|
|
204
|
+
|
|
205
|
+
// Verify success
|
|
206
|
+
expect(await screen.findByText('Registration successful')).toBeInTheDocument();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## 🔍 Query Best Practices
|
|
212
|
+
|
|
213
|
+
### Semantic Queries (Preferred)
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// ✅ Excellent - Semantic and accessible
|
|
217
|
+
screen.getByRole('button', { name: 'Submit' });
|
|
218
|
+
screen.getByLabelText('Email address');
|
|
219
|
+
screen.getByText('Welcome back');
|
|
220
|
+
screen.getByPlaceholderText('Enter your name');
|
|
221
|
+
|
|
222
|
+
// ✅ Good - Content-based
|
|
223
|
+
screen.getByDisplayValue('current-value');
|
|
224
|
+
screen.getByTitle('Close dialog');
|
|
225
|
+
|
|
226
|
+
// ⚠️ Use sparingly - When semantic queries aren't sufficient
|
|
227
|
+
screen.getByTestId('complex-widget');
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Query Priority Order
|
|
231
|
+
|
|
232
|
+
1. **getByRole** - Most accessible and semantic
|
|
233
|
+
2. **getByLabelText** - Form elements with labels
|
|
234
|
+
3. **getByText** - Visible text content
|
|
235
|
+
4. **getByDisplayValue** - Form elements with values
|
|
236
|
+
5. **getByAltText** - Images with alt text
|
|
237
|
+
6. **getByTitle** - Elements with title attribute
|
|
238
|
+
7. **getByTestId** - Last resort for complex cases
|
|
239
|
+
|
|
240
|
+
### Async Query Patterns
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// ✅ Good - Built-in waiting with findBy
|
|
244
|
+
const button = await screen.findByRole('button', { name: 'Submit' });
|
|
245
|
+
|
|
246
|
+
// ✅ Good - Wait for specific condition
|
|
247
|
+
await waitFor(() => {
|
|
248
|
+
expect(screen.getByText('Data loaded')).toBeInTheDocument();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// ❌ Bad - Waiting for synchronous operations
|
|
252
|
+
await waitFor(() => {
|
|
253
|
+
expect(result.current.count).toBe(0); // This is immediate!
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## 🔧 Mocking & Test Doubles
|
|
258
|
+
|
|
259
|
+
### Mock Strategy
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// ✅ Good - Mock external dependencies only
|
|
263
|
+
vi.mock('../api/users', () => ({
|
|
264
|
+
fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'John' })
|
|
265
|
+
}));
|
|
266
|
+
|
|
267
|
+
// ✅ Good - Mock complex external libraries
|
|
268
|
+
vi.mock('react-router-dom', () => ({
|
|
269
|
+
useNavigate: () => vi.fn(),
|
|
270
|
+
useLocation: () => ({ pathname: '/test' })
|
|
271
|
+
}));
|
|
272
|
+
|
|
273
|
+
// ❌ Bad - Over-mocking internal utilities
|
|
274
|
+
vi.mock('../utils/formatDate'); // Usually not necessary
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Multi-Call Mocking
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// ✅ Good - Handle different calls with mockImplementation
|
|
281
|
+
mockSupabase.rpc.mockImplementation((functionName, params) => {
|
|
282
|
+
if (functionName === 'get_user') {
|
|
283
|
+
return Promise.resolve({ data: { id: '1' }, error: null });
|
|
284
|
+
}
|
|
285
|
+
if (functionName === 'get_permissions') {
|
|
286
|
+
return Promise.resolve({ data: permissions, error: null });
|
|
287
|
+
}
|
|
288
|
+
return Promise.resolve({ data: null, error: null });
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Cleanup Best Practices
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
describe('Component with Resources', () => {
|
|
296
|
+
beforeEach(() => {
|
|
297
|
+
vi.useFakeTimers();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
afterEach(() => {
|
|
301
|
+
// Clean up timers
|
|
302
|
+
vi.clearAllTimers();
|
|
303
|
+
vi.useRealTimers();
|
|
304
|
+
|
|
305
|
+
// React Testing Library cleanup
|
|
306
|
+
cleanup();
|
|
307
|
+
|
|
308
|
+
// Clear all mocks
|
|
309
|
+
vi.clearAllMocks();
|
|
310
|
+
|
|
311
|
+
// Clear storage
|
|
312
|
+
localStorage.clear();
|
|
313
|
+
sessionStorage.clear();
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## 🚫 Anti-Patterns to Avoid
|
|
319
|
+
|
|
320
|
+
### 1. Testing Implementation Details
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
// ❌ Bad - Testing internal state
|
|
324
|
+
expect(component.state.isVisible).toBe(true);
|
|
325
|
+
expect(wrapper.find('.hidden-class')).toHaveLength(0);
|
|
326
|
+
|
|
327
|
+
// ✅ Good - Testing observable behavior
|
|
328
|
+
expect(screen.getByText('Visible content')).toBeInTheDocument();
|
|
329
|
+
expect(screen.queryByText('Hidden content')).not.toBeInTheDocument();
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### 2. Brittle Selectors
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
// ❌ Bad - Fragile and non-semantic
|
|
336
|
+
screen.getByClassName('btn-primary');
|
|
337
|
+
container.querySelector('.form > div:nth-child(2)');
|
|
338
|
+
|
|
339
|
+
// ✅ Good - Semantic and stable
|
|
340
|
+
screen.getByRole('button', { name: 'Submit' });
|
|
341
|
+
screen.getByLabelText('Email address');
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### 3. Testing Multiple Concerns
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
// ❌ Bad - Too many assertions in one test
|
|
348
|
+
it('renders form and validates and submits and shows success', () => {
|
|
349
|
+
// Multiple unrelated assertions
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// ✅ Good - Single responsibility
|
|
353
|
+
it('renders form with all required fields', () => {});
|
|
354
|
+
it('validates email format on blur', () => {});
|
|
355
|
+
it('submits form with valid data', () => {});
|
|
356
|
+
it('shows success message after submission', () => {});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## 📊 Coverage Standards
|
|
360
|
+
|
|
361
|
+
### Coverage Thresholds
|
|
362
|
+
|
|
363
|
+
| Component Type | Target | CI Blocking | Rationale |
|
|
364
|
+
|----------------|--------|-------------|-----------|
|
|
365
|
+
| **Utils/Hooks** | 95% | Yes | Core business logic, high reuse |
|
|
366
|
+
| **UI Components** | 90% | Yes | User-facing, must be reliable |
|
|
367
|
+
| **Services** | 85% | Yes | API interactions, critical paths |
|
|
368
|
+
| **Validation** | 95% | Yes | Security and data integrity |
|
|
369
|
+
| **Overall Project** | 82% | Yes | Quality baseline |
|
|
370
|
+
|
|
371
|
+
### What to Test
|
|
372
|
+
|
|
373
|
+
- ✅ **Happy paths** - Normal user workflows
|
|
374
|
+
- ✅ **Error conditions** - Network failures, validation errors
|
|
375
|
+
- ✅ **Edge cases** - Empty states, boundary values
|
|
376
|
+
- ✅ **User interactions** - Clicks, form submissions, navigation
|
|
377
|
+
- ✅ **State changes** - Loading states, data updates
|
|
378
|
+
- ✅ **Accessibility** - Keyboard navigation, screen reader support
|
|
379
|
+
|
|
380
|
+
### What NOT to Test
|
|
381
|
+
|
|
382
|
+
- ❌ **Third-party libraries** - Already tested by maintainers
|
|
383
|
+
- ❌ **Generated code** - Auto-generated types, configs
|
|
384
|
+
- ❌ **Pure styling** - CSS classes without behavior
|
|
385
|
+
- ❌ **Type definitions** - Interfaces and type-only exports
|
|
386
|
+
|
|
387
|
+
## 🔐 RBAC Testing Patterns
|
|
388
|
+
|
|
389
|
+
### Provider Testing
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
describe('RBACProvider', () => {
|
|
393
|
+
it('requires organisation context when configured', () => {
|
|
394
|
+
const { result } = renderHook(() => useRBAC());
|
|
395
|
+
|
|
396
|
+
expect(() => result.current.requireOrganisationContext()).toThrow(
|
|
397
|
+
'Organisation context is required'
|
|
398
|
+
);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('refreshes permissions when event changes', async () => {
|
|
402
|
+
const { result } = renderHook(() => useRBAC());
|
|
403
|
+
|
|
404
|
+
act(() => {
|
|
405
|
+
result.current.setSelectedEventId('new-event-id');
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
await waitFor(() => {
|
|
409
|
+
expect(result.current.rbacLoading).toBe(false);
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Permission Hook Testing
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
describe('useHasPermission', () => {
|
|
419
|
+
it('returns true when user has required permission', async () => {
|
|
420
|
+
setupRBACMock([
|
|
421
|
+
{ permission_type: 'read:users', role_name: 'viewer' }
|
|
422
|
+
]);
|
|
423
|
+
|
|
424
|
+
const { result } = renderHook(() =>
|
|
425
|
+
useHasPermission('user-123', scope, 'read:users')
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
await waitFor(() => {
|
|
429
|
+
expect(result.current.hasPermission).toBe(true);
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## 🎨 Component-Specific Patterns
|
|
436
|
+
|
|
437
|
+
### DataTable Testing Strategy
|
|
438
|
+
|
|
439
|
+
DataTable subcomponents intentionally have low unit test coverage because they're comprehensively tested through integration tests. This is **correct and intentional**.
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
// ✅ Good - Integration test for DataTable workflows
|
|
443
|
+
describe('DataTable - Complete Workflow', () => {
|
|
444
|
+
it('allows editing, sorting, and filtering data', async () => {
|
|
445
|
+
const user = userEvent.setup();
|
|
446
|
+
|
|
447
|
+
render(<DataTable data={mockData} onSave={mockSave} />);
|
|
448
|
+
|
|
449
|
+
// Test editing
|
|
450
|
+
await user.click(screen.getByRole('button', { name: 'Edit' }));
|
|
451
|
+
await user.type(screen.getByRole('textbox'), 'Updated Value');
|
|
452
|
+
await user.click(screen.getByRole('button', { name: 'Save' }));
|
|
453
|
+
|
|
454
|
+
// Test sorting
|
|
455
|
+
await user.click(screen.getByRole('columnheader', { name: 'Name' }));
|
|
456
|
+
|
|
457
|
+
// Test filtering
|
|
458
|
+
await user.type(screen.getByPlaceholderText('Filter...'), 'search term');
|
|
459
|
+
|
|
460
|
+
expect(mockSave).toHaveBeenCalledWith(expectedData);
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### Service Hook Testing
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
describe('useEventService', () => {
|
|
469
|
+
it('throws error when used outside provider', () => {
|
|
470
|
+
expect(() => {
|
|
471
|
+
renderHook(() => useEventService());
|
|
472
|
+
}).toThrow('useEventService must be used within EventServiceProvider');
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('subscribes to service updates and cleans up', () => {
|
|
476
|
+
const unsubscribe = vi.fn();
|
|
477
|
+
const mockService = {
|
|
478
|
+
subscribe: vi.fn(() => unsubscribe)
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const { unmount } = renderHook(() => useEventService(), {
|
|
482
|
+
wrapper: ({ children }) => (
|
|
483
|
+
<EventServiceContext.Provider value={{ eventService: mockService }}>
|
|
484
|
+
{children}
|
|
485
|
+
</EventServiceContext.Provider>
|
|
486
|
+
)
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
expect(mockService.subscribe).toHaveBeenCalled();
|
|
490
|
+
|
|
491
|
+
unmount();
|
|
492
|
+
expect(unsubscribe).toHaveBeenCalled();
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
## ⚡ Performance & Memory
|
|
498
|
+
|
|
499
|
+
### Memory Leak Prevention
|
|
500
|
+
|
|
501
|
+
```typescript
|
|
502
|
+
describe('Component with Async Operations', () => {
|
|
503
|
+
beforeEach(() => {
|
|
504
|
+
vi.useFakeTimers();
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
afterEach(() => {
|
|
508
|
+
// Prevent memory leaks
|
|
509
|
+
vi.clearAllTimers();
|
|
510
|
+
vi.useRealTimers();
|
|
511
|
+
cleanup();
|
|
512
|
+
vi.clearAllMocks();
|
|
513
|
+
localStorage.clear();
|
|
514
|
+
sessionStorage.clear();
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('handles cleanup properly', async () => {
|
|
518
|
+
// Test implementation
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Async Testing Guidelines
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
// ✅ Good - Appropriate timeouts based on operation speed
|
|
527
|
+
await waitFor(() => {
|
|
528
|
+
expect(result.current.isLoading).toBe(false);
|
|
529
|
+
}, { timeout: 100, interval: 10 }); // Fast operations
|
|
530
|
+
|
|
531
|
+
await waitFor(() => {
|
|
532
|
+
expect(result.current.data).toBeDefined();
|
|
533
|
+
}, { timeout: 5000 }); // Slow operations only
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
## 🔧 Test Configuration
|
|
537
|
+
|
|
538
|
+
### Vitest Configuration
|
|
539
|
+
|
|
540
|
+
```typescript
|
|
541
|
+
// vitest.config.ts
|
|
542
|
+
export default defineConfig({
|
|
543
|
+
test: {
|
|
544
|
+
environment: 'jsdom',
|
|
545
|
+
environmentOptions: {
|
|
546
|
+
jsdom: {
|
|
547
|
+
pretendToBeVisual: true, // Enables getComputedStyle
|
|
548
|
+
resources: 'usable' // Enables resource loading
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
coverage: {
|
|
552
|
+
thresholds: {
|
|
553
|
+
lines: 82,
|
|
554
|
+
branches: 80,
|
|
555
|
+
functions: 80,
|
|
556
|
+
statements: 82
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### Test Utilities Setup
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
// test-utils.tsx
|
|
567
|
+
export function renderWithProviders(
|
|
568
|
+
ui: React.ReactElement,
|
|
569
|
+
options: { organisationId?: string } = {}
|
|
570
|
+
) {
|
|
571
|
+
function Wrapper({ children }: { children: React.ReactNode }) {
|
|
572
|
+
return (
|
|
573
|
+
<OrganisationProvider organisationId={options.organisationId}>
|
|
574
|
+
<RBACProvider>
|
|
575
|
+
{children}
|
|
576
|
+
</RBACProvider>
|
|
577
|
+
</OrganisationProvider>
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return render(ui, { wrapper: Wrapper, ...options });
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
## 🚀 Running Tests
|
|
586
|
+
|
|
587
|
+
### Commands
|
|
588
|
+
|
|
589
|
+
```bash
|
|
590
|
+
# Run all tests
|
|
591
|
+
npm test
|
|
592
|
+
|
|
593
|
+
# Run with coverage
|
|
594
|
+
npm test -- --coverage
|
|
595
|
+
|
|
596
|
+
# Run specific test file
|
|
597
|
+
npm test -- Button.test.tsx
|
|
598
|
+
|
|
599
|
+
# Run tests in watch mode
|
|
600
|
+
npm test -- --watch
|
|
601
|
+
|
|
602
|
+
# Run tests with timeout (prevents hanging)
|
|
603
|
+
timeout 60 npm test
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Debugging
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
// Debug output
|
|
610
|
+
screen.debug();
|
|
611
|
+
|
|
612
|
+
// Debug specific element
|
|
613
|
+
screen.debug(screen.getByRole('button'));
|
|
614
|
+
|
|
615
|
+
// Log available queries
|
|
616
|
+
screen.logTestingPlaygroundURL();
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
## 📋 Pre-Merge Checklist
|
|
620
|
+
|
|
621
|
+
- [ ] All tests pass without `it.only` or `test.skip`
|
|
622
|
+
- [ ] Coverage thresholds are met
|
|
623
|
+
- [ ] Tests use semantic queries (avoid test IDs)
|
|
624
|
+
- [ ] Async operations use proper waiting patterns
|
|
625
|
+
- [ ] Mocks are cleaned up in `afterEach`
|
|
626
|
+
- [ ] Tests focus on behavior, not implementation
|
|
627
|
+
- [ ] Error scenarios are covered
|
|
628
|
+
- [ ] Tests are colocated with source files
|
|
629
|
+
|
|
630
|
+
## 🎯 Test Categories & Tags
|
|
631
|
+
|
|
632
|
+
Use explicit tags for test organization:
|
|
633
|
+
|
|
634
|
+
```typescript
|
|
635
|
+
describe('[unit] useDebounce', () => {
|
|
636
|
+
it('delays function execution', () => {});
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
describe('[component] Button', () => {
|
|
640
|
+
it('renders with correct styling', () => {});
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
describe('[integration] User Registration', () => {
|
|
644
|
+
it('completes full signup flow', () => {});
|
|
645
|
+
});
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
## 📚 Resources
|
|
649
|
+
|
|
650
|
+
### Documentation
|
|
651
|
+
- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/)
|
|
652
|
+
- [Vitest Documentation](https://vitest.dev/)
|
|
653
|
+
- [Jest DOM Matchers](https://github.com/testing-library/jest-dom)
|
|
654
|
+
|
|
655
|
+
### Internal Guides
|
|
656
|
+
- [Documentation Standard](../../docs/DOCUMENTATION_STANDARD.md)
|
|
657
|
+
- [Memory Performance Analysis](../../../MEMORY_PERFORMANCE_ANALYSIS.md)
|
|
658
|
+
- [Test Failure Analysis](../../../TEST_FAILURE_ANALYSIS.md)
|
|
659
|
+
|
|
660
|
+
---
|
|
661
|
+
|
|
662
|
+
## 🎖️ Excellence Indicators
|
|
663
|
+
|
|
664
|
+
A world-class test suite demonstrates:
|
|
665
|
+
|
|
666
|
+
- **Comprehensive Coverage** - All critical paths tested
|
|
667
|
+
- **Behavioral Focus** - Tests what users experience
|
|
668
|
+
- **Maintainable Structure** - Easy to update and extend
|
|
669
|
+
- **Fast Execution** - Quick feedback loops
|
|
670
|
+
- **Clear Intent** - Tests serve as living documentation
|
|
671
|
+
- **Reliable Results** - Consistent across environments
|
|
672
|
+
- **Accessibility Awareness** - Tests include a11y considerations
|
|
673
|
+
|
|
674
|
+
Remember: **Tests are not just about catching bugs—they're about building confidence, enabling refactoring, and documenting expected behavior for future developers.**
|
|
@@ -192,13 +192,14 @@ const createQueryBuilder = () => {
|
|
|
192
192
|
sharedQueryBuilder = {
|
|
193
193
|
select: vi.fn(() => sharedQueryBuilder),
|
|
194
194
|
eq: vi.fn(() => sharedQueryBuilder),
|
|
195
|
+
in: vi.fn(() => Promise.resolve({ data: [], error: null })),
|
|
195
196
|
single: vi.fn(() => Promise.resolve({ data: { id: 'test-app-id' }, error: null })),
|
|
196
197
|
maybeSingle: vi.fn(() => Promise.resolve({ data: null, error: null })),
|
|
197
198
|
order: vi.fn(() => sharedQueryBuilder),
|
|
198
199
|
limit: vi.fn(() => Promise.resolve({ data: [], error: null })),
|
|
199
200
|
insert: vi.fn(() => Promise.resolve({ data: null, error: null })),
|
|
200
|
-
update: vi.fn(() =>
|
|
201
|
-
delete: vi.fn(() =>
|
|
201
|
+
update: vi.fn(() => sharedQueryBuilder),
|
|
202
|
+
delete: vi.fn(() => sharedQueryBuilder),
|
|
202
203
|
};
|
|
203
204
|
}
|
|
204
205
|
return sharedQueryBuilder;
|