@urbackend/react 0.1.1 → 0.2.0
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/index.d.mts +69 -2
- package/dist/index.d.ts +69 -2
- package/dist/index.js +245 -81
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +246 -82
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -1
- package/src/components/Toast.tsx +0 -91
- package/src/components/UrAuth.tsx +0 -405
- package/src/components/UrUserButton.tsx +0 -207
- package/src/components.tsx +0 -83
- package/src/context.tsx +0 -140
- package/src/hooks.ts +0 -163
- package/src/index.ts +0 -13
- package/tests/UrAuth.test.tsx +0 -90
- package/tests/context.test.tsx +0 -113
- package/tests/setupTests.ts +0 -1
- package/tsconfig.json +0 -24
- package/tsup.config.ts +0 -11
- package/vitest.config.ts +0 -9
package/src/context.tsx
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useContext, useEffect, useState, useMemo } from 'react';
|
|
2
|
-
import { UrBackendClient, AuthModule, DatabaseModule, StorageModule } from '@urbackend/sdk';
|
|
3
|
-
import type { AuthUser } from '@urbackend/sdk';
|
|
4
|
-
|
|
5
|
-
interface UrContextValue {
|
|
6
|
-
client: UrBackendClient | null;
|
|
7
|
-
auth: AuthModule | null;
|
|
8
|
-
db: DatabaseModule | null;
|
|
9
|
-
storage: StorageModule | null;
|
|
10
|
-
user: AuthUser | null;
|
|
11
|
-
setUser: React.Dispatch<React.SetStateAction<AuthUser | null>>;
|
|
12
|
-
isInitializing: boolean;
|
|
13
|
-
isLoading: boolean;
|
|
14
|
-
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
|
15
|
-
error: string | null;
|
|
16
|
-
setError: React.Dispatch<React.SetStateAction<string | null>>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const UrContext = createContext<UrContextValue | undefined>(undefined);
|
|
20
|
-
|
|
21
|
-
export interface UrProviderProps {
|
|
22
|
-
apiKey: string;
|
|
23
|
-
baseUrl?: string;
|
|
24
|
-
children: React.ReactNode;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const UrProvider: React.FC<UrProviderProps> = ({ apiKey, baseUrl, children }) => {
|
|
28
|
-
const [user, setUser] = useState<AuthUser | null>(null);
|
|
29
|
-
const [isInitializing, setIsInitializing] = useState(true);
|
|
30
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
31
|
-
const [error, setError] = useState<string | null>(null);
|
|
32
|
-
|
|
33
|
-
const { client, auth, db, storage } = useMemo(() => {
|
|
34
|
-
const _client = new UrBackendClient({ apiKey, baseUrl });
|
|
35
|
-
return {
|
|
36
|
-
client: _client,
|
|
37
|
-
auth: new AuthModule(_client),
|
|
38
|
-
db: new DatabaseModule(_client),
|
|
39
|
-
storage: new StorageModule(_client),
|
|
40
|
-
};
|
|
41
|
-
}, [apiKey, baseUrl]);
|
|
42
|
-
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
let mounted = true;
|
|
45
|
-
|
|
46
|
-
const initAuth = async () => {
|
|
47
|
-
try {
|
|
48
|
-
// Hydrate from localStorage first as a fallback for environments without cookies
|
|
49
|
-
if (typeof window !== 'undefined') {
|
|
50
|
-
const savedToken = localStorage.getItem('ur_auth_token');
|
|
51
|
-
if (savedToken) auth.setToken(savedToken);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Check for social auth callback params
|
|
55
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
56
|
-
const hashParams = new URLSearchParams(window.location.hash.substring(1));
|
|
57
|
-
const token = hashParams.get('token');
|
|
58
|
-
const rtCode = urlParams.get('rtCode');
|
|
59
|
-
const error = urlParams.get('error');
|
|
60
|
-
|
|
61
|
-
if (error) {
|
|
62
|
-
console.error('Social Auth Error:', error);
|
|
63
|
-
if (mounted) setError(error);
|
|
64
|
-
window.history.replaceState({}, document.title, window.location.pathname);
|
|
65
|
-
} else if (token) {
|
|
66
|
-
// Social auth succeeded, establish session immediately
|
|
67
|
-
auth.setToken(token);
|
|
68
|
-
if (typeof window !== 'undefined') localStorage.setItem('ur_auth_token', token);
|
|
69
|
-
|
|
70
|
-
if (rtCode) {
|
|
71
|
-
// Exchange for long-lived refresh token
|
|
72
|
-
try {
|
|
73
|
-
const exRes = await auth.socialExchange({ token, rtCode });
|
|
74
|
-
const exToken = (exRes as any).accessToken || (exRes as any).token;
|
|
75
|
-
if (exToken && typeof window !== 'undefined') localStorage.setItem('ur_auth_token', exToken);
|
|
76
|
-
} catch (err: any) {
|
|
77
|
-
console.error('Failed to exchange refresh token', err);
|
|
78
|
-
if (mounted) setError(err.message || 'Failed to complete social login');
|
|
79
|
-
throw err;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
window.history.replaceState({}, document.title, window.location.pathname);
|
|
83
|
-
} else {
|
|
84
|
-
// Attempt to silently refresh session using the HTTP-only cookie
|
|
85
|
-
try {
|
|
86
|
-
const res = await auth.refreshToken();
|
|
87
|
-
const newToken = res.accessToken || (res as any).token;
|
|
88
|
-
if (newToken && typeof window !== 'undefined') localStorage.setItem('ur_auth_token', newToken);
|
|
89
|
-
} catch (e) {
|
|
90
|
-
// If refresh fails, me() will catch it
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const currentUser = await auth.me();
|
|
95
|
-
if (mounted) {
|
|
96
|
-
setUser(currentUser);
|
|
97
|
-
}
|
|
98
|
-
} catch (error: any) {
|
|
99
|
-
if (mounted) {
|
|
100
|
-
setUser(null);
|
|
101
|
-
// Don't set global error for initial me() check failure (usually just means not logged in)
|
|
102
|
-
}
|
|
103
|
-
} finally {
|
|
104
|
-
if (mounted) {
|
|
105
|
-
setIsInitializing(false);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
initAuth();
|
|
111
|
-
|
|
112
|
-
return () => {
|
|
113
|
-
mounted = false;
|
|
114
|
-
};
|
|
115
|
-
}, [auth]);
|
|
116
|
-
|
|
117
|
-
const value: UrContextValue = {
|
|
118
|
-
client,
|
|
119
|
-
auth,
|
|
120
|
-
db,
|
|
121
|
-
storage,
|
|
122
|
-
user,
|
|
123
|
-
setUser,
|
|
124
|
-
isInitializing,
|
|
125
|
-
isLoading,
|
|
126
|
-
setIsLoading,
|
|
127
|
-
error,
|
|
128
|
-
setError,
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
return <UrContext.Provider value={value}>{children}</UrContext.Provider>;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
export const useUrContext = () => {
|
|
135
|
-
const context = useContext(UrContext);
|
|
136
|
-
if (!context) {
|
|
137
|
-
throw new Error('useUrContext must be used within an UrProvider');
|
|
138
|
-
}
|
|
139
|
-
return context;
|
|
140
|
-
};
|
package/src/hooks.ts
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
2
|
-
import { useUrContext } from './context';
|
|
3
|
-
import type {
|
|
4
|
-
LoginPayload,
|
|
5
|
-
SignUpPayload,
|
|
6
|
-
ChangePasswordPayload,
|
|
7
|
-
VerifyEmailPayload,
|
|
8
|
-
RequestPasswordResetPayload,
|
|
9
|
-
ResetPasswordPayload
|
|
10
|
-
} from '@urbackend/sdk';
|
|
11
|
-
|
|
12
|
-
export const useAuth = () => {
|
|
13
|
-
const { auth, user, setUser, isInitializing, isLoading, setIsLoading, error, setError } = useUrContext();
|
|
14
|
-
|
|
15
|
-
if (!auth) {
|
|
16
|
-
throw new Error('Auth module not initialized. Make sure you are inside UrProvider.');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const login = useCallback(async (payload: LoginPayload) => {
|
|
20
|
-
try {
|
|
21
|
-
setError(null);
|
|
22
|
-
setIsLoading(true);
|
|
23
|
-
const res = await auth.login(payload);
|
|
24
|
-
const token = res.accessToken || (res as any).token;
|
|
25
|
-
if (token && typeof window !== 'undefined') localStorage.setItem('ur_auth_token', token);
|
|
26
|
-
const currentUser = await auth.me();
|
|
27
|
-
setUser(currentUser);
|
|
28
|
-
} catch (err: any) {
|
|
29
|
-
setError(err.message || 'Login failed');
|
|
30
|
-
throw err;
|
|
31
|
-
} finally {
|
|
32
|
-
setIsLoading(false);
|
|
33
|
-
}
|
|
34
|
-
}, [auth, setUser, setIsLoading, setError]);
|
|
35
|
-
|
|
36
|
-
const signUp = useCallback(async (payload: SignUpPayload) => {
|
|
37
|
-
try {
|
|
38
|
-
setError(null);
|
|
39
|
-
setIsLoading(true);
|
|
40
|
-
const newUser = await auth.signUp(payload);
|
|
41
|
-
return newUser;
|
|
42
|
-
} catch (err: any) {
|
|
43
|
-
setError(err.message || 'Sign up failed');
|
|
44
|
-
throw err;
|
|
45
|
-
} finally {
|
|
46
|
-
setIsLoading(false);
|
|
47
|
-
}
|
|
48
|
-
}, [auth, setIsLoading, setError]);
|
|
49
|
-
|
|
50
|
-
const logout = useCallback(async () => {
|
|
51
|
-
try {
|
|
52
|
-
setError(null);
|
|
53
|
-
setIsLoading(true);
|
|
54
|
-
await auth.logout();
|
|
55
|
-
if (typeof window !== 'undefined') localStorage.removeItem('ur_auth_token');
|
|
56
|
-
setUser(null);
|
|
57
|
-
} catch (err: any) {
|
|
58
|
-
setError(err.message || 'Logout failed');
|
|
59
|
-
throw err;
|
|
60
|
-
} finally {
|
|
61
|
-
setIsLoading(false);
|
|
62
|
-
}
|
|
63
|
-
}, [auth, setUser, setIsLoading, setError]);
|
|
64
|
-
|
|
65
|
-
const socialLogin = useCallback((provider: 'google' | 'github') => {
|
|
66
|
-
setError(null);
|
|
67
|
-
const url = auth.socialStart(provider);
|
|
68
|
-
window.location.href = url;
|
|
69
|
-
}, [auth, setError]);
|
|
70
|
-
|
|
71
|
-
const verifyEmail = useCallback(async (payload: VerifyEmailPayload) => {
|
|
72
|
-
try {
|
|
73
|
-
setError(null);
|
|
74
|
-
return await auth.verifyEmail(payload);
|
|
75
|
-
} catch (err: any) {
|
|
76
|
-
setError(err.message || 'Email verification failed');
|
|
77
|
-
throw err;
|
|
78
|
-
}
|
|
79
|
-
}, [auth, setError]);
|
|
80
|
-
|
|
81
|
-
const changePassword = useCallback(async (payload: ChangePasswordPayload) => {
|
|
82
|
-
try {
|
|
83
|
-
setError(null);
|
|
84
|
-
return await auth.changePassword(payload);
|
|
85
|
-
} catch (err: any) {
|
|
86
|
-
setError(err.message || 'Failed to change password');
|
|
87
|
-
throw err;
|
|
88
|
-
}
|
|
89
|
-
}, [auth, setError]);
|
|
90
|
-
|
|
91
|
-
const requestPasswordReset = useCallback(async (payload: RequestPasswordResetPayload) => {
|
|
92
|
-
try {
|
|
93
|
-
setError(null);
|
|
94
|
-
setIsLoading(true);
|
|
95
|
-
return await auth.requestPasswordReset(payload);
|
|
96
|
-
} catch (err: any) {
|
|
97
|
-
setError(err.message || 'Failed to request password reset');
|
|
98
|
-
throw err;
|
|
99
|
-
} finally {
|
|
100
|
-
setIsLoading(false);
|
|
101
|
-
}
|
|
102
|
-
}, [auth, setError, setIsLoading]);
|
|
103
|
-
|
|
104
|
-
const resetPassword = useCallback(async (payload: ResetPasswordPayload) => {
|
|
105
|
-
try {
|
|
106
|
-
setError(null);
|
|
107
|
-
setIsLoading(true);
|
|
108
|
-
return await auth.resetPassword(payload);
|
|
109
|
-
} catch (err: any) {
|
|
110
|
-
setError(err.message || 'Failed to reset password');
|
|
111
|
-
throw err;
|
|
112
|
-
} finally {
|
|
113
|
-
setIsLoading(false);
|
|
114
|
-
}
|
|
115
|
-
}, [auth, setError, setIsLoading]);
|
|
116
|
-
|
|
117
|
-
const clearError = useCallback(() => setError(null), [setError]);
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
user,
|
|
121
|
-
isInitializing,
|
|
122
|
-
isLoading,
|
|
123
|
-
error,
|
|
124
|
-
isAuthenticated: !!user,
|
|
125
|
-
login,
|
|
126
|
-
signUp,
|
|
127
|
-
logout,
|
|
128
|
-
socialLogin,
|
|
129
|
-
verifyEmail,
|
|
130
|
-
changePassword,
|
|
131
|
-
requestPasswordReset,
|
|
132
|
-
resetPassword,
|
|
133
|
-
clearError,
|
|
134
|
-
authApi: auth // Escape hatch to underlying SDK
|
|
135
|
-
};
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
export const useUser = () => {
|
|
139
|
-
const { user, isInitializing, isLoading, error } = useUrContext();
|
|
140
|
-
return {
|
|
141
|
-
user,
|
|
142
|
-
isInitializing,
|
|
143
|
-
isLoading,
|
|
144
|
-
error,
|
|
145
|
-
isAuthenticated: !!user,
|
|
146
|
-
};
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
export const useDb = () => {
|
|
150
|
-
const { db } = useUrContext();
|
|
151
|
-
if (!db) {
|
|
152
|
-
throw new Error('Database module not initialized.');
|
|
153
|
-
}
|
|
154
|
-
return db;
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
export const useStorage = () => {
|
|
158
|
-
const { storage } = useUrContext();
|
|
159
|
-
if (!storage) {
|
|
160
|
-
throw new Error('Storage module not initialized.');
|
|
161
|
-
}
|
|
162
|
-
return storage;
|
|
163
|
-
};
|
package/src/index.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export { UrProvider, useUrContext } from './context';
|
|
2
|
-
export type { UrProviderProps } from './context';
|
|
3
|
-
|
|
4
|
-
export { useAuth, useUser, useDb, useStorage } from './hooks';
|
|
5
|
-
export { ProtectedRoute, GuestRoute } from './components';
|
|
6
|
-
export type { ProtectedRouteProps, GuestRouteProps } from './components';
|
|
7
|
-
|
|
8
|
-
export { UrAuth } from './components/UrAuth';
|
|
9
|
-
export type { UrAuthProps } from './components/UrAuth';
|
|
10
|
-
|
|
11
|
-
export * from './components/UrUserButton';
|
|
12
|
-
|
|
13
|
-
export * from '@urbackend/sdk'; // re-export types so users don't need to import from sdk directly
|
package/tests/UrAuth.test.tsx
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
3
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
4
|
-
import { UrAuth } from '../src/components/UrAuth';
|
|
5
|
-
|
|
6
|
-
// Mock the hooks module
|
|
7
|
-
const mockLogin = vi.fn();
|
|
8
|
-
const mockSignUp = vi.fn();
|
|
9
|
-
const mockSocialLogin = vi.fn();
|
|
10
|
-
const mockRequestPasswordReset = vi.fn();
|
|
11
|
-
const mockResetPassword = vi.fn();
|
|
12
|
-
const mockClearError = vi.fn();
|
|
13
|
-
|
|
14
|
-
vi.mock('../src/hooks', () => ({
|
|
15
|
-
useAuth: () => ({
|
|
16
|
-
login: mockLogin,
|
|
17
|
-
signUp: mockSignUp,
|
|
18
|
-
socialLogin: mockSocialLogin,
|
|
19
|
-
requestPasswordReset: mockRequestPasswordReset,
|
|
20
|
-
resetPassword: mockResetPassword,
|
|
21
|
-
isLoading: false,
|
|
22
|
-
error: null,
|
|
23
|
-
clearError: mockClearError,
|
|
24
|
-
}),
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
|
-
describe('UrAuth Component', () => {
|
|
28
|
-
beforeEach(() => {
|
|
29
|
-
vi.clearAllMocks();
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('renders login form by default', () => {
|
|
33
|
-
render(<UrAuth />);
|
|
34
|
-
|
|
35
|
-
expect(screen.getByPlaceholderText('Enter your email address')).toBeInTheDocument();
|
|
36
|
-
expect(screen.getByPlaceholderText('Enter your password')).toBeInTheDocument();
|
|
37
|
-
expect(screen.getByRole('button', { name: 'Log In' })).toBeInTheDocument();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('switches to signup form', () => {
|
|
41
|
-
render(<UrAuth />);
|
|
42
|
-
|
|
43
|
-
const signupToggle = screen.getAllByText('Sign Up')[0]; // Top switcher
|
|
44
|
-
fireEvent.click(signupToggle);
|
|
45
|
-
|
|
46
|
-
expect(screen.getByPlaceholderText('Enter your name')).toBeInTheDocument();
|
|
47
|
-
expect(screen.getByRole('button', { name: 'Create Account' })).toBeInTheDocument();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('calls login on submit', async () => {
|
|
51
|
-
mockLogin.mockResolvedValueOnce(undefined);
|
|
52
|
-
render(<UrAuth onSuccess={() => {}} />);
|
|
53
|
-
|
|
54
|
-
fireEvent.change(screen.getByPlaceholderText('Enter your email address'), { target: { value: 'test@example.com' } });
|
|
55
|
-
fireEvent.change(screen.getByPlaceholderText('Enter your password'), { target: { value: 'password123' } });
|
|
56
|
-
|
|
57
|
-
fireEvent.click(screen.getByRole('button', { name: 'Log In' }));
|
|
58
|
-
|
|
59
|
-
await waitFor(() => {
|
|
60
|
-
expect(mockLogin).toHaveBeenCalledWith({ email: 'test@example.com', password: 'password123' });
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('calls socialLogin when a provider button is clicked', () => {
|
|
65
|
-
render(<UrAuth providers={['google', 'github']} />);
|
|
66
|
-
|
|
67
|
-
fireEvent.click(screen.getByText('Continue with Google'));
|
|
68
|
-
expect(mockSocialLogin).toHaveBeenCalledWith('google');
|
|
69
|
-
|
|
70
|
-
fireEvent.click(screen.getByText('Continue with GitHub'));
|
|
71
|
-
expect(mockSocialLogin).toHaveBeenCalledWith('github');
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('switches to forgot password flow', async () => {
|
|
75
|
-
render(<UrAuth />);
|
|
76
|
-
|
|
77
|
-
fireEvent.click(screen.getByText('Forgot password?'));
|
|
78
|
-
|
|
79
|
-
expect(screen.getByText('Reset Password')).toBeInTheDocument();
|
|
80
|
-
expect(screen.getByRole('button', { name: 'Send Reset Code' })).toBeInTheDocument();
|
|
81
|
-
|
|
82
|
-
mockRequestPasswordReset.mockResolvedValueOnce(undefined);
|
|
83
|
-
fireEvent.change(screen.getByPlaceholderText('Enter your email address'), { target: { value: 'test@example.com' } });
|
|
84
|
-
fireEvent.click(screen.getByRole('button', { name: 'Send Reset Code' }));
|
|
85
|
-
|
|
86
|
-
await waitFor(() => {
|
|
87
|
-
expect(mockRequestPasswordReset).toHaveBeenCalledWith({ email: 'test@example.com' });
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
});
|
package/tests/context.test.tsx
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
4
|
-
import { UrProvider, useUrContext } from '../src/context';
|
|
5
|
-
import { AuthModule } from '@urbackend/sdk';
|
|
6
|
-
|
|
7
|
-
// Mock the SDK modules
|
|
8
|
-
vi.mock('@urbackend/sdk', () => {
|
|
9
|
-
const MockAuthModule = vi.fn().mockImplementation(() => ({
|
|
10
|
-
setToken: vi.fn(),
|
|
11
|
-
refreshToken: vi.fn().mockResolvedValue(undefined),
|
|
12
|
-
me: vi.fn().mockResolvedValue({ id: 'user123', email: 'test@example.com' }),
|
|
13
|
-
socialExchange: vi.fn().mockResolvedValue({ refreshToken: 'fake-rt' }),
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
UrBackendClient: vi.fn(),
|
|
18
|
-
AuthModule: MockAuthModule,
|
|
19
|
-
DatabaseModule: vi.fn(),
|
|
20
|
-
StorageModule: vi.fn(),
|
|
21
|
-
};
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe('UrProvider & useUrContext', () => {
|
|
25
|
-
beforeEach(() => {
|
|
26
|
-
vi.clearAllMocks();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('throws an error if useUrContext is used outside UrProvider', () => {
|
|
30
|
-
const TestComponent = () => {
|
|
31
|
-
useUrContext();
|
|
32
|
-
return <div>Test</div>;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// React Error Boundary catch
|
|
36
|
-
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
37
|
-
|
|
38
|
-
expect(() => render(<TestComponent />)).toThrow('useUrContext must be used within an UrProvider');
|
|
39
|
-
|
|
40
|
-
consoleError.mockRestore();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('initializes context and fetches user', async () => {
|
|
44
|
-
const TestComponent = () => {
|
|
45
|
-
const { user, isInitializing } = useUrContext();
|
|
46
|
-
if (isInitializing) return <div>Loading...</div>;
|
|
47
|
-
return <div>User: {user?.email}</div>;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
render(
|
|
51
|
-
<UrProvider apiKey="test-key" baseUrl="http://localhost:3000">
|
|
52
|
-
<TestComponent />
|
|
53
|
-
</UrProvider>
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
|
57
|
-
|
|
58
|
-
await waitFor(() => {
|
|
59
|
-
expect(screen.getByText('User: test@example.com')).toBeInTheDocument();
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('exchanges social token and rtCode if present in URL', async () => {
|
|
64
|
-
// Mock window.location
|
|
65
|
-
const originalLocation = window.location;
|
|
66
|
-
// @ts-ignore
|
|
67
|
-
delete window.location;
|
|
68
|
-
window.location = {
|
|
69
|
-
...originalLocation,
|
|
70
|
-
search: '?rtCode=test-rt-code',
|
|
71
|
-
hash: '#token=test-temp-token',
|
|
72
|
-
pathname: '/auth/callback',
|
|
73
|
-
} as any;
|
|
74
|
-
|
|
75
|
-
const originalHistory = window.history;
|
|
76
|
-
// @ts-ignore
|
|
77
|
-
delete window.history;
|
|
78
|
-
window.history = {
|
|
79
|
-
...originalHistory,
|
|
80
|
-
replaceState: vi.fn(),
|
|
81
|
-
} as any;
|
|
82
|
-
|
|
83
|
-
const TestComponent = () => {
|
|
84
|
-
const { isInitializing } = useUrContext();
|
|
85
|
-
if (isInitializing) return <div>Loading...</div>;
|
|
86
|
-
return <div>Ready</div>;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
render(
|
|
90
|
-
<UrProvider apiKey="test-key" baseUrl="http://localhost:3000">
|
|
91
|
-
<TestComponent />
|
|
92
|
-
</UrProvider>
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
await waitFor(() => {
|
|
96
|
-
expect(screen.getByText('Ready')).toBeInTheDocument();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// We can't access the specific instance directly, but we can check if the methods on the mock prototype were called
|
|
100
|
-
// Since AuthModule is mocked to return the object above, we can check its methods.
|
|
101
|
-
// However, vitest module mocking returns the factory. Let's just verify it using a spy on a global or check the logic.
|
|
102
|
-
// Actually, since we redefined AuthModule mock above, we need to inspect the instances.
|
|
103
|
-
const mockAuthInstance = vi.mocked(AuthModule).mock.results[0]?.value;
|
|
104
|
-
if (mockAuthInstance) {
|
|
105
|
-
expect(mockAuthInstance.setToken).toHaveBeenCalledWith('test-temp-token');
|
|
106
|
-
expect(mockAuthInstance.socialExchange).toHaveBeenCalledWith({ token: 'test-temp-token', rtCode: 'test-rt-code' });
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Restore window.location and window.history
|
|
110
|
-
window.location = originalLocation;
|
|
111
|
-
window.history = originalHistory;
|
|
112
|
-
});
|
|
113
|
-
});
|
package/tests/setupTests.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import '@testing-library/jest-dom';
|
package/tsconfig.json
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"useDefineForClassFields": true,
|
|
5
|
-
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
-
"module": "ESNext",
|
|
7
|
-
"skipLibCheck": true,
|
|
8
|
-
|
|
9
|
-
/* Bundler mode */
|
|
10
|
-
"moduleResolution": "bundler",
|
|
11
|
-
"allowImportingTsExtensions": true,
|
|
12
|
-
"resolveJsonModule": true,
|
|
13
|
-
"isolatedModules": true,
|
|
14
|
-
"noEmit": true,
|
|
15
|
-
"jsx": "react-jsx",
|
|
16
|
-
|
|
17
|
-
/* Linting */
|
|
18
|
-
"strict": true,
|
|
19
|
-
"noUnusedLocals": true,
|
|
20
|
-
"noUnusedParameters": true,
|
|
21
|
-
"noFallthroughCasesInSwitch": true
|
|
22
|
-
},
|
|
23
|
-
"include": ["src"]
|
|
24
|
-
}
|
package/tsup.config.ts
DELETED