@umituz/react-native-auth 4.3.51 → 4.3.53

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-auth",
3
- "version": "4.3.51",
3
+ "version": "4.3.53",
4
4
  "description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design with dependency injection, configurable validation, and comprehensive error handling.",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -75,6 +75,7 @@
75
75
  "@typescript-eslint/eslint-plugin": "^7.0.0",
76
76
  "@typescript-eslint/parser": "^7.0.0",
77
77
  "@umituz/react-native-design-system": "*",
78
+ "@umituz/react-native-firebase": "*",
78
79
  "@umituz/react-native-settings": "*",
79
80
  "eslint": "^8.57.0",
80
81
  "expo-apple-authentication": "^6.0.0",
package/README.md DELETED
@@ -1,425 +0,0 @@
1
- # @umituz/react-native-auth
2
-
3
- Authentication service for React Native applications with secure, type-safe, and production-ready implementation.
4
-
5
- ---
6
-
7
- ## Strategy
8
-
9
- **Purpose**: Provides comprehensive authentication solution for React Native apps with Domain-Driven Design architecture, supporting multiple authentication methods and providers.
10
-
11
- **When to Use**:
12
- - Building React Native apps requiring authentication
13
- - Need multiple auth methods (email, social, anonymous)
14
- - Want type-safe auth implementation
15
- - Prefer DDD architecture
16
- - Need production-ready auth solution
17
-
18
- **Package Location**: `/src`
19
-
20
- **Documentation**: See `/src/[layer]/README.md` for detailed documentation
21
-
22
- ---
23
-
24
- ## Core Features
25
-
26
- ### Authentication Methods
27
-
28
- **SUPPORTED METHODS**:
29
- - Email/Password authentication
30
- - Google OAuth integration
31
- - Apple Sign-In (iOS)
32
- - Anonymous user sessions
33
- - Account upgrade (anonymous → registered)
34
-
35
- ### Architecture
36
-
37
- **DOMAIN-DRIVEN DESIGN LAYERS**:
38
- - **Domain**: Core business logic and entities
39
- - **Application**: Use cases and interfaces
40
- - **Infrastructure**: External integrations
41
- - **Presentation**: UI components and hooks
42
-
43
- ---
44
-
45
- ## Installation
46
-
47
- ### Package Installation
48
-
49
- **NPM**:
50
- ```bash
51
- npm install @umituz/react-native-auth
52
- ```
53
-
54
- **Yarn**:
55
- ```bash
56
- yarn add @umituz/react-native-auth
57
- ```
58
-
59
- ### Peer Dependencies
60
-
61
- **REQUIRED PACKAGES**:
62
- - `firebase`: >= 11.0.0
63
- - `react`: >= 18.2.0
64
- - `react-native`: >= 0.74.0
65
- - `@tanstack/react-query`: >= 5.0.0
66
- - `zustand`: >= 4.0.0
67
-
68
- **EXTERNAL DEPENDENCIES**:
69
- - `@umituz/react-native-firebase` - Firebase integration
70
- - `@umituz/react-native-design-system` - UI components
71
-
72
- ---
73
-
74
- ## Configuration
75
-
76
- ### Firebase Setup
77
-
78
- **Rules**:
79
- - MUST create Firebase project
80
- - MUST enable Authentication
81
- - MUST enable Firestore (for user documents)
82
- - MUST configure OAuth providers
83
-
84
- **MUST NOT**:
85
- - Skip Firebase console setup
86
- - Use production keys in development
87
- - Forget to enable required providers
88
-
89
- **Steps**:
90
- 1. Create Firebase project at console.firebase.google.com
91
- 2. Enable Authentication
92
- 3. Enable Google Sign-In
93
- 4. Enable Apple Sign-In (for iOS)
94
- 5. Enable Firestore
95
- 6. Download config files
96
-
97
- ---
98
-
99
- ## Layer Overview
100
-
101
- ### Domain Layer
102
-
103
- **Location**: `src/domain/`
104
-
105
- **Purpose**: Core business logic and entities
106
-
107
- **CONTAINS**:
108
- - `AuthUser` entity
109
- - `UserProfile` entity
110
- - `AuthConfig` value object
111
- - `AuthError` hierarchy
112
-
113
- **Documentation**: `src/domain/README.md`
114
-
115
- ---
116
-
117
- ### Application Layer
118
-
119
- **Location**: `src/application/`
120
-
121
- **Purpose**: Use cases and interfaces
122
-
123
- **CONTAINS**:
124
- - Authentication ports
125
- - User profile ports
126
- - Account management ports
127
-
128
- **Documentation**: `src/application/README.md`
129
-
130
- ---
131
-
132
- ### Infrastructure Layer
133
-
134
- **Location**: `src/infrastructure/`
135
-
136
- **Purpose**: External integrations and implementations
137
-
138
- **CONTAINS**:
139
- - Firebase Auth service
140
- - Firestore repositories
141
- - Validation utilities
142
- - Provider implementations
143
-
144
- **Documentation**:
145
- - `src/infrastructure/README.md`
146
- - `src/infrastructure/services/README.md`
147
-
148
- ---
149
-
150
- ### Presentation Layer
151
-
152
- **Location**: `src/presentation/`
153
-
154
- **Purpose**: UI components and hooks
155
-
156
- **CONTAINS**:
157
- - React hooks for auth
158
- - Pre-built components
159
- - Screen components
160
- - State management (Zustand)
161
-
162
- **Documentation**:
163
- - `src/presentation/README.md`
164
- - `src/presentation/hooks/README.md`
165
- - `src/presentation/components/README.md`
166
- - `src/presentation/screens/README.md`
167
-
168
- ---
169
-
170
- ## Usage Guidelines
171
-
172
- ### Authentication Hooks
173
-
174
- **PRIMARY HOOK**: `useAuth`
175
- **Location**: `src/presentation/hooks/useAuth.ts`
176
-
177
- **When to Use**:
178
- - Need authentication state
179
- - Require user information
180
- - Performing auth operations
181
- - Checking auth status
182
-
183
- **Import Path**:
184
- ```typescript
185
- import { useAuth } from '@umituz/react-native-auth';
186
- ```
187
-
188
- **Rules**:
189
- - MUST initialize AuthProvider before use
190
- - MUST handle loading state
191
- - MUST check auth readiness
192
- - MUST handle errors appropriately
193
-
194
- ---
195
-
196
- ### Components
197
-
198
- **AVAILABLE COMPONENTS**:
199
- - `LoginForm` - Email/password login
200
- - `RegisterForm` - User registration
201
- - `SocialLoginButtons` - Google/Apple buttons
202
- - `ProfileSection` - Profile display
203
- - `AccountActions` - Account management
204
-
205
- **Import Path**:
206
- ```typescript
207
- import {
208
- LoginForm,
209
- RegisterForm,
210
- SocialLoginButtons
211
- } from '@umituz/react-native-auth';
212
- ```
213
-
214
- **Rules**:
215
- - MUST follow component documentation
216
- - MUST provide required props
217
- - MUST handle events appropriately
218
- - MUST NOT override internal logic
219
-
220
- ---
221
-
222
- ## Platform Support
223
-
224
- ### Supported Platforms
225
-
226
- **iOS**: ✅ Full support
227
- - All authentication methods
228
- - Apple Sign-In available
229
- - Google Sign-In available
230
-
231
- **Android**: ✅ Full support
232
- - All authentication methods (except Apple)
233
- - Google Sign-In available
234
-
235
- **Web**: ✅ Full support
236
- - All authentication methods (except Apple)
237
- - Google Sign-In available
238
-
239
- ---
240
-
241
- ## Security Requirements
242
-
243
- ### Rules
244
-
245
- **MUST**:
246
- - Validate all inputs
247
- - Use HTTPS for all operations
248
- - Implement proper error handling
249
- - Follow Firebase security best practices
250
- - Use secure token storage
251
- - Validate tokens server-side
252
-
253
- **MUST NOT**:
254
- - Store tokens in AsyncStorage
255
- - Log passwords or tokens
256
- - Expose sensitive data in errors
257
- - Skip validation
258
- - Use HTTP for auth operations
259
-
260
- ---
261
-
262
- ## Architecture Principles
263
-
264
- ### Domain-Driven Design
265
-
266
- **PRINCIPLES**:
267
- - Business logic in domain layer
268
- - Infrastructure concerns isolated
269
- - Presentation layer UI-focused
270
- - Application layer orchestrates
271
-
272
- **BENEFITS**:
273
- - Testable business logic
274
- - Swappable providers
275
- - Clear separation of concerns
276
- - Maintainable codebase
277
-
278
- ---
279
-
280
- ## Error Handling
281
-
282
- ### Strategy
283
-
284
- **Purpose**: Comprehensive error handling throughout application.
285
-
286
- **Rules**:
287
- - MUST handle auth errors gracefully
288
- - MUST show user-friendly messages
289
- - MUST allow retry after failures
290
- - MUST log errors for debugging
291
- - MUST not expose sensitive data
292
-
293
- **Error Hierarchy**:
294
- - `AuthError` - Base error class
295
- - `ValidationError` - Input validation errors
296
- - `AuthenticationError` - Auth operation errors
297
- - `NetworkError` - Network issues
298
-
299
- ---
300
-
301
- ## Validation Strategy
302
-
303
- ### Purpose
304
-
305
- **Purpose**: Ensure data integrity and security.
306
-
307
- **Rules**:
308
- - MUST validate email format
309
- - MUST validate password complexity
310
- - MUST validate required fields
311
- - MUST provide clear error messages
312
- - MUST prevent invalid submissions
313
-
314
- **Validation Location**: `src/infrastructure/utils/AuthValidation.ts`
315
-
316
- ---
317
-
318
- ## Migration Guide
319
-
320
- ### From Previous Versions
321
-
322
- **Breaking Changes**:
323
- - See changelog for details
324
- - Follow migration steps
325
- - Update component props
326
- - Update hook usage
327
-
328
- **Rules**:
329
- - MUST read migration guide
330
- - MUST test thoroughly after upgrade
331
- - MUST update dependencies
332
- - MUST check deprecated features
333
-
334
- ---
335
-
336
- ## Performance Considerations
337
-
338
- ### Optimization
339
-
340
- **Rules**:
341
- - MUST memoize expensive computations
342
- - MUST minimize re-renders
343
- - MUST optimize state updates
344
- - MUST use efficient selectors
345
-
346
- **Constraints**:
347
- - Auth state single source of truth
348
- - Minimal network requests
349
- - Efficient validation checks
350
- - Optimized component rendering
351
-
352
- ---
353
-
354
- ## Testing Strategy
355
-
356
- ### Unit Testing
357
-
358
- **WHAT TO TEST**:
359
- - Domain logic and entities
360
- - Validation utilities
361
- - Hook behavior
362
- - Component rendering
363
-
364
- **RULES**:
365
- - MUST test auth operations
366
- - MUST test validation
367
- - MUST test error handling
368
- - MUST mock Firebase dependencies
369
-
370
- ---
371
-
372
- ## Contributing
373
-
374
- ### Development Setup
375
-
376
- **RULES**:
377
- - MUST follow DDD principles
378
- - MUST maintain type safety
379
- - MUST update documentation
380
- - MUST add tests for new features
381
- - MUST follow existing patterns
382
-
383
- **MUST NOT**:
384
- - Break DDD layer boundaries
385
- - Skip documentation
386
- - Add code without tests
387
- - Introduce breaking changes without major version bump
388
-
389
- ---
390
-
391
- ## License
392
-
393
- MIT License - See LICENSE file for details
394
-
395
- ---
396
-
397
- ## Support and Documentation
398
-
399
- ### Documentation Structure
400
-
401
- **MAIN README**: This file
402
-
403
- **LAYER DOCUMENTATION**:
404
- - `src/domain/README.md` - Domain layer details
405
- - `src/application/README.md` - Application layer details
406
- - `src/infrastructure/README.md` - Infrastructure details
407
- - `src/presentation/README.md` - Presentation layer details
408
-
409
- **COMPONENT/HOOK DOCUMENTATION**:
410
- - Each component has dedicated .md file
411
- - Each hook has dedicated .md file
412
- - Follows Strategy/Rules/Constraints format
413
-
414
- ### Getting Help
415
-
416
- - Read documentation first
417
- - Check existing issues
418
- - Review examples in docs
419
- - Follow AI agent guidelines
420
-
421
- ---
422
-
423
- ## Changelog
424
-
425
- See CHANGELOG.md for version history and changes.
@@ -1,53 +0,0 @@
1
- import { AnonymousModeService } from '../../infrastructure/services/AnonymousModeService';
2
- import type { IStorageProvider } from '../../infrastructure/services/AuthPackage';
3
-
4
- describe('AnonymousModeService', () => {
5
- let anonymousModeService: AnonymousModeService;
6
- let mockStorageProvider: jest.Mocked<IStorageProvider>;
7
-
8
- beforeEach(() => {
9
- mockStorageProvider = { get: jest.fn(), set: jest.fn(), remove: jest.fn() };
10
- anonymousModeService = new AnonymousModeService('@test_anonymous_mode');
11
- });
12
-
13
- describe('configuration', () => {
14
- it('should use storage keys correctly', () => {
15
- const service = new AnonymousModeService('@custom');
16
- expect(service.getIsAnonymousMode()).toBe(false);
17
- });
18
- });
19
-
20
- describe('persistence', () => {
21
- it('should load state from storage', async () => {
22
- mockStorageProvider.get.mockResolvedValue('true');
23
- const result = await anonymousModeService.load(mockStorageProvider);
24
- expect(result).toBe(true);
25
- expect(anonymousModeService.getIsAnonymousMode()).toBe(true);
26
- });
27
-
28
- it('should handle missing storage state', async () => {
29
- mockStorageProvider.get.mockResolvedValue(null);
30
- expect(await anonymousModeService.load(mockStorageProvider)).toBe(false);
31
- });
32
-
33
- it('should save state to storage', async () => {
34
- anonymousModeService.setAnonymousMode(true);
35
- await anonymousModeService.save(mockStorageProvider);
36
- expect(mockStorageProvider.set).toHaveBeenCalledWith('@test_anonymous_mode', 'true');
37
- });
38
-
39
- it('should clear state from storage', async () => {
40
- await anonymousModeService.clear(mockStorageProvider);
41
- expect(anonymousModeService.getIsAnonymousMode()).toBe(false);
42
- expect(mockStorageProvider.remove).toHaveBeenCalledWith('@test_anonymous_mode');
43
- });
44
- });
45
-
46
- describe('auth integration', () => {
47
- it('should enable anonymous mode', async () => {
48
- mockStorageProvider.set.mockResolvedValue(undefined);
49
- await anonymousModeService.enable(mockStorageProvider);
50
- expect(anonymousModeService.getIsAnonymousMode()).toBe(true);
51
- });
52
- });
53
- });
@@ -1,45 +0,0 @@
1
- import { AuthCoreService } from '../../../src/infrastructure/services/AuthCoreService';
2
- import { DEFAULT_AUTH_CONFIG } from '../../../src/domain/value-objects/AuthConfig';
3
- import type { IAuthProvider } from '../../../src/application/ports/IAuthProvider';
4
-
5
- describe('AuthCoreInitialization', () => {
6
- let authCoreService: AuthCoreService;
7
- let mockProvider: jest.Mocked<IAuthProvider>;
8
-
9
- beforeEach(() => {
10
- mockProvider = {
11
- initialize: jest.fn(),
12
- isInitialized: jest.fn().mockReturnValue(true),
13
- signIn: jest.fn(),
14
- signUp: jest.fn(),
15
- signOut: jest.fn(),
16
- getCurrentUser: jest.fn(),
17
- onAuthStateChange: jest.fn().mockReturnValue(jest.fn()),
18
- };
19
- authCoreService = new AuthCoreService(DEFAULT_AUTH_CONFIG);
20
- });
21
-
22
- it('should initialize with provided config', () => {
23
- const customConfig = { ...DEFAULT_AUTH_CONFIG, password: { ...DEFAULT_AUTH_CONFIG.password, minLength: 12 } };
24
- const service = new AuthCoreService(customConfig);
25
- expect(service.getConfig()).toEqual(customConfig);
26
- });
27
-
28
- it('should initialize with IAuthProvider', async () => {
29
- await authCoreService.initialize(mockProvider);
30
- expect(mockProvider.initialize).toHaveBeenCalled();
31
- });
32
-
33
- it('should throw error when no provider provided', async () => {
34
- await expect(authCoreService.initialize(null as any)).rejects.toThrow();
35
- });
36
-
37
- it('should return false when not initialized', () => {
38
- expect(authCoreService.isInitialized()).toBe(false);
39
- });
40
-
41
- it('should return true when initialized', async () => {
42
- await authCoreService.initialize(mockProvider);
43
- expect(authCoreService.isInitialized()).toBe(true);
44
- });
45
- });
@@ -1,71 +0,0 @@
1
- import { AuthCoreService } from '../../../src/infrastructure/services/AuthCoreService';
2
- import { DEFAULT_AUTH_CONFIG } from '../../../src/domain/value-objects/AuthConfig';
3
- import type { IAuthProvider } from '../../../src/application/ports/IAuthProvider';
4
- import type { AuthUser } from '../../../src/domain/entities/AuthUser';
5
-
6
- describe('AuthCoreOperations', () => {
7
- let authCoreService: AuthCoreService;
8
- let mockProvider: jest.Mocked<IAuthProvider>;
9
- const mockUser: AuthUser = {
10
- uid: 'test-uid',
11
- email: 'test@example.com',
12
- displayName: 'Test User',
13
- isAnonymous: false,
14
- emailVerified: false,
15
- photoURL: null,
16
- };
17
-
18
- beforeEach(async () => {
19
- mockProvider = {
20
- initialize: jest.fn(),
21
- isInitialized: jest.fn().mockReturnValue(true),
22
- signIn: jest.fn(),
23
- signUp: jest.fn(),
24
- signOut: jest.fn(),
25
- getCurrentUser: jest.fn(),
26
- onAuthStateChange: jest.fn().mockReturnValue(jest.fn()),
27
- };
28
- authCoreService = new AuthCoreService(DEFAULT_AUTH_CONFIG);
29
- await authCoreService.initialize(mockProvider);
30
- });
31
-
32
- describe('Auth Flow', () => {
33
- it('should sign up successfully', async () => {
34
- mockProvider.signUp.mockResolvedValue(mockUser);
35
- const result = await authCoreService.signUp({ email: 'test@example.com', password: 'password123' });
36
- expect(result).toEqual(mockUser);
37
- });
38
-
39
- it('should sign in successfully', async () => {
40
- mockProvider.signIn.mockResolvedValue(mockUser);
41
- const result = await authCoreService.signIn({ email: 'test@example.com', password: 'password123' });
42
- expect(result).toEqual(mockUser);
43
- });
44
-
45
- it('should sign out successfully', async () => {
46
- await authCoreService.signOut();
47
- expect(mockProvider.signOut).toHaveBeenCalled();
48
- });
49
- });
50
-
51
- describe('User State', () => {
52
- it('should return current user', () => {
53
- mockProvider.getCurrentUser.mockReturnValue(mockUser);
54
- expect(authCoreService.getCurrentUser()).toEqual(mockUser);
55
- });
56
-
57
- it('should return null when no user', () => {
58
- mockProvider.getCurrentUser.mockReturnValue(null);
59
- expect(authCoreService.getCurrentUser()).toBeNull();
60
- });
61
-
62
- it('should subscribe to auth state changes', () => {
63
- const callback = jest.fn();
64
- const mockCleanup = jest.fn();
65
- mockProvider.onAuthStateChange.mockReturnValue(mockCleanup);
66
- const cleanup = authCoreService.onAuthStateChange(callback);
67
- expect(mockProvider.onAuthStateChange).toHaveBeenCalledWith(callback);
68
- expect(cleanup).toBe(mockCleanup);
69
- });
70
- });
71
- });
@@ -1,44 +0,0 @@
1
- import { validateDisplayName } from '../../../src/infrastructure/utils/AuthValidation';
2
-
3
- describe('AuthDisplayNameValidation', () => {
4
- describe('validateDisplayName', () => {
5
- it('should reject empty name', () => {
6
- const result = validateDisplayName('');
7
- expect(result.isValid).toBe(false);
8
- expect(result.error).toBe('Name is required');
9
- });
10
-
11
- it('should reject whitespace-only name', () => {
12
- const result = validateDisplayName(' ');
13
- expect(result.isValid).toBe(false);
14
- expect(result.error).toBe('Name is required');
15
- });
16
-
17
- it('should reject name that is too short', () => {
18
- const result = validateDisplayName('A');
19
- expect(result.isValid).toBe(false);
20
- expect(result.error).toBe('Name must be at least 2 characters');
21
- });
22
-
23
- it('should accept name that meets minimum length', () => {
24
- const result = validateDisplayName('Al');
25
- expect(result.isValid).toBe(true);
26
- });
27
-
28
- it('should use custom minimum length', () => {
29
- const result = validateDisplayName('Al', 3);
30
- expect(result.isValid).toBe(false);
31
- expect(result.error).toBe('Name must be at least 3 characters');
32
- });
33
-
34
- it('should trim whitespace', () => {
35
- const result = validateDisplayName(' John Doe ');
36
- expect(result.isValid).toBe(true);
37
- });
38
-
39
- it('should accept name with special characters', () => {
40
- const result = validateDisplayName('John-O\'Connor');
41
- expect(result.isValid).toBe(true);
42
- });
43
- });
44
- });
@@ -1,38 +0,0 @@
1
- import { validateEmail } from '../../../src/infrastructure/utils/AuthValidation';
2
-
3
- describe('AuthEmailValidation', () => {
4
- describe('validateEmail', () => {
5
- it('should reject empty email', () => {
6
- const result = validateEmail('');
7
- expect(result.isValid).toBe(false);
8
- expect(result.error).toBe('Email is required');
9
- });
10
-
11
- it('should reject whitespace-only email', () => {
12
- const result = validateEmail(' ');
13
- expect(result.isValid).toBe(false);
14
- expect(result.error).toBe('Email is required');
15
- });
16
-
17
- it('should reject invalid email format', () => {
18
- const result = validateEmail('invalid-email');
19
- expect(result.isValid).toBe(false);
20
- expect(result.error).toBe('Please enter a valid email address');
21
- });
22
-
23
- it('should accept valid email format', () => {
24
- const result = validateEmail('test@example.com');
25
- expect(result.isValid).toBe(true);
26
- });
27
-
28
- it('should accept valid email with subdomain', () => {
29
- const result = validateEmail('test@mail.example.com');
30
- expect(result.isValid).toBe(true);
31
- });
32
-
33
- it('should trim whitespace', () => {
34
- const result = validateEmail(' test@example.com ');
35
- expect(result.isValid).toBe(true);
36
- });
37
- });
38
- });
@@ -1,59 +0,0 @@
1
- import {
2
- validatePasswordForLogin,
3
- validatePasswordForRegister,
4
- validatePasswordConfirmation,
5
- } from '../../../src/infrastructure/utils/AuthValidation';
6
- import { DEFAULT_PASSWORD_CONFIG } from '../../../src/domain/value-objects/AuthConfig';
7
-
8
- describe('AuthPasswordValidation', () => {
9
- describe('validatePasswordForLogin', () => {
10
- it('should reject empty password', () => {
11
- const result = validatePasswordForLogin('');
12
- expect(result.isValid).toBe(false);
13
- expect(result.error).toBe('Password is required');
14
- });
15
-
16
- it('should accept any non-empty password', () => {
17
- const result = validatePasswordForLogin('any');
18
- expect(result.isValid).toBe(true);
19
- });
20
- });
21
-
22
- describe('validatePasswordForRegister', () => {
23
- const config = DEFAULT_PASSWORD_CONFIG;
24
-
25
- it('should reject empty password', () => {
26
- const result = validatePasswordForRegister('', config);
27
- expect(result.isValid).toBe(false);
28
- expect(result.requirements.hasMinLength).toBe(false);
29
- });
30
-
31
- it('should reject password that is too short', () => {
32
- const result = validatePasswordForRegister('123', config);
33
- expect(result.isValid).toBe(false);
34
- expect(result.requirements.hasMinLength).toBe(false);
35
- });
36
-
37
- it('should accept password that meets length requirement', () => {
38
- const result = validatePasswordForRegister('Password1!', config);
39
- expect(result.isValid).toBe(true);
40
- });
41
- });
42
-
43
- describe('validatePasswordConfirmation', () => {
44
- it('should reject empty confirmation', () => {
45
- const result = validatePasswordConfirmation('password', '');
46
- expect(result.isValid).toBe(false);
47
- });
48
-
49
- it('should reject mismatched passwords', () => {
50
- const result = validatePasswordConfirmation('password', 'different');
51
- expect(result.isValid).toBe(false);
52
- });
53
-
54
- it('should accept matching passwords', () => {
55
- const result = validatePasswordConfirmation('password', 'password');
56
- expect(result.isValid).toBe(true);
57
- });
58
- });
59
- });