@umituz/react-native-auth 1.12.0 → 2.0.1

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.
Files changed (136) hide show
  1. package/README.md +0 -0
  2. package/lib/__tests__/services/AuthCoreService.test.d.ts +4 -0
  3. package/lib/__tests__/services/AuthCoreService.test.js +198 -0
  4. package/lib/__tests__/services/AuthPackage.test.d.ts +4 -0
  5. package/lib/__tests__/services/AuthPackage.test.js +177 -0
  6. package/lib/__tests__/services/GuestModeService.test.d.ts +4 -0
  7. package/lib/__tests__/services/GuestModeService.test.js +141 -0
  8. package/lib/__tests__/utils/AuthValidation.test.d.ts +4 -0
  9. package/lib/__tests__/utils/AuthValidation.test.js +222 -0
  10. package/lib/application/ports/IAuthProvider.d.ts +42 -0
  11. package/lib/application/ports/IAuthProvider.js +5 -0
  12. package/lib/application/ports/IAuthService.d.ts +48 -0
  13. package/lib/application/ports/IAuthService.js +5 -0
  14. package/lib/domain/entities/AuthUser.d.ts +12 -0
  15. package/lib/domain/entities/AuthUser.js +5 -0
  16. package/lib/domain/errors/AuthError.d.ts +36 -0
  17. package/lib/domain/errors/AuthError.js +76 -0
  18. package/lib/domain/value-objects/AuthConfig.d.ts +16 -0
  19. package/lib/domain/value-objects/AuthConfig.js +14 -0
  20. package/lib/index.d.ts +45 -0
  21. package/lib/index.js +59 -0
  22. package/lib/infrastructure/adapters/StorageProviderAdapter.d.ts +16 -0
  23. package/lib/infrastructure/adapters/StorageProviderAdapter.js +72 -0
  24. package/lib/infrastructure/adapters/UIProviderAdapter.d.ts +18 -0
  25. package/lib/infrastructure/adapters/UIProviderAdapter.js +28 -0
  26. package/lib/infrastructure/providers/FirebaseAuthProvider.d.ts +19 -0
  27. package/lib/infrastructure/providers/FirebaseAuthProvider.js +94 -0
  28. package/lib/infrastructure/services/AuthCoreService.d.ts +22 -0
  29. package/lib/infrastructure/services/AuthCoreService.js +102 -0
  30. package/lib/infrastructure/services/AuthEventService.d.ts +28 -0
  31. package/lib/infrastructure/services/AuthEventService.js +88 -0
  32. package/lib/infrastructure/services/AuthPackage.d.ts +62 -0
  33. package/lib/infrastructure/services/AuthPackage.js +91 -0
  34. package/lib/infrastructure/services/AuthService.d.ts +42 -0
  35. package/lib/infrastructure/services/AuthService.js +123 -0
  36. package/lib/infrastructure/services/GuestModeService.d.ts +23 -0
  37. package/lib/infrastructure/services/GuestModeService.js +69 -0
  38. package/lib/infrastructure/storage/GuestModeStorage.d.ts +16 -0
  39. package/lib/infrastructure/storage/GuestModeStorage.js +73 -0
  40. package/lib/infrastructure/utils/AuthErrorMapper.d.ts +8 -0
  41. package/lib/infrastructure/utils/AuthErrorMapper.js +51 -0
  42. package/lib/infrastructure/utils/AuthEventEmitter.d.ts +12 -0
  43. package/lib/infrastructure/utils/AuthEventEmitter.js +25 -0
  44. package/lib/infrastructure/utils/AuthValidation.d.ts +49 -0
  45. package/lib/infrastructure/utils/AuthValidation.js +133 -0
  46. package/lib/infrastructure/utils/UserMapper.d.ts +15 -0
  47. package/lib/infrastructure/utils/UserMapper.js +16 -0
  48. package/lib/presentation/components/AuthContainer.d.ts +10 -0
  49. package/lib/presentation/components/AuthContainer.js +27 -0
  50. package/lib/presentation/components/AuthDivider.d.ts +6 -0
  51. package/lib/presentation/components/AuthDivider.js +36 -0
  52. package/lib/presentation/components/AuthErrorDisplay.d.ts +10 -0
  53. package/lib/presentation/components/AuthErrorDisplay.js +24 -0
  54. package/lib/presentation/components/AuthFormCard.d.ts +10 -0
  55. package/lib/presentation/components/AuthFormCard.js +19 -0
  56. package/lib/presentation/components/AuthGradientBackground.d.ts +6 -0
  57. package/lib/presentation/components/AuthGradientBackground.js +8 -0
  58. package/lib/presentation/components/AuthHeader.d.ts +11 -0
  59. package/lib/presentation/components/AuthHeader.js +38 -0
  60. package/lib/presentation/components/AuthLegalLinks.d.ts +28 -0
  61. package/lib/presentation/components/AuthLegalLinks.js +54 -0
  62. package/lib/presentation/components/AuthLink.d.ts +13 -0
  63. package/lib/presentation/components/AuthLink.js +27 -0
  64. package/lib/presentation/components/LoginForm.d.ts +10 -0
  65. package/lib/presentation/components/LoginForm.js +27 -0
  66. package/lib/presentation/components/PasswordMatchIndicator.d.ts +9 -0
  67. package/lib/presentation/components/PasswordMatchIndicator.js +30 -0
  68. package/lib/presentation/components/PasswordStrengthIndicator.d.ts +11 -0
  69. package/lib/presentation/components/PasswordStrengthIndicator.js +60 -0
  70. package/lib/presentation/components/RegisterForm.d.ts +14 -0
  71. package/lib/presentation/components/RegisterForm.js +30 -0
  72. package/lib/presentation/hooks/useAuth.d.ts +44 -0
  73. package/lib/presentation/hooks/useAuth.js +38 -0
  74. package/lib/presentation/hooks/useAuthActions.d.ts +15 -0
  75. package/lib/presentation/hooks/useAuthActions.js +162 -0
  76. package/lib/presentation/hooks/useAuthState.d.ts +19 -0
  77. package/lib/presentation/hooks/useAuthState.js +79 -0
  78. package/lib/presentation/hooks/useLoginForm.d.ts +21 -0
  79. package/lib/presentation/hooks/useLoginForm.js +131 -0
  80. package/lib/presentation/hooks/useRegisterForm.d.ts +31 -0
  81. package/lib/presentation/hooks/useRegisterForm.js +136 -0
  82. package/lib/presentation/navigation/AuthNavigator.d.ts +28 -0
  83. package/lib/presentation/navigation/AuthNavigator.js +37 -0
  84. package/lib/presentation/screens/LoginScreen.d.ts +6 -0
  85. package/lib/presentation/screens/LoginScreen.js +15 -0
  86. package/lib/presentation/screens/RegisterScreen.d.ts +12 -0
  87. package/lib/presentation/screens/RegisterScreen.js +15 -0
  88. package/lib/presentation/utils/getAuthErrorMessage.d.ts +8 -0
  89. package/lib/presentation/utils/getAuthErrorMessage.js +69 -0
  90. package/package.json +12 -4
  91. package/src/__tests__/services/AuthCoreService.test.ts +247 -0
  92. package/src/__tests__/services/AuthPackage.test.ts +226 -0
  93. package/src/__tests__/services/GuestModeService.test.ts +194 -0
  94. package/src/__tests__/utils/AuthValidation.test.ts +270 -0
  95. package/src/application/ports/IAuthProvider.ts +0 -0
  96. package/src/application/ports/IAuthService.ts +0 -0
  97. package/src/domain/entities/AuthUser.ts +0 -0
  98. package/src/domain/errors/AuthError.ts +0 -0
  99. package/src/domain/value-objects/AuthConfig.ts +0 -0
  100. package/src/index.ts +0 -0
  101. package/src/infrastructure/adapters/StorageProviderAdapter.ts +73 -0
  102. package/src/infrastructure/adapters/UIProviderAdapter.ts +39 -0
  103. package/src/infrastructure/providers/FirebaseAuthProvider.ts +10 -2
  104. package/src/infrastructure/services/AuthCoreService.ts +138 -0
  105. package/src/infrastructure/services/AuthEventService.ts +115 -0
  106. package/src/infrastructure/services/AuthPackage.ts +148 -0
  107. package/src/infrastructure/services/AuthService.ts +62 -128
  108. package/src/infrastructure/services/GuestModeService.ts +86 -0
  109. package/src/infrastructure/storage/GuestModeStorage.ts +40 -14
  110. package/src/infrastructure/utils/AuthErrorMapper.ts +7 -3
  111. package/src/infrastructure/utils/AuthEventEmitter.ts +0 -0
  112. package/src/infrastructure/utils/AuthValidation.ts +47 -17
  113. package/src/infrastructure/utils/UserMapper.ts +0 -0
  114. package/src/presentation/components/AuthContainer.tsx +0 -0
  115. package/src/presentation/components/AuthDivider.tsx +0 -0
  116. package/src/presentation/components/AuthErrorDisplay.tsx +0 -0
  117. package/src/presentation/components/AuthFormCard.tsx +0 -0
  118. package/src/presentation/components/AuthGradientBackground.tsx +0 -0
  119. package/src/presentation/components/AuthHeader.tsx +0 -0
  120. package/src/presentation/components/AuthLegalLinks.tsx +0 -0
  121. package/src/presentation/components/AuthLink.tsx +0 -0
  122. package/src/presentation/components/LoginForm.tsx +0 -0
  123. package/src/presentation/components/PasswordMatchIndicator.tsx +2 -2
  124. package/src/presentation/components/PasswordStrengthIndicator.tsx +0 -0
  125. package/src/presentation/components/RegisterForm.tsx +0 -0
  126. package/src/presentation/hooks/useAuth.ts +0 -0
  127. package/src/presentation/hooks/useAuthActions.ts +8 -11
  128. package/src/presentation/hooks/useAuthState.ts +10 -0
  129. package/src/presentation/hooks/useLoginForm.ts +0 -0
  130. package/src/presentation/hooks/useRegisterForm.ts +16 -17
  131. package/src/presentation/navigation/AuthNavigator.tsx +2 -2
  132. package/src/presentation/screens/LoginScreen.tsx +3 -6
  133. package/src/presentation/screens/RegisterScreen.tsx +3 -6
  134. package/src/presentation/utils/getAuthErrorMessage.ts +0 -0
  135. package/src/types/external.d.ts +68 -0
  136. package/LICENSE +0 -22
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useNavigation } from "@react-navigation/native";
3
+ import { useLocalization } from "@umituz/react-native-localization";
4
+ import { AuthContainer } from "../components/AuthContainer";
5
+ import { AuthHeader } from "../components/AuthHeader";
6
+ import { AuthFormCard } from "../components/AuthFormCard";
7
+ import { RegisterForm } from "../components/RegisterForm";
8
+ export const RegisterScreen = ({ termsUrl, privacyUrl, onTermsPress, onPrivacyPress, }) => {
9
+ const { t } = useLocalization();
10
+ const navigation = useNavigation();
11
+ const handleNavigateToLogin = () => {
12
+ navigation.navigate("Login");
13
+ };
14
+ return (_jsxs(AuthContainer, { children: [_jsx(AuthHeader, { title: t("auth.createAccount") }), _jsx(AuthFormCard, { children: _jsx(RegisterForm, { onNavigateToLogin: handleNavigateToLogin, termsUrl: termsUrl, privacyUrl: privacyUrl, onTermsPress: onTermsPress, onPrivacyPress: onPrivacyPress }) })] }));
15
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Get localized error message from AuthError
3
+ * Maps error codes to localization keys
4
+ */
5
+ /**
6
+ * Map AuthError code to localization key
7
+ */
8
+ export declare function getAuthErrorLocalizationKey(error: Error): string;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Get localized error message from AuthError
3
+ * Maps error codes to localization keys
4
+ */
5
+ import { AuthError } from "../../domain/errors/AuthError";
6
+ /**
7
+ * Map AuthError code to localization key
8
+ */
9
+ export function getAuthErrorLocalizationKey(error) {
10
+ const code = error instanceof AuthError ? error.code : undefined;
11
+ // Map error codes to localization keys
12
+ const errorCodeMap = {
13
+ AUTH_INVALID_EMAIL: "auth.errors.invalidEmail",
14
+ AUTH_WEAK_PASSWORD: "auth.errors.weakPassword",
15
+ AUTH_USER_NOT_FOUND: "auth.errors.userNotFound",
16
+ AUTH_WRONG_PASSWORD: "auth.errors.wrongPassword",
17
+ AUTH_EMAIL_ALREADY_IN_USE: "auth.errors.emailAlreadyInUse",
18
+ AUTH_NETWORK_ERROR: "auth.errors.networkError",
19
+ AUTH_CONFIG_ERROR: "auth.errors.configurationError",
20
+ AUTH_TOO_MANY_REQUESTS: "auth.errors.tooManyRequests",
21
+ AUTH_USER_DISABLED: "auth.errors.userDisabled",
22
+ AUTH_NOT_INITIALIZED: "auth.errors.authNotInitialized",
23
+ };
24
+ // Check error name for specific error types
25
+ if (error.name === "AuthInvalidEmailError") {
26
+ return "auth.errors.invalidEmail";
27
+ }
28
+ if (error.name === "AuthWeakPasswordError") {
29
+ return "auth.errors.weakPassword";
30
+ }
31
+ if (error.name === "AuthUserNotFoundError") {
32
+ return "auth.errors.userNotFound";
33
+ }
34
+ if (error.name === "AuthWrongPasswordError") {
35
+ return "auth.errors.wrongPassword";
36
+ }
37
+ if (error.name === "AuthEmailAlreadyInUseError") {
38
+ return "auth.errors.emailAlreadyInUse";
39
+ }
40
+ if (error.name === "AuthNetworkError") {
41
+ return "auth.errors.networkError";
42
+ }
43
+ if (error.name === "AuthConfigurationError") {
44
+ return "auth.errors.configurationError";
45
+ }
46
+ if (error.name === "AuthInitializationError") {
47
+ return "auth.errors.authNotInitialized";
48
+ }
49
+ // Use code if available
50
+ if (code && errorCodeMap[code]) {
51
+ return errorCodeMap[code];
52
+ }
53
+ // Check error message for specific patterns
54
+ const message = error.message.toLowerCase();
55
+ if (message.includes("too many requests")) {
56
+ return "auth.errors.tooManyRequests";
57
+ }
58
+ if (message.includes("user account has been disabled") || message.includes("user disabled")) {
59
+ return "auth.errors.userDisabled";
60
+ }
61
+ if (message.includes("not properly configured") || message.includes("configuration")) {
62
+ return "auth.errors.configurationError";
63
+ }
64
+ if (message.includes("not enabled") || message.includes("operation not allowed")) {
65
+ return "auth.errors.operationNotAllowed";
66
+ }
67
+ // Default to unknown error
68
+ return "auth.errors.unknownError";
69
+ }
package/package.json CHANGED
@@ -1,12 +1,17 @@
1
1
  {
2
2
  "name": "@umituz/react-native-auth",
3
- "version": "1.12.0",
4
- "description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design supports Firebase Auth and can be adapted for Supabase or other providers.",
5
- "main": "./src/index.ts",
6
- "types": "./src/index.ts",
3
+ "version": "2.0.1",
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
+ "main": "./lib/index.js",
6
+ "types": "./lib/index.d.ts",
7
7
  "scripts": {
8
+ "build": "tsc",
8
9
  "typecheck": "tsc --noEmit",
9
10
  "lint": "tsc --noEmit",
11
+ "prepublishOnly": "npm run build",
12
+ "test": "jest",
13
+ "test:watch": "jest --watch",
14
+ "test:coverage": "jest --coverage",
10
15
  "version:patch": "npm version patch -m 'chore: release v%s'",
11
16
  "version:minor": "npm version minor -m 'chore: release v%s'",
12
17
  "version:major": "npm version major -m 'chore: release v%s'"
@@ -49,6 +54,8 @@
49
54
  "firebase": "^11.10.0",
50
55
  "@types/react": "^18.2.45",
51
56
  "@types/react-native": "^0.73.0",
57
+ "@types/jest": "^29.5.0",
58
+ "jest": "^29.5.0",
52
59
  "react": "^18.2.0",
53
60
  "react-native": "^0.74.0",
54
61
  "typescript": "^5.3.3"
@@ -57,6 +64,7 @@
57
64
  "access": "public"
58
65
  },
59
66
  "files": [
67
+ "lib",
60
68
  "src",
61
69
  "README.md",
62
70
  "LICENSE"
@@ -0,0 +1,247 @@
1
+ /**
2
+ * AuthCoreService Tests
3
+ */
4
+
5
+ import { AuthCoreService } from '../../../src/infrastructure/services/AuthCoreService';
6
+ import { DEFAULT_AUTH_CONFIG } from '../../../src/domain/value-objects/AuthConfig';
7
+ import type { IAuthProvider } from '../../../src/application/ports/IAuthProvider';
8
+ import type { AuthUser } from '../../../src/domain/entities/AuthUser';
9
+
10
+ describe('AuthCoreService', () => {
11
+ let authCoreService: AuthCoreService;
12
+ let mockProvider: jest.Mocked<IAuthProvider>;
13
+
14
+ beforeEach(() => {
15
+ mockProvider = {
16
+ initialize: jest.fn(),
17
+ isInitialized: jest.fn().mockReturnValue(true),
18
+ signIn: jest.fn(),
19
+ signUp: jest.fn(),
20
+ signOut: jest.fn(),
21
+ getCurrentUser: jest.fn(),
22
+ onAuthStateChange: jest.fn().mockReturnValue(jest.fn()),
23
+ };
24
+
25
+ authCoreService = new AuthCoreService(DEFAULT_AUTH_CONFIG);
26
+ });
27
+
28
+ describe('constructor', () => {
29
+ it('should initialize with provided config', () => {
30
+ const customConfig = {
31
+ ...DEFAULT_AUTH_CONFIG,
32
+ password: {
33
+ ...DEFAULT_AUTH_CONFIG.password,
34
+ minLength: 12,
35
+ },
36
+ };
37
+
38
+ const service = new AuthCoreService(customConfig);
39
+ expect(service.getConfig()).toEqual(customConfig);
40
+ });
41
+ });
42
+
43
+ describe('initialize', () => {
44
+ it('should initialize with IAuthProvider', async () => {
45
+ await authCoreService.initialize(mockProvider);
46
+ expect(mockProvider.initialize).toHaveBeenCalled();
47
+ });
48
+
49
+ it('should initialize with Firebase Auth instance', async () => {
50
+ const mockFirebaseAuth = {
51
+ currentUser: null,
52
+ } as any;
53
+
54
+ await expect(authCoreService.initialize(mockFirebaseAuth)).rejects.toThrow();
55
+ });
56
+
57
+ it('should throw error when no provider provided', async () => {
58
+ await expect(authCoreService.initialize(null as any)).rejects.toThrow(
59
+ 'Auth provider or Firebase Auth instance is required'
60
+ );
61
+ });
62
+ });
63
+
64
+ describe('isInitialized', () => {
65
+ it('should return false when not initialized', () => {
66
+ expect(authCoreService.isInitialized()).toBe(false);
67
+ });
68
+
69
+ it('should return true when initialized', async () => {
70
+ await authCoreService.initialize(mockProvider);
71
+ expect(authCoreService.isInitialized()).toBe(true);
72
+ });
73
+ });
74
+
75
+ describe('signUp', () => {
76
+ const mockUser: AuthUser = {
77
+ uid: 'test-uid',
78
+ email: 'test@example.com',
79
+ displayName: 'Test User',
80
+ isAnonymous: false,
81
+ emailVerified: false,
82
+ photoURL: null,
83
+ };
84
+
85
+ beforeEach(async () => {
86
+ await authCoreService.initialize(mockProvider);
87
+ });
88
+
89
+ it('should sign up successfully with valid credentials', async () => {
90
+ mockProvider.signUp.mockResolvedValue(mockUser);
91
+
92
+ const result = await authCoreService.signUp({
93
+ email: 'test@example.com',
94
+ password: 'password123',
95
+ displayName: 'Test User',
96
+ });
97
+
98
+ expect(result).toEqual(mockUser);
99
+ expect(mockProvider.signUp).toHaveBeenCalledWith({
100
+ email: 'test@example.com',
101
+ password: 'password123',
102
+ displayName: 'Test User',
103
+ });
104
+ });
105
+
106
+ it('should sign up without display name', async () => {
107
+ mockProvider.signUp.mockResolvedValue(mockUser);
108
+
109
+ await authCoreService.signUp({
110
+ email: 'test@example.com',
111
+ password: 'password123',
112
+ });
113
+
114
+ expect(mockProvider.signUp).toHaveBeenCalledWith({
115
+ email: 'test@example.com',
116
+ password: 'password123',
117
+ displayName: undefined,
118
+ });
119
+ });
120
+
121
+ it('should throw error when not initialized', async () => {
122
+ const uninitializedService = new AuthCoreService(DEFAULT_AUTH_CONFIG);
123
+
124
+ await expect(uninitializedService.signUp({
125
+ email: 'test@example.com',
126
+ password: 'password123',
127
+ })).rejects.toThrow('Auth service is not initialized');
128
+ });
129
+ });
130
+
131
+ describe('signIn', () => {
132
+ const mockUser: AuthUser = {
133
+ uid: 'test-uid',
134
+ email: 'test@example.com',
135
+ displayName: 'Test User',
136
+ isAnonymous: false,
137
+ emailVerified: false,
138
+ photoURL: null,
139
+ };
140
+
141
+ beforeEach(async () => {
142
+ await authCoreService.initialize(mockProvider);
143
+ });
144
+
145
+ it('should sign in successfully with valid credentials', async () => {
146
+ mockProvider.signIn.mockResolvedValue(mockUser);
147
+
148
+ const result = await authCoreService.signIn({
149
+ email: 'test@example.com',
150
+ password: 'password123',
151
+ });
152
+
153
+ expect(result).toEqual(mockUser);
154
+ expect(mockProvider.signIn).toHaveBeenCalledWith({
155
+ email: 'test@example.com',
156
+ password: 'password123',
157
+ });
158
+ });
159
+
160
+ it('should throw error when not initialized', async () => {
161
+ const uninitializedService = new AuthCoreService(DEFAULT_AUTH_CONFIG);
162
+
163
+ await expect(uninitializedService.signIn({
164
+ email: 'test@example.com',
165
+ password: 'password123',
166
+ })).rejects.toThrow('Auth service is not initialized');
167
+ });
168
+ });
169
+
170
+ describe('signOut', () => {
171
+ beforeEach(async () => {
172
+ await authCoreService.initialize(mockProvider);
173
+ });
174
+
175
+ it('should sign out successfully', async () => {
176
+ await authCoreService.signOut();
177
+ expect(mockProvider.signOut).toHaveBeenCalled();
178
+ });
179
+
180
+ it('should handle sign out when not initialized', async () => {
181
+ const uninitializedService = new AuthCoreService(DEFAULT_AUTH_CONFIG);
182
+
183
+ await expect(uninitializedService.signOut()).resolves.not.toThrow();
184
+ });
185
+ });
186
+
187
+ describe('getCurrentUser', () => {
188
+ const mockUser: AuthUser = {
189
+ uid: 'test-uid',
190
+ email: 'test@example.com',
191
+ displayName: 'Test User',
192
+ isAnonymous: false,
193
+ emailVerified: false,
194
+ photoURL: null,
195
+ };
196
+
197
+ it('should return null when not initialized', () => {
198
+ const result = authCoreService.getCurrentUser();
199
+ expect(result).toBeNull();
200
+ });
201
+
202
+ it('should return current user when initialized', async () => {
203
+ mockProvider.getCurrentUser.mockReturnValue(mockUser);
204
+ await authCoreService.initialize(mockProvider);
205
+
206
+ const result = authCoreService.getCurrentUser();
207
+ expect(result).toEqual(mockUser);
208
+ });
209
+
210
+ it('should return null when no current user', async () => {
211
+ mockProvider.getCurrentUser.mockReturnValue(null);
212
+ await authCoreService.initialize(mockProvider);
213
+
214
+ const result = authCoreService.getCurrentUser();
215
+ expect(result).toBeNull();
216
+ });
217
+ });
218
+
219
+ describe('onAuthStateChange', () => {
220
+ it('should return cleanup function when not initialized', () => {
221
+ const callback = jest.fn();
222
+ const cleanup = authCoreService.onAuthStateChange(callback);
223
+
224
+ expect(callback).toHaveBeenCalledWith(null);
225
+ expect(typeof cleanup).toBe('function');
226
+ });
227
+
228
+ it('should subscribe to auth state changes when initialized', async () => {
229
+ const callback = jest.fn();
230
+ const mockCleanup = jest.fn();
231
+ mockProvider.onAuthStateChange.mockReturnValue(mockCleanup);
232
+
233
+ await authCoreService.initialize(mockProvider);
234
+ const cleanup = authCoreService.onAuthStateChange(callback);
235
+
236
+ expect(mockProvider.onAuthStateChange).toHaveBeenCalledWith(callback);
237
+ expect(cleanup).toBe(mockCleanup);
238
+ });
239
+ });
240
+
241
+ describe('getConfig', () => {
242
+ it('should return the current config', () => {
243
+ const config = authCoreService.getConfig();
244
+ expect(config).toEqual(DEFAULT_AUTH_CONFIG);
245
+ });
246
+ });
247
+ });
@@ -0,0 +1,226 @@
1
+ /**
2
+ * AuthPackage Tests
3
+ */
4
+
5
+ import {
6
+ AuthPackage,
7
+ initializeAuthPackage,
8
+ getAuthPackage,
9
+ resetAuthPackage,
10
+ DEFAULT_AUTH_PACKAGE_CONFIG
11
+ } from '../../../src/infrastructure/services/AuthPackage';
12
+ import type { IStorageProvider, IUIProvider, IValidationProvider } from '../../../src/infrastructure/services/AuthPackage';
13
+
14
+ describe('AuthPackage', () => {
15
+ let mockStorageProvider: jest.Mocked<IStorageProvider>;
16
+ let mockUIProvider: jest.Mocked<IUIProvider>;
17
+ let mockValidationProvider: jest.Mocked<IValidationProvider>;
18
+
19
+ beforeEach(() => {
20
+ mockStorageProvider = {
21
+ get: jest.fn(),
22
+ set: jest.fn(),
23
+ remove: jest.fn(),
24
+ };
25
+
26
+ mockUIProvider = {
27
+ getTheme: jest.fn(),
28
+ getLocalization: jest.fn(),
29
+ };
30
+
31
+ mockValidationProvider = {
32
+ validateEmail: jest.fn(),
33
+ validatePassword: jest.fn(),
34
+ };
35
+
36
+ // Reset package state
37
+ resetAuthPackage();
38
+ });
39
+
40
+ describe('constructor', () => {
41
+ it('should initialize with default config', () => {
42
+ const authPackage = new AuthPackage();
43
+ const config = authPackage.getConfig();
44
+
45
+ expect(config).toEqual(DEFAULT_AUTH_PACKAGE_CONFIG);
46
+ });
47
+
48
+ it('should merge custom config with defaults', () => {
49
+ const customConfig = {
50
+ storageKeys: {
51
+ guestMode: '@custom_guest_mode',
52
+ showRegister: 'custom_show_register',
53
+ },
54
+ features: {
55
+ guestMode: false,
56
+ registration: false,
57
+ passwordStrength: false,
58
+ },
59
+ };
60
+
61
+ const authPackage = new AuthPackage(customConfig);
62
+ const config = authPackage.getConfig();
63
+
64
+ expect(config.storageKeys).toEqual(customConfig.storageKeys);
65
+ expect(config.features).toEqual(customConfig.features);
66
+ expect(config.validation).toEqual(DEFAULT_AUTH_PACKAGE_CONFIG.validation);
67
+ expect(config.ui).toEqual(DEFAULT_AUTH_PACKAGE_CONFIG.ui);
68
+ });
69
+
70
+ it('should deeply merge nested config', () => {
71
+ const customConfig = {
72
+ validation: {
73
+ passwordConfig: {
74
+ minLength: 12,
75
+ requireUppercase: true,
76
+ },
77
+ },
78
+ };
79
+
80
+ const authPackage = new AuthPackage(customConfig);
81
+ const config = authPackage.getConfig();
82
+
83
+ expect(config.validation.passwordConfig.minLength).toBe(12);
84
+ expect(config.validation.passwordConfig.requireUppercase).toBe(true);
85
+ expect(config.validation.passwordConfig.requireLowercase).toBe(true); // Should keep default
86
+ });
87
+ });
88
+
89
+ describe('provider setters and getters', () => {
90
+ let authPackage: AuthPackage;
91
+
92
+ beforeEach(() => {
93
+ authPackage = new AuthPackage();
94
+ });
95
+
96
+ it('should set and get storage provider', () => {
97
+ authPackage.setStorageProvider(mockStorageProvider);
98
+ expect(authPackage.getStorageProvider()).toBe(mockStorageProvider);
99
+ });
100
+
101
+ it('should set and get UI provider', () => {
102
+ authPackage.setUIProvider(mockUIProvider);
103
+ expect(authPackage.getUIProvider()).toBe(mockUIProvider);
104
+ });
105
+
106
+ it('should set and get validation provider', () => {
107
+ authPackage.setValidationProvider(mockValidationProvider);
108
+ expect(authPackage.getValidationProvider()).toBe(mockValidationProvider);
109
+ });
110
+ });
111
+
112
+ describe('isFeatureEnabled', () => {
113
+ let authPackage: AuthPackage;
114
+
115
+ beforeEach(() => {
116
+ authPackage = new AuthPackage();
117
+ });
118
+
119
+ it('should return feature status from config', () => {
120
+ expect(authPackage.isFeatureEnabled('guestMode')).toBe(true);
121
+ expect(authPackage.isFeatureEnabled('registration')).toBe(true);
122
+ expect(authPackage.isFeatureEnabled('passwordStrength')).toBe(true);
123
+ });
124
+
125
+ it('should return custom feature status', () => {
126
+ const customConfig = {
127
+ features: {
128
+ guestMode: false,
129
+ registration: false,
130
+ passwordStrength: false,
131
+ },
132
+ };
133
+
134
+ const customPackage = new AuthPackage(customConfig);
135
+
136
+ expect(customPackage.isFeatureEnabled('guestMode')).toBe(false);
137
+ expect(customPackage.isFeatureEnabled('registration')).toBe(false);
138
+ expect(customPackage.isFeatureEnabled('passwordStrength')).toBe(false);
139
+ });
140
+ });
141
+
142
+ describe('global package instance', () => {
143
+ it('should initialize package globally', () => {
144
+ const customConfig = {
145
+ storageKeys: {
146
+ guestMode: '@global_guest_mode',
147
+ },
148
+ };
149
+
150
+ const packageInstance = initializeAuthPackage(customConfig);
151
+ expect(packageInstance.getConfig().storageKeys.guestMode).toBe('@global_guest_mode');
152
+ });
153
+
154
+ it('should return existing package instance', () => {
155
+ const firstInstance = initializeAuthPackage();
156
+ const secondInstance = getAuthPackage();
157
+
158
+ expect(firstInstance).toBe(secondInstance);
159
+ });
160
+
161
+ it('should return null when no package initialized', () => {
162
+ resetAuthPackage();
163
+ const packageInstance = getAuthPackage();
164
+ expect(packageInstance).toBeNull();
165
+ });
166
+
167
+ it('should not reinitialize when already initialized', () => {
168
+ const firstInstance = initializeAuthPackage();
169
+ const secondInstance = initializeAuthPackage({
170
+ storageKeys: { guestMode: '@different' },
171
+ });
172
+
173
+ expect(firstInstance).toBe(secondInstance);
174
+ expect(secondInstance.getConfig().storageKeys.guestMode).toBe('@auth_guest_mode'); // Should keep original
175
+ });
176
+
177
+ it('should reset package instance', () => {
178
+ initializeAuthPackage();
179
+ expect(getAuthPackage()).not.toBeNull();
180
+
181
+ resetAuthPackage();
182
+ expect(getAuthPackage()).toBeNull();
183
+ });
184
+ });
185
+
186
+ describe('provider integration', () => {
187
+ let authPackage: AuthPackage;
188
+
189
+ beforeEach(() => {
190
+ authPackage = new AuthPackage();
191
+ authPackage.setStorageProvider(mockStorageProvider);
192
+ authPackage.setUIProvider(mockUIProvider);
193
+ authPackage.setValidationProvider(mockValidationProvider);
194
+ });
195
+
196
+ it('should integrate all providers', () => {
197
+ expect(authPackage.getStorageProvider()).toBe(mockStorageProvider);
198
+ expect(authPackage.getUIProvider()).toBe(mockUIProvider);
199
+ expect(authPackage.getValidationProvider()).toBe(mockValidationProvider);
200
+ });
201
+ });
202
+
203
+ describe('config validation', () => {
204
+ it('should handle empty config', () => {
205
+ const authPackage = new AuthPackage({});
206
+ const config = authPackage.getConfig();
207
+
208
+ expect(config).toEqual(DEFAULT_AUTH_PACKAGE_CONFIG);
209
+ });
210
+
211
+ it('should handle partial config', () => {
212
+ const partialConfig = {
213
+ features: {
214
+ guestMode: false,
215
+ },
216
+ };
217
+
218
+ const authPackage = new AuthPackage(partialConfig);
219
+ const config = authPackage.getConfig();
220
+
221
+ expect(config.features.guestMode).toBe(false);
222
+ expect(config.features.registration).toBe(true); // Should keep default
223
+ expect(config.features.passwordStrength).toBe(true); // Should keep default
224
+ });
225
+ });
226
+ });