@sudobility/auth-components-rn 1.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.
- package/dist/AuthAction.d.ts +7 -0
- package/dist/AuthAction.d.ts.map +1 -0
- package/dist/AuthInline.d.ts +7 -0
- package/dist/AuthInline.d.ts.map +1 -0
- package/dist/AuthProvider.d.ts +15 -0
- package/dist/AuthProvider.d.ts.map +1 -0
- package/dist/AuthScreen.d.ts +7 -0
- package/dist/AuthScreen.d.ts.map +1 -0
- package/dist/Avatar.d.ts +8 -0
- package/dist/Avatar.d.ts.map +1 -0
- package/dist/EmailSignInForm.d.ts +7 -0
- package/dist/EmailSignInForm.d.ts.map +1 -0
- package/dist/EmailSignUpForm.d.ts +7 -0
- package/dist/EmailSignUpForm.d.ts.map +1 -0
- package/dist/ForgotPasswordForm.d.ts +7 -0
- package/dist/ForgotPasswordForm.d.ts.map +1 -0
- package/dist/ProviderButtons.d.ts +7 -0
- package/dist/ProviderButtons.d.ts.map +1 -0
- package/dist/index.cjs.js +1850 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +1850 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/types.d.ts +313 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +64 -0
- package/src/AuthAction.tsx +107 -0
- package/src/AuthInline.tsx +124 -0
- package/src/AuthProvider.tsx +276 -0
- package/src/AuthScreen.tsx +117 -0
- package/src/Avatar.tsx +86 -0
- package/src/EmailSignInForm.tsx +130 -0
- package/src/EmailSignUpForm.tsx +161 -0
- package/src/ForgotPasswordForm.tsx +129 -0
- package/src/ProviderButtons.tsx +106 -0
- package/src/__tests__/AuthAction.test.tsx +261 -0
- package/src/__tests__/AuthProvider.test.tsx +459 -0
- package/src/__tests__/Avatar.test.tsx +165 -0
- package/src/index.ts +52 -0
- package/src/nativewind.d.ts +30 -0
- package/src/types.ts +383 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent, act } from '@testing-library/react-native';
|
|
3
|
+
import { Text } from 'react-native';
|
|
4
|
+
import { AuthAction } from '../AuthAction';
|
|
5
|
+
import { AuthProvider, createDefaultErrorTexts } from '../AuthProvider';
|
|
6
|
+
|
|
7
|
+
// Mock @sudobility/components-rn
|
|
8
|
+
jest.mock('@sudobility/components-rn', () => ({
|
|
9
|
+
cn: function () {
|
|
10
|
+
var args = Array.prototype.slice.call(arguments);
|
|
11
|
+
return args.filter(Boolean).join(' ');
|
|
12
|
+
},
|
|
13
|
+
Button: function (props) {
|
|
14
|
+
var RN = require('react-native');
|
|
15
|
+
var R = require('react');
|
|
16
|
+
return R.createElement(
|
|
17
|
+
RN.Pressable,
|
|
18
|
+
{ onPress: props.onPress, disabled: props.disabled, accessibilityRole: 'button' },
|
|
19
|
+
typeof props.children === 'string'
|
|
20
|
+
? R.createElement(RN.Text, null, props.children)
|
|
21
|
+
: props.children
|
|
22
|
+
);
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
var defaultTexts = {
|
|
27
|
+
signInTitle: 'Sign In',
|
|
28
|
+
signInWithEmail: 'Sign in with Email',
|
|
29
|
+
createAccount: 'Create Account',
|
|
30
|
+
resetPassword: 'Reset Password',
|
|
31
|
+
signIn: 'Sign In',
|
|
32
|
+
signUp: 'Sign Up',
|
|
33
|
+
logout: 'Log Out',
|
|
34
|
+
login: 'Log In',
|
|
35
|
+
continueWithGoogle: 'Continue with Google',
|
|
36
|
+
continueWithApple: 'Continue with Apple',
|
|
37
|
+
continueWithEmail: 'Continue with Email',
|
|
38
|
+
sendResetLink: 'Send Reset Link',
|
|
39
|
+
backToSignIn: 'Back to Sign In',
|
|
40
|
+
close: 'Close',
|
|
41
|
+
email: 'Email',
|
|
42
|
+
password: 'Password',
|
|
43
|
+
confirmPassword: 'Confirm Password',
|
|
44
|
+
displayName: 'Display Name',
|
|
45
|
+
emailPlaceholder: 'you@example.com',
|
|
46
|
+
passwordPlaceholder: 'Enter password',
|
|
47
|
+
confirmPasswordPlaceholder: 'Confirm password',
|
|
48
|
+
displayNamePlaceholder: 'Your name',
|
|
49
|
+
forgotPassword: 'Forgot password?',
|
|
50
|
+
noAccount: "Don't have an account?",
|
|
51
|
+
haveAccount: 'Already have an account?',
|
|
52
|
+
or: 'or',
|
|
53
|
+
resetEmailSent: 'Email Sent',
|
|
54
|
+
resetEmailSentDesc: 'Check {{email}} for a reset link.',
|
|
55
|
+
passwordMismatch: 'Passwords do not match',
|
|
56
|
+
passwordTooShort: 'Password must be at least 6 characters',
|
|
57
|
+
loading: 'Loading...',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
var defaultErrorTexts = createDefaultErrorTexts();
|
|
61
|
+
|
|
62
|
+
var defaultProviderConfig = {
|
|
63
|
+
providers: ['google', 'email'],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
function renderWithProvider(ui) {
|
|
67
|
+
return render(
|
|
68
|
+
<AuthProvider
|
|
69
|
+
providerConfig={defaultProviderConfig}
|
|
70
|
+
texts={defaultTexts}
|
|
71
|
+
errorTexts={defaultErrorTexts}
|
|
72
|
+
>
|
|
73
|
+
{ui}
|
|
74
|
+
</AuthProvider>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
var mockUser = {
|
|
79
|
+
uid: 'user-123',
|
|
80
|
+
email: 'test@example.com',
|
|
81
|
+
displayName: 'Test User',
|
|
82
|
+
photoURL: null,
|
|
83
|
+
isAnonymous: false,
|
|
84
|
+
emailVerified: true,
|
|
85
|
+
providerId: 'password',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
describe('AuthAction', () => {
|
|
89
|
+
describe('unauthenticated state', () => {
|
|
90
|
+
it('shows login button when not authenticated', async () => {
|
|
91
|
+
renderWithProvider(<AuthAction />);
|
|
92
|
+
|
|
93
|
+
await act(async () => {});
|
|
94
|
+
|
|
95
|
+
expect(screen.getByText('Log In')).toBeTruthy();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('shows custom login button content', async () => {
|
|
99
|
+
renderWithProvider(
|
|
100
|
+
<AuthAction loginButtonContent="Get Started" />
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
await act(async () => {});
|
|
104
|
+
|
|
105
|
+
expect(screen.getByText('Get Started')).toBeTruthy();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('calls onLoginPress when login button is pressed', async () => {
|
|
109
|
+
var onLoginPress = jest.fn();
|
|
110
|
+
renderWithProvider(<AuthAction onLoginPress={onLoginPress} />);
|
|
111
|
+
|
|
112
|
+
await act(async () => {});
|
|
113
|
+
|
|
114
|
+
fireEvent.press(screen.getByText('Log In'));
|
|
115
|
+
expect(onLoginPress).toHaveBeenCalledTimes(1);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('fires tracking event on login press', async () => {
|
|
119
|
+
var onTrack = jest.fn();
|
|
120
|
+
renderWithProvider(
|
|
121
|
+
<AuthAction onTrack={onTrack} trackingLabel="header" />
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
await act(async () => {});
|
|
125
|
+
|
|
126
|
+
fireEvent.press(screen.getByText('Log In'));
|
|
127
|
+
expect(onTrack).toHaveBeenCalledWith({
|
|
128
|
+
action: 'login_press',
|
|
129
|
+
trackingLabel: 'header',
|
|
130
|
+
componentName: 'AuthAction',
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('authenticated state', () => {
|
|
136
|
+
var mockSignOut;
|
|
137
|
+
|
|
138
|
+
beforeEach(() => {
|
|
139
|
+
mockSignOut = jest.fn().mockResolvedValue(undefined);
|
|
140
|
+
|
|
141
|
+
jest.spyOn(
|
|
142
|
+
require('../AuthProvider'),
|
|
143
|
+
'useAuthStatus'
|
|
144
|
+
).mockReturnValue({
|
|
145
|
+
user: mockUser,
|
|
146
|
+
loading: false,
|
|
147
|
+
error: null,
|
|
148
|
+
isAuthenticated: true,
|
|
149
|
+
isAnonymous: false,
|
|
150
|
+
signInWithGoogle: jest.fn(),
|
|
151
|
+
signInWithApple: jest.fn(),
|
|
152
|
+
signInWithEmail: jest.fn(),
|
|
153
|
+
signUpWithEmail: jest.fn(),
|
|
154
|
+
resetPassword: jest.fn(),
|
|
155
|
+
signOut: mockSignOut,
|
|
156
|
+
signInAnonymously: jest.fn(),
|
|
157
|
+
clearError: jest.fn(),
|
|
158
|
+
texts: defaultTexts,
|
|
159
|
+
providerConfig: defaultProviderConfig,
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
afterEach(() => {
|
|
164
|
+
jest.restoreAllMocks();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('shows user display name when authenticated', () => {
|
|
168
|
+
renderWithProvider(<AuthAction />);
|
|
169
|
+
|
|
170
|
+
expect(screen.getByText('Test User')).toBeTruthy();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('shows user email when displayName and email both exist', () => {
|
|
174
|
+
renderWithProvider(<AuthAction />);
|
|
175
|
+
|
|
176
|
+
expect(screen.getByText('test@example.com')).toBeTruthy();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('shows logout button when authenticated', () => {
|
|
180
|
+
renderWithProvider(<AuthAction />);
|
|
181
|
+
|
|
182
|
+
expect(screen.getByText('Log Out')).toBeTruthy();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('calls signOut and onLogoutPress when logout is pressed', async () => {
|
|
186
|
+
var onLogoutPress = jest.fn();
|
|
187
|
+
renderWithProvider(<AuthAction onLogoutPress={onLogoutPress} />);
|
|
188
|
+
|
|
189
|
+
await act(async () => {
|
|
190
|
+
fireEvent.press(screen.getByText('Log Out'));
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(onLogoutPress).toHaveBeenCalledTimes(1);
|
|
194
|
+
expect(mockSignOut).toHaveBeenCalledTimes(1);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('fires tracking event on logout press', async () => {
|
|
198
|
+
var onTrack = jest.fn();
|
|
199
|
+
renderWithProvider(
|
|
200
|
+
<AuthAction onTrack={onTrack} trackingLabel="header" />
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
await act(async () => {
|
|
204
|
+
fireEvent.press(screen.getByText('Log Out'));
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(onTrack).toHaveBeenCalledWith({
|
|
208
|
+
action: 'logout_press',
|
|
209
|
+
trackingLabel: 'header',
|
|
210
|
+
componentName: 'AuthAction',
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('hides user info when showUserInfo is false', () => {
|
|
215
|
+
renderWithProvider(<AuthAction showUserInfo={false} />);
|
|
216
|
+
|
|
217
|
+
expect(screen.queryByText('Test User')).toBeNull();
|
|
218
|
+
expect(screen.queryByText('test@example.com')).toBeNull();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('shows email as primary text when displayName is null', () => {
|
|
222
|
+
jest.spyOn(
|
|
223
|
+
require('../AuthProvider'),
|
|
224
|
+
'useAuthStatus'
|
|
225
|
+
).mockReturnValue({
|
|
226
|
+
user: Object.assign({}, mockUser, { displayName: null }),
|
|
227
|
+
loading: false,
|
|
228
|
+
error: null,
|
|
229
|
+
isAuthenticated: true,
|
|
230
|
+
isAnonymous: false,
|
|
231
|
+
signInWithGoogle: jest.fn(),
|
|
232
|
+
signInWithApple: jest.fn(),
|
|
233
|
+
signInWithEmail: jest.fn(),
|
|
234
|
+
signUpWithEmail: jest.fn(),
|
|
235
|
+
resetPassword: jest.fn(),
|
|
236
|
+
signOut: mockSignOut,
|
|
237
|
+
signInAnonymously: jest.fn(),
|
|
238
|
+
clearError: jest.fn(),
|
|
239
|
+
texts: defaultTexts,
|
|
240
|
+
providerConfig: defaultProviderConfig,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
renderWithProvider(<AuthAction />);
|
|
244
|
+
|
|
245
|
+
expect(screen.getByText('test@example.com')).toBeTruthy();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('uses custom renderAvatar when provided', () => {
|
|
249
|
+
renderWithProvider(
|
|
250
|
+
<AuthAction
|
|
251
|
+
renderAvatar={function (user) {
|
|
252
|
+
return <Text testID="custom-avatar">{user.uid}</Text>;
|
|
253
|
+
}}
|
|
254
|
+
/>
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
expect(screen.getByTestId('custom-avatar')).toBeTruthy();
|
|
258
|
+
expect(screen.getByText('user-123')).toBeTruthy();
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
});
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, act } from '@testing-library/react-native';
|
|
3
|
+
import { Text } from 'react-native';
|
|
4
|
+
import {
|
|
5
|
+
AuthProvider,
|
|
6
|
+
useAuthStatus,
|
|
7
|
+
createDefaultErrorTexts,
|
|
8
|
+
} from '../AuthProvider';
|
|
9
|
+
|
|
10
|
+
// Mock @sudobility/components-rn (imported by child components)
|
|
11
|
+
jest.mock('@sudobility/components-rn', () => ({
|
|
12
|
+
cn: function () {
|
|
13
|
+
var args = Array.prototype.slice.call(arguments);
|
|
14
|
+
return args.filter(Boolean).join(' ');
|
|
15
|
+
},
|
|
16
|
+
Button: function (props) {
|
|
17
|
+
var RN = require('react-native');
|
|
18
|
+
var R = require('react');
|
|
19
|
+
return R.createElement(
|
|
20
|
+
RN.Pressable,
|
|
21
|
+
{ onPress: props.onPress, disabled: props.disabled, accessibilityRole: 'button' },
|
|
22
|
+
typeof props.children === 'string'
|
|
23
|
+
? R.createElement(RN.Text, null, props.children)
|
|
24
|
+
: props.children
|
|
25
|
+
);
|
|
26
|
+
},
|
|
27
|
+
Card: function (props) {
|
|
28
|
+
var RN = require('react-native');
|
|
29
|
+
var R = require('react');
|
|
30
|
+
return R.createElement(RN.View, null, props.children);
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
var defaultTexts = {
|
|
35
|
+
signInTitle: 'Sign In',
|
|
36
|
+
signInWithEmail: 'Sign in with Email',
|
|
37
|
+
createAccount: 'Create Account',
|
|
38
|
+
resetPassword: 'Reset Password',
|
|
39
|
+
signIn: 'Sign In',
|
|
40
|
+
signUp: 'Sign Up',
|
|
41
|
+
logout: 'Log Out',
|
|
42
|
+
login: 'Log In',
|
|
43
|
+
continueWithGoogle: 'Continue with Google',
|
|
44
|
+
continueWithApple: 'Continue with Apple',
|
|
45
|
+
continueWithEmail: 'Continue with Email',
|
|
46
|
+
sendResetLink: 'Send Reset Link',
|
|
47
|
+
backToSignIn: 'Back to Sign In',
|
|
48
|
+
close: 'Close',
|
|
49
|
+
email: 'Email',
|
|
50
|
+
password: 'Password',
|
|
51
|
+
confirmPassword: 'Confirm Password',
|
|
52
|
+
displayName: 'Display Name',
|
|
53
|
+
emailPlaceholder: 'you@example.com',
|
|
54
|
+
passwordPlaceholder: 'Enter password',
|
|
55
|
+
confirmPasswordPlaceholder: 'Confirm password',
|
|
56
|
+
displayNamePlaceholder: 'Your name',
|
|
57
|
+
forgotPassword: 'Forgot password?',
|
|
58
|
+
noAccount: "Don't have an account?",
|
|
59
|
+
haveAccount: 'Already have an account?',
|
|
60
|
+
or: 'or',
|
|
61
|
+
resetEmailSent: 'Email Sent',
|
|
62
|
+
resetEmailSentDesc: 'Check {{email}} for a reset link.',
|
|
63
|
+
passwordMismatch: 'Passwords do not match',
|
|
64
|
+
passwordTooShort: 'Password must be at least 6 characters',
|
|
65
|
+
loading: 'Loading...',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
var defaultErrorTexts = createDefaultErrorTexts();
|
|
69
|
+
|
|
70
|
+
var defaultProviderConfig = {
|
|
71
|
+
providers: ['google', 'email'],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function renderWithProvider(ui, options) {
|
|
75
|
+
var opts = options || {};
|
|
76
|
+
return render(
|
|
77
|
+
<AuthProvider
|
|
78
|
+
providerConfig={defaultProviderConfig}
|
|
79
|
+
texts={defaultTexts}
|
|
80
|
+
errorTexts={defaultErrorTexts}
|
|
81
|
+
callbacks={opts.callbacks}
|
|
82
|
+
resolveErrorMessage={opts.resolveErrorMessage}
|
|
83
|
+
>
|
|
84
|
+
{ui}
|
|
85
|
+
</AuthProvider>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Helper component that exposes context values via text */
|
|
90
|
+
function AuthStatusDisplay() {
|
|
91
|
+
var ctx = useAuthStatus();
|
|
92
|
+
return (
|
|
93
|
+
<>
|
|
94
|
+
<Text testID="loading">{String(ctx.loading)}</Text>
|
|
95
|
+
<Text testID="error">{ctx.error != null ? ctx.error : 'null'}</Text>
|
|
96
|
+
<Text testID="isAuthenticated">{String(ctx.isAuthenticated)}</Text>
|
|
97
|
+
<Text testID="isAnonymous">{String(ctx.isAnonymous)}</Text>
|
|
98
|
+
<Text testID="user">{ctx.user ? ctx.user.uid : 'null'}</Text>
|
|
99
|
+
</>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
describe('AuthProvider', () => {
|
|
104
|
+
describe('createDefaultErrorTexts', () => {
|
|
105
|
+
it('returns an object with expected error keys', () => {
|
|
106
|
+
var texts = createDefaultErrorTexts();
|
|
107
|
+
expect(texts['auth/user-not-found']).toBe(
|
|
108
|
+
'No account found with this email.'
|
|
109
|
+
);
|
|
110
|
+
expect(texts['auth/wrong-password']).toBe('Incorrect password.');
|
|
111
|
+
expect(texts.default).toBe('An error occurred. Please try again.');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('useAuthStatus outside provider', () => {
|
|
116
|
+
it('throws when used outside AuthProvider', () => {
|
|
117
|
+
var spy = jest.spyOn(console, 'error').mockImplementation(function () {});
|
|
118
|
+
|
|
119
|
+
expect(function () {
|
|
120
|
+
render(<AuthStatusDisplay />);
|
|
121
|
+
}).toThrow('useAuthStatus must be used within an AuthProvider');
|
|
122
|
+
|
|
123
|
+
spy.mockRestore();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('initial state', () => {
|
|
128
|
+
it('starts with loading false after mount and no user', async () => {
|
|
129
|
+
renderWithProvider(<AuthStatusDisplay />);
|
|
130
|
+
|
|
131
|
+
await act(async () => {});
|
|
132
|
+
|
|
133
|
+
expect(screen.getByTestId('loading').props.children).toBe('false');
|
|
134
|
+
expect(screen.getByTestId('user').props.children).toBe('null');
|
|
135
|
+
expect(screen.getByTestId('isAuthenticated').props.children).toBe('false');
|
|
136
|
+
expect(screen.getByTestId('isAnonymous').props.children).toBe('false');
|
|
137
|
+
expect(screen.getByTestId('error').props.children).toBe('null');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('signOut', () => {
|
|
142
|
+
it('calls onSignOut callback on sign out', async () => {
|
|
143
|
+
var onSignOut = jest.fn();
|
|
144
|
+
|
|
145
|
+
function SignOutTrigger() {
|
|
146
|
+
var ctx = useAuthStatus();
|
|
147
|
+
return (
|
|
148
|
+
<Text testID="signout" onPress={function () { ctx.signOut(); }}>
|
|
149
|
+
Sign Out
|
|
150
|
+
</Text>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
renderWithProvider(
|
|
155
|
+
<>
|
|
156
|
+
<AuthStatusDisplay />
|
|
157
|
+
<SignOutTrigger />
|
|
158
|
+
</>,
|
|
159
|
+
{ callbacks: { onSignOut: onSignOut } }
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
await act(async () => {});
|
|
163
|
+
|
|
164
|
+
await act(async () => {
|
|
165
|
+
screen.getByTestId('signout').props.onPress();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(onSignOut).toHaveBeenCalledTimes(1);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('signInWithEmail', () => {
|
|
173
|
+
it('sets error when sign-in fails (placeholder implementation)', async () => {
|
|
174
|
+
var onError = jest.fn();
|
|
175
|
+
|
|
176
|
+
function SignInTrigger() {
|
|
177
|
+
var ctx = useAuthStatus();
|
|
178
|
+
return (
|
|
179
|
+
<Text
|
|
180
|
+
testID="signin"
|
|
181
|
+
onPress={function () { ctx.signInWithEmail('test@test.com', 'pass'); }}
|
|
182
|
+
>
|
|
183
|
+
Sign In
|
|
184
|
+
</Text>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
renderWithProvider(
|
|
189
|
+
<>
|
|
190
|
+
<AuthStatusDisplay />
|
|
191
|
+
<SignInTrigger />
|
|
192
|
+
</>,
|
|
193
|
+
{ callbacks: { onError: onError } }
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
await act(async () => {});
|
|
197
|
+
|
|
198
|
+
await act(async () => {
|
|
199
|
+
screen.getByTestId('signin').props.onPress();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
expect(screen.getByTestId('error').props.children).toBe(
|
|
203
|
+
'An error occurred. Please try again.'
|
|
204
|
+
);
|
|
205
|
+
expect(onError).toHaveBeenCalledTimes(1);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('signUpWithEmail', () => {
|
|
210
|
+
it('sets error when sign-up fails (placeholder implementation)', async () => {
|
|
211
|
+
function SignUpTrigger() {
|
|
212
|
+
var ctx = useAuthStatus();
|
|
213
|
+
return (
|
|
214
|
+
<Text
|
|
215
|
+
testID="signup"
|
|
216
|
+
onPress={function () {
|
|
217
|
+
ctx.signUpWithEmail('test@test.com', 'pass123', 'Test User');
|
|
218
|
+
}}
|
|
219
|
+
>
|
|
220
|
+
Sign Up
|
|
221
|
+
</Text>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
renderWithProvider(
|
|
226
|
+
<>
|
|
227
|
+
<AuthStatusDisplay />
|
|
228
|
+
<SignUpTrigger />
|
|
229
|
+
</>
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
await act(async () => {});
|
|
233
|
+
|
|
234
|
+
await act(async () => {
|
|
235
|
+
screen.getByTestId('signup').props.onPress();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(screen.getByTestId('error').props.children).not.toBe('null');
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe('signInWithGoogle', () => {
|
|
243
|
+
it('sets error when Google sign-in fails (placeholder)', async () => {
|
|
244
|
+
function GoogleTrigger() {
|
|
245
|
+
var ctx = useAuthStatus();
|
|
246
|
+
return (
|
|
247
|
+
<Text testID="google" onPress={function () { ctx.signInWithGoogle(); }}>
|
|
248
|
+
Google
|
|
249
|
+
</Text>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
renderWithProvider(
|
|
254
|
+
<>
|
|
255
|
+
<AuthStatusDisplay />
|
|
256
|
+
<GoogleTrigger />
|
|
257
|
+
</>
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
await act(async () => {});
|
|
261
|
+
|
|
262
|
+
await act(async () => {
|
|
263
|
+
screen.getByTestId('google').props.onPress();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(screen.getByTestId('error').props.children).not.toBe('null');
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('signInWithApple', () => {
|
|
271
|
+
it('sets error when Apple sign-in fails (placeholder)', async () => {
|
|
272
|
+
function AppleTrigger() {
|
|
273
|
+
var ctx = useAuthStatus();
|
|
274
|
+
return (
|
|
275
|
+
<Text testID="apple" onPress={function () { ctx.signInWithApple(); }}>
|
|
276
|
+
Apple
|
|
277
|
+
</Text>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
renderWithProvider(
|
|
282
|
+
<>
|
|
283
|
+
<AuthStatusDisplay />
|
|
284
|
+
<AppleTrigger />
|
|
285
|
+
</>
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
await act(async () => {});
|
|
289
|
+
|
|
290
|
+
await act(async () => {
|
|
291
|
+
screen.getByTestId('apple').props.onPress();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
expect(screen.getByTestId('error').props.children).not.toBe('null');
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('resetPassword', () => {
|
|
299
|
+
it('sets error when password reset fails (placeholder)', async () => {
|
|
300
|
+
function ResetTrigger() {
|
|
301
|
+
var ctx = useAuthStatus();
|
|
302
|
+
return (
|
|
303
|
+
<Text testID="reset" onPress={function () { ctx.resetPassword('test@test.com'); }}>
|
|
304
|
+
Reset
|
|
305
|
+
</Text>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
renderWithProvider(
|
|
310
|
+
<>
|
|
311
|
+
<AuthStatusDisplay />
|
|
312
|
+
<ResetTrigger />
|
|
313
|
+
</>
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
await act(async () => {});
|
|
317
|
+
|
|
318
|
+
await act(async () => {
|
|
319
|
+
screen.getByTestId('reset').props.onPress();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
expect(screen.getByTestId('error').props.children).not.toBe('null');
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe('signInAnonymously', () => {
|
|
327
|
+
it('sets error when anonymous sign-in fails (placeholder)', async () => {
|
|
328
|
+
function AnonTrigger() {
|
|
329
|
+
var ctx = useAuthStatus();
|
|
330
|
+
return (
|
|
331
|
+
<Text testID="anon" onPress={function () { ctx.signInAnonymously(); }}>
|
|
332
|
+
Anon
|
|
333
|
+
</Text>
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
renderWithProvider(
|
|
338
|
+
<>
|
|
339
|
+
<AuthStatusDisplay />
|
|
340
|
+
<AnonTrigger />
|
|
341
|
+
</>
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
await act(async () => {});
|
|
345
|
+
|
|
346
|
+
await act(async () => {
|
|
347
|
+
screen.getByTestId('anon').props.onPress();
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
expect(screen.getByTestId('error').props.children).not.toBe('null');
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
describe('clearError', () => {
|
|
355
|
+
it('clears error state', async () => {
|
|
356
|
+
function ErrorTrigger() {
|
|
357
|
+
var ctx = useAuthStatus();
|
|
358
|
+
return (
|
|
359
|
+
<>
|
|
360
|
+
<Text
|
|
361
|
+
testID="signin"
|
|
362
|
+
onPress={function () { ctx.signInWithEmail('a@b.com', 'p'); }}
|
|
363
|
+
>
|
|
364
|
+
Sign In
|
|
365
|
+
</Text>
|
|
366
|
+
<Text testID="clear" onPress={function () { ctx.clearError(); }}>
|
|
367
|
+
Clear
|
|
368
|
+
</Text>
|
|
369
|
+
</>
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
renderWithProvider(
|
|
374
|
+
<>
|
|
375
|
+
<AuthStatusDisplay />
|
|
376
|
+
<ErrorTrigger />
|
|
377
|
+
</>
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
await act(async () => {});
|
|
381
|
+
|
|
382
|
+
// Trigger an error
|
|
383
|
+
await act(async () => {
|
|
384
|
+
screen.getByTestId('signin').props.onPress();
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
expect(screen.getByTestId('error').props.children).not.toBe('null');
|
|
388
|
+
|
|
389
|
+
// Clear the error
|
|
390
|
+
await act(async () => {
|
|
391
|
+
screen.getByTestId('clear').props.onPress();
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
expect(screen.getByTestId('error').props.children).toBe('null');
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
describe('resolveErrorMessage', () => {
|
|
399
|
+
it('uses custom resolveErrorMessage when provided', async () => {
|
|
400
|
+
var customResolver = jest.fn(function (code) {
|
|
401
|
+
return 'Custom: ' + code;
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
function ErrorTrigger() {
|
|
405
|
+
var ctx = useAuthStatus();
|
|
406
|
+
return (
|
|
407
|
+
<Text
|
|
408
|
+
testID="signin"
|
|
409
|
+
onPress={function () { ctx.signInWithEmail('a@b.com', 'p'); }}
|
|
410
|
+
>
|
|
411
|
+
Sign In
|
|
412
|
+
</Text>
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
renderWithProvider(
|
|
417
|
+
<>
|
|
418
|
+
<AuthStatusDisplay />
|
|
419
|
+
<ErrorTrigger />
|
|
420
|
+
</>,
|
|
421
|
+
{ resolveErrorMessage: customResolver }
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
await act(async () => {});
|
|
425
|
+
|
|
426
|
+
await act(async () => {
|
|
427
|
+
screen.getByTestId('signin').props.onPress();
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
expect(customResolver).toHaveBeenCalled();
|
|
431
|
+
expect(screen.getByTestId('error').props.children).toMatch(/^Custom:/);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
describe('context values', () => {
|
|
436
|
+
it('exposes texts and providerConfig from provider', async () => {
|
|
437
|
+
function ConfigDisplay() {
|
|
438
|
+
var ctx = useAuthStatus();
|
|
439
|
+
return (
|
|
440
|
+
<>
|
|
441
|
+
<Text testID="login-text">{ctx.texts.login}</Text>
|
|
442
|
+
<Text testID="providers">
|
|
443
|
+
{ctx.providerConfig.providers.join(',')}
|
|
444
|
+
</Text>
|
|
445
|
+
</>
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
renderWithProvider(<ConfigDisplay />);
|
|
450
|
+
|
|
451
|
+
await act(async () => {});
|
|
452
|
+
|
|
453
|
+
expect(screen.getByTestId('login-text').props.children).toBe('Log In');
|
|
454
|
+
expect(screen.getByTestId('providers').props.children).toBe(
|
|
455
|
+
'google,email'
|
|
456
|
+
);
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
});
|