@workos-inc/authkit-nextjs 2.12.2 → 2.14.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.
Files changed (46) hide show
  1. package/README.md +138 -73
  2. package/dist/esm/errors.js +33 -0
  3. package/dist/esm/errors.js.map +1 -0
  4. package/dist/esm/get-authorization-url.js +7 -2
  5. package/dist/esm/get-authorization-url.js.map +1 -1
  6. package/dist/esm/index.js +3 -1
  7. package/dist/esm/index.js.map +1 -1
  8. package/dist/esm/middleware-helpers.js +99 -0
  9. package/dist/esm/middleware-helpers.js.map +1 -0
  10. package/dist/esm/session.js +11 -35
  11. package/dist/esm/session.js.map +1 -1
  12. package/dist/esm/types/errors.d.ts +15 -0
  13. package/dist/esm/types/index.d.ts +3 -1
  14. package/dist/esm/types/middleware-helpers.d.ts +25 -0
  15. package/dist/esm/types/session.d.ts +1 -1
  16. package/dist/esm/types/validate-api-key.d.ts +1 -1
  17. package/dist/esm/types/workos.d.ts +1 -1
  18. package/dist/esm/utils.js +0 -2
  19. package/dist/esm/utils.js.map +1 -1
  20. package/dist/esm/workos.js +1 -1
  21. package/package.json +20 -21
  22. package/src/actions.spec.ts +14 -12
  23. package/src/auth.spec.ts +27 -29
  24. package/src/authkit-callback-route.spec.ts +31 -29
  25. package/src/components/authkit-provider.spec.tsx +67 -71
  26. package/src/components/button.spec.tsx +4 -6
  27. package/src/components/impersonation.spec.tsx +25 -25
  28. package/src/components/min-max-button.spec.tsx +2 -1
  29. package/src/components/tokenStore.spec.ts +21 -21
  30. package/src/components/useAccessToken.spec.tsx +73 -77
  31. package/src/components/useTokenClaims.spec.tsx +22 -22
  32. package/src/cookie.spec.ts +14 -9
  33. package/src/errors.spec.ts +108 -0
  34. package/src/errors.ts +46 -0
  35. package/src/get-authorization-url.spec.ts +12 -13
  36. package/src/get-authorization-url.ts +6 -10
  37. package/src/index.ts +16 -2
  38. package/src/middleware-helpers.spec.ts +231 -0
  39. package/src/middleware-helpers.ts +130 -0
  40. package/src/session.spec.ts +81 -73
  41. package/src/session.ts +16 -38
  42. package/src/utils.spec.ts +14 -31
  43. package/src/utils.ts +0 -2
  44. package/src/validate-api-key.spec.ts +4 -6
  45. package/src/workos.spec.ts +2 -2
  46. package/src/workos.ts +1 -1
@@ -10,17 +10,17 @@ import {
10
10
  switchToOrganizationAction,
11
11
  } from '../actions.js';
12
12
 
13
- jest.mock('../actions', () => ({
14
- checkSessionAction: jest.fn(),
15
- getAuthAction: jest.fn(),
16
- refreshAuthAction: jest.fn(),
17
- handleSignOutAction: jest.fn(),
18
- switchToOrganizationAction: jest.fn(),
13
+ vi.mock('../actions', () => ({
14
+ checkSessionAction: vi.fn(),
15
+ getAuthAction: vi.fn(),
16
+ refreshAuthAction: vi.fn(),
17
+ handleSignOutAction: vi.fn(),
18
+ switchToOrganizationAction: vi.fn(),
19
19
  }));
20
20
 
21
21
  describe('AuthKitProvider', () => {
22
22
  beforeEach(() => {
23
- jest.clearAllMocks();
23
+ vi.clearAllMocks();
24
24
  });
25
25
 
26
26
  it('should render children', async () => {
@@ -134,7 +134,7 @@ describe('AuthKitProvider', () => {
134
134
  });
135
135
 
136
136
  it('should call getAuthAction when initialAuth is not provided', async () => {
137
- (getAuthAction as jest.Mock).mockResolvedValueOnce({
137
+ (getAuthAction as Mock).mockResolvedValueOnce({
138
138
  user: { email: 'test@example.com' },
139
139
  sessionId: 'test-session',
140
140
  });
@@ -151,7 +151,7 @@ describe('AuthKitProvider', () => {
151
151
  });
152
152
 
153
153
  it('should do nothing if onSessionExpired is false', async () => {
154
- jest.spyOn(window, 'addEventListener');
154
+ vi.spyOn(window, 'addEventListener');
155
155
 
156
156
  await act(async () => {
157
157
  render(
@@ -166,8 +166,8 @@ describe('AuthKitProvider', () => {
166
166
  });
167
167
 
168
168
  it('should call onSessionExpired when session is expired', async () => {
169
- (checkSessionAction as jest.Mock).mockRejectedValueOnce(new Error('Failed to fetch'));
170
- const onSessionExpired = jest.fn();
169
+ (checkSessionAction as Mock).mockRejectedValueOnce(new Error('Failed to fetch'));
170
+ const onSessionExpired = vi.fn();
171
171
 
172
172
  render(
173
173
  <AuthKitProvider onSessionExpired={onSessionExpired}>
@@ -186,8 +186,8 @@ describe('AuthKitProvider', () => {
186
186
  });
187
187
 
188
188
  it('should only call onSessionExpired once if multiple visibility changes occur', async () => {
189
- (checkSessionAction as jest.Mock).mockRejectedValueOnce(new Error('Failed to fetch'));
190
- const onSessionExpired = jest.fn();
189
+ (checkSessionAction as Mock).mockRejectedValueOnce(new Error('Failed to fetch'));
190
+ const onSessionExpired = vi.fn();
191
191
 
192
192
  render(
193
193
  <AuthKitProvider onSessionExpired={onSessionExpired}>
@@ -207,9 +207,9 @@ describe('AuthKitProvider', () => {
207
207
  });
208
208
 
209
209
  it('should pass through if checkSessionAction does not throw "Failed to fetch"', async () => {
210
- (checkSessionAction as jest.Mock).mockResolvedValueOnce(false);
210
+ (checkSessionAction as Mock).mockResolvedValueOnce(false);
211
211
 
212
- const onSessionExpired = jest.fn();
212
+ const onSessionExpired = vi.fn();
213
213
 
214
214
  render(
215
215
  <AuthKitProvider onSessionExpired={onSessionExpired}>
@@ -227,74 +227,70 @@ describe('AuthKitProvider', () => {
227
227
  });
228
228
  });
229
229
 
230
- it('should reload the page when session is expired and no onSessionExpired handler is provided', async () => {
231
- (checkSessionAction as jest.Mock).mockRejectedValueOnce(new Error('Failed to fetch'));
230
+ describe('window.location.reload behavior', () => {
231
+ let originalLocation: Location;
232
232
 
233
- const originalLocation = window.location;
234
-
235
- // @ts-expect-error - we're deleting the property to test the mock
236
- delete window.location;
237
-
238
- window.location = { ...window.location, reload: jest.fn() };
239
-
240
- render(
241
- <AuthKitProvider>
242
- <div>Test Child</div>
243
- </AuthKitProvider>,
244
- );
245
-
246
- act(() => {
247
- // Simulate visibility change
248
- window.dispatchEvent(new Event('visibilitychange'));
233
+ beforeEach(() => {
234
+ originalLocation = window.location;
235
+ // @ts-expect-error - deleting window.location to mock it
236
+ delete window.location;
237
+ window.location = { reload: vi.fn() } as unknown as Location;
249
238
  });
250
239
 
251
- await waitFor(() => {
252
- expect(window.location.reload).toHaveBeenCalled();
240
+ afterEach(() => {
241
+ window.location = originalLocation;
253
242
  });
254
243
 
255
- // Restore original reload function
256
- window.location = originalLocation;
257
- });
244
+ it('should reload the page when session is expired and no onSessionExpired handler is provided', async () => {
245
+ (checkSessionAction as Mock).mockRejectedValueOnce(new Error('Failed to fetch'));
258
246
 
259
- it('should not call onSessionExpired or reload the page if session is valid', async () => {
260
- (checkSessionAction as jest.Mock).mockResolvedValueOnce(true);
261
- const onSessionExpired = jest.fn();
247
+ render(
248
+ <AuthKitProvider>
249
+ <div>Test Child</div>
250
+ </AuthKitProvider>,
251
+ );
262
252
 
263
- const originalLocation = window.location;
253
+ act(() => {
254
+ // Simulate visibility change
255
+ window.dispatchEvent(new Event('visibilitychange'));
256
+ });
264
257
 
265
- // @ts-expect-error - we're deleting the property to test the mock
266
- delete window.location;
258
+ await waitFor(() => {
259
+ expect(window.location.reload).toHaveBeenCalled();
260
+ });
261
+ });
267
262
 
268
- window.location = { ...window.location, reload: jest.fn() };
263
+ it('should not call onSessionExpired or reload the page if session is valid', async () => {
264
+ (checkSessionAction as Mock).mockResolvedValueOnce(true);
265
+ const onSessionExpired = vi.fn();
269
266
 
270
- render(
271
- <AuthKitProvider onSessionExpired={onSessionExpired}>
272
- <div>Test Child</div>
273
- </AuthKitProvider>,
274
- );
267
+ render(
268
+ <AuthKitProvider onSessionExpired={onSessionExpired}>
269
+ <div>Test Child</div>
270
+ </AuthKitProvider>,
271
+ );
275
272
 
276
- act(() => {
277
- // Simulate visibility change
278
- window.dispatchEvent(new Event('visibilitychange'));
279
- });
273
+ act(() => {
274
+ // Simulate visibility change
275
+ window.dispatchEvent(new Event('visibilitychange'));
276
+ });
280
277
 
281
- await waitFor(() => {
282
- expect(onSessionExpired).not.toHaveBeenCalled();
283
- expect(window.location.reload).not.toHaveBeenCalled();
278
+ await waitFor(() => {
279
+ expect(onSessionExpired).not.toHaveBeenCalled();
280
+ expect(window.location.reload).not.toHaveBeenCalled();
281
+ });
284
282
  });
285
-
286
- window.location = originalLocation;
287
283
  });
288
284
  });
289
285
 
290
286
  describe('useAuth', () => {
291
287
  beforeEach(() => {
292
- jest.clearAllMocks();
288
+ vi.clearAllMocks();
293
289
  });
294
290
 
295
291
  it('should call getAuth when a user is not returned when ensureSignedIn is true', async () => {
296
292
  // First and second calls return no user, second call returns a user
297
- (getAuthAction as jest.Mock)
293
+ (getAuthAction as Mock)
298
294
  .mockResolvedValueOnce({ user: null, loading: true })
299
295
  .mockResolvedValueOnce({ user: { email: 'test@example.com' }, loading: false });
300
296
 
@@ -323,7 +319,7 @@ describe('useAuth', () => {
323
319
  };
324
320
 
325
321
  // Suppress console.error for this test since we expect an error
326
- const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
322
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
327
323
 
328
324
  expect(() => {
329
325
  render(<TestComponent />);
@@ -333,7 +329,7 @@ describe('useAuth', () => {
333
329
  });
334
330
 
335
331
  it('should provide auth context values when used within AuthKitProvider', async () => {
336
- (getAuthAction as jest.Mock).mockResolvedValueOnce({
332
+ (getAuthAction as Mock).mockResolvedValueOnce({
337
333
  user: { email: 'test@example.com' },
338
334
  sessionId: 'test-session',
339
335
  organizationId: 'test-org',
@@ -381,8 +377,8 @@ describe('useAuth', () => {
381
377
  sessionId: 'test-session',
382
378
  };
383
379
 
384
- (getAuthAction as jest.Mock).mockResolvedValueOnce(mockAuth);
385
- (refreshAuthAction as jest.Mock).mockResolvedValueOnce({
380
+ (getAuthAction as Mock).mockResolvedValueOnce(mockAuth);
381
+ (refreshAuthAction as Mock).mockResolvedValueOnce({
386
382
  ...mockAuth,
387
383
  sessionId: 'new-session',
388
384
  });
@@ -424,10 +420,10 @@ describe('useAuth', () => {
424
420
  organizationId: 'new-org',
425
421
  };
426
422
 
427
- (getAuthAction as jest.Mock)
423
+ (getAuthAction as Mock)
428
424
  .mockResolvedValue(mockAuth)
429
425
  .mockResolvedValueOnce({ ...mockAuth, organizationId: 'old-org' });
430
- (switchToOrganizationAction as jest.Mock).mockResolvedValueOnce(mockAuth);
426
+ (switchToOrganizationAction as Mock).mockResolvedValueOnce(mockAuth);
431
427
 
432
428
  const TestComponent = () => {
433
429
  const auth = useAuth();
@@ -460,7 +456,7 @@ describe('useAuth', () => {
460
456
  });
461
457
 
462
458
  it('should receive an error when refreshAuth fails with an error', async () => {
463
- (refreshAuthAction as jest.Mock).mockRejectedValueOnce(new Error('Refresh failed'));
459
+ (refreshAuthAction as Mock).mockRejectedValueOnce(new Error('Refresh failed'));
464
460
 
465
461
  let error: string | undefined;
466
462
 
@@ -497,7 +493,7 @@ describe('useAuth', () => {
497
493
  });
498
494
 
499
495
  it('should receive an error when refreshAuth fails with a string error', async () => {
500
- (refreshAuthAction as jest.Mock).mockRejectedValueOnce('Refresh failed');
496
+ (refreshAuthAction as Mock).mockRejectedValueOnce('Refresh failed');
501
497
 
502
498
  let error: string | undefined;
503
499
 
@@ -534,7 +530,7 @@ describe('useAuth', () => {
534
530
  });
535
531
 
536
532
  it('should call handleSignOutAction when signOut is called', async () => {
537
- (handleSignOutAction as jest.Mock).mockResolvedValueOnce({});
533
+ (handleSignOutAction as Mock).mockResolvedValueOnce({});
538
534
 
539
535
  const TestComponent = () => {
540
536
  const auth = useAuth();
@@ -560,7 +556,7 @@ describe('useAuth', () => {
560
556
  });
561
557
 
562
558
  it('should pass returnTo parameter to handleSignOutAction', async () => {
563
- (handleSignOutAction as jest.Mock).mockResolvedValueOnce({});
559
+ (handleSignOutAction as Mock).mockResolvedValueOnce({});
564
560
 
565
561
  const TestComponent = () => {
566
562
  const auth = useAuth();
@@ -24,12 +24,10 @@ describe('Button', () => {
24
24
  const { getByRole } = render(<Button style={{ backgroundColor: 'red' }}>Click me</Button>);
25
25
  const button = getByRole('button');
26
26
 
27
- expect(button).toHaveStyle({
28
- backgroundColor: 'red',
29
- display: 'inline-flex',
30
- alignItems: 'center',
31
- justifyContent: 'center',
32
- });
27
+ expect(button.style.backgroundColor).toBe('red');
28
+ expect(button.style.display).toBe('inline-flex');
29
+ expect(button.style.alignItems).toBe('center');
30
+ expect(button.style.justifyContent).toBe('center');
33
31
  });
34
32
 
35
33
  it('should pass through additional props', () => {
@@ -7,23 +7,23 @@ import * as React from 'react';
7
7
  import { handleSignOutAction } from '../actions.js';
8
8
 
9
9
  // Mock the useAuth hook
10
- jest.mock('./authkit-provider', () => ({
11
- useAuth: jest.fn(),
10
+ vi.mock('./authkit-provider', () => ({
11
+ useAuth: vi.fn(),
12
12
  }));
13
13
 
14
14
  // Mock the getOrganizationAction
15
- jest.mock('../actions', () => ({
16
- getOrganizationAction: jest.fn(),
17
- handleSignOutAction: jest.fn(),
15
+ vi.mock('../actions', () => ({
16
+ getOrganizationAction: vi.fn(),
17
+ handleSignOutAction: vi.fn(),
18
18
  }));
19
19
 
20
20
  describe('Impersonation', () => {
21
21
  beforeEach(() => {
22
- jest.clearAllMocks();
22
+ vi.clearAllMocks();
23
23
  });
24
24
 
25
25
  it('should return null if not impersonating', () => {
26
- (useAuth as jest.Mock).mockReturnValue({
26
+ (useAuth as Mock).mockReturnValue({
27
27
  impersonator: null,
28
28
  user: { id: '123', email: 'user@example.com' },
29
29
  organizationId: null,
@@ -34,7 +34,7 @@ describe('Impersonation', () => {
34
34
  });
35
35
 
36
36
  it('should render impersonation banner when impersonating', () => {
37
- (useAuth as jest.Mock).mockReturnValue({
37
+ (useAuth as Mock).mockReturnValue({
38
38
  impersonator: { email: 'admin@example.com' },
39
39
  user: { id: '123', email: 'user@example.com' },
40
40
  organizationId: null,
@@ -45,13 +45,13 @@ describe('Impersonation', () => {
45
45
  });
46
46
 
47
47
  it('should render with organization info when organizationId is provided', async () => {
48
- (useAuth as jest.Mock).mockReturnValue({
48
+ (useAuth as Mock).mockReturnValue({
49
49
  impersonator: { email: 'admin@example.com' },
50
50
  user: { id: '123', email: 'user@example.com' },
51
51
  organizationId: 'org_123',
52
52
  });
53
53
 
54
- (getOrganizationAction as jest.Mock).mockResolvedValue({
54
+ (getOrganizationAction as Mock).mockResolvedValue({
55
55
  id: 'org_123',
56
56
  name: 'Test Org',
57
57
  });
@@ -64,7 +64,7 @@ describe('Impersonation', () => {
64
64
  });
65
65
 
66
66
  it('should render at the bottom by default', () => {
67
- (useAuth as jest.Mock).mockReturnValue({
67
+ (useAuth as Mock).mockReturnValue({
68
68
  impersonator: { email: 'admin@example.com' },
69
69
  user: { id: '123', email: 'user@example.com' },
70
70
  organizationId: null,
@@ -76,7 +76,7 @@ describe('Impersonation', () => {
76
76
  });
77
77
 
78
78
  it('should render at the top when side prop is "top"', () => {
79
- (useAuth as jest.Mock).mockReturnValue({
79
+ (useAuth as Mock).mockReturnValue({
80
80
  impersonator: { email: 'admin@example.com' },
81
81
  user: { id: '123', email: 'user@example.com' },
82
82
  organizationId: null,
@@ -88,7 +88,7 @@ describe('Impersonation', () => {
88
88
  });
89
89
 
90
90
  it('should merge custom styles with default styles', () => {
91
- (useAuth as jest.Mock).mockReturnValue({
91
+ (useAuth as Mock).mockReturnValue({
92
92
  impersonator: { email: 'admin@example.com' },
93
93
  user: { id: '123', email: 'user@example.com' },
94
94
  organizationId: null,
@@ -97,11 +97,11 @@ describe('Impersonation', () => {
97
97
  const customStyle = { backgroundColor: 'red' };
98
98
  const { container } = render(<Impersonation style={customStyle} />);
99
99
  const root = container.querySelector('[data-workos-impersonation-root]');
100
- expect(root).toHaveStyle({ backgroundColor: 'red' });
100
+ expect((root as HTMLElement).style.backgroundColor).toBe('red');
101
101
  });
102
102
 
103
103
  it('should should sign out when the Stop button is called', async () => {
104
- (useAuth as jest.Mock).mockReturnValue({
104
+ (useAuth as Mock).mockReturnValue({
105
105
  impersonator: { email: 'admin@example.com' },
106
106
  user: { id: '123', email: 'user@example.com' },
107
107
  organizationId: null,
@@ -114,7 +114,7 @@ describe('Impersonation', () => {
114
114
  });
115
115
 
116
116
  it('should pass returnTo prop to handleSignOutAction when provided', async () => {
117
- (useAuth as jest.Mock).mockReturnValue({
117
+ (useAuth as Mock).mockReturnValue({
118
118
  impersonator: { email: 'admin@example.com' },
119
119
  user: { id: '123', email: 'user@example.com' },
120
120
  organizationId: null,
@@ -129,7 +129,7 @@ describe('Impersonation', () => {
129
129
  });
130
130
 
131
131
  it('should not call getOrganizationAction when organizationId is not provided', () => {
132
- (useAuth as jest.Mock).mockReturnValue({
132
+ (useAuth as Mock).mockReturnValue({
133
133
  impersonator: { email: 'admin@example.com' },
134
134
  user: { id: '123', email: 'user@example.com' },
135
135
  organizationId: null,
@@ -140,7 +140,7 @@ describe('Impersonation', () => {
140
140
  });
141
141
 
142
142
  it('should not call getOrganizationAction when impersonator is not present', () => {
143
- (useAuth as jest.Mock).mockReturnValue({
143
+ (useAuth as Mock).mockReturnValue({
144
144
  impersonator: null,
145
145
  user: { id: '123', email: 'user@example.com' },
146
146
  organizationId: 'org_123',
@@ -151,7 +151,7 @@ describe('Impersonation', () => {
151
151
  });
152
152
 
153
153
  it('should not call getOrganizationAction when user is not present', () => {
154
- (useAuth as jest.Mock).mockReturnValue({
154
+ (useAuth as Mock).mockReturnValue({
155
155
  impersonator: { email: 'admin@example.com' },
156
156
  user: null,
157
157
  organizationId: 'org_123',
@@ -167,10 +167,10 @@ describe('Impersonation', () => {
167
167
  name: 'Test Org',
168
168
  };
169
169
 
170
- (getOrganizationAction as jest.Mock).mockResolvedValue(mockOrg);
170
+ (getOrganizationAction as Mock).mockResolvedValue(mockOrg);
171
171
 
172
172
  const { rerender } = await act(async () => {
173
- (useAuth as jest.Mock).mockReturnValue({
173
+ (useAuth as Mock).mockReturnValue({
174
174
  impersonator: { email: 'admin@example.com' },
175
175
  user: { id: '123', email: 'user@example.com' },
176
176
  organizationId: 'org_123',
@@ -188,7 +188,7 @@ describe('Impersonation', () => {
188
188
 
189
189
  // Rerender with the same organizationId
190
190
  await act(async () => {
191
- (useAuth as jest.Mock).mockReturnValue({
191
+ (useAuth as Mock).mockReturnValue({
192
192
  impersonator: { email: 'admin@example.com' },
193
193
  user: { id: '123', email: 'user@example.com' },
194
194
  organizationId: 'org_123',
@@ -212,10 +212,10 @@ describe('Impersonation', () => {
212
212
  name: 'Test Org 2',
213
213
  };
214
214
 
215
- (getOrganizationAction as jest.Mock).mockResolvedValueOnce(mockOrg1).mockResolvedValueOnce(mockOrg2);
215
+ (getOrganizationAction as Mock).mockResolvedValueOnce(mockOrg1).mockResolvedValueOnce(mockOrg2);
216
216
 
217
217
  const { rerender } = await act(async () => {
218
- (useAuth as jest.Mock).mockReturnValue({
218
+ (useAuth as Mock).mockReturnValue({
219
219
  impersonator: { email: 'admin@example.com' },
220
220
  user: { id: '123', email: 'user@example.com' },
221
221
  organizationId: 'org_123',
@@ -234,7 +234,7 @@ describe('Impersonation', () => {
234
234
 
235
235
  // Rerender with a different organizationId
236
236
  await act(async () => {
237
- (useAuth as jest.Mock).mockReturnValue({
237
+ (useAuth as Mock).mockReturnValue({
238
238
  impersonator: { email: 'admin@example.com' },
239
239
  user: { id: '123', email: 'user@example.com' },
240
240
  organizationId: 'org_456',
@@ -14,6 +14,7 @@ describe('MinMaxButton', () => {
14
14
  afterEach(() => {
15
15
  // Clean up after each test
16
16
  document.body.innerHTML = '';
17
+ vi.restoreAllMocks();
17
18
  });
18
19
 
19
20
  it('sets minimized value when clicked', () => {
@@ -33,7 +34,7 @@ describe('MinMaxButton', () => {
33
34
  const root = document.querySelector('[data-workos-impersonation-root]');
34
35
 
35
36
  // Mock querySelector to return null for this test
36
- jest.spyOn(document, 'querySelector').mockReturnValue(null);
37
+ vi.spyOn(document, 'querySelector').mockReturnValue(null);
37
38
 
38
39
  act(() => {
39
40
  getByRole('button').click();
@@ -1,20 +1,20 @@
1
1
  import { tokenStore, TokenStore } from './tokenStore.js';
2
2
  import { getAccessTokenAction, refreshAccessTokenAction } from '../actions.js';
3
3
 
4
- jest.mock('../actions.js', () => ({
5
- getAccessTokenAction: jest.fn(),
6
- refreshAccessTokenAction: jest.fn(),
4
+ vi.mock('../actions.js', () => ({
5
+ getAccessTokenAction: vi.fn(),
6
+ refreshAccessTokenAction: vi.fn(),
7
7
  }));
8
8
 
9
- const mockGetAccessTokenAction = getAccessTokenAction as jest.Mock;
10
- const mockRefreshAccessTokenAction = refreshAccessTokenAction as jest.Mock;
9
+ const mockGetAccessTokenAction = getAccessTokenAction as Mock;
10
+ const mockRefreshAccessTokenAction = refreshAccessTokenAction as Mock;
11
11
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
12
  const _global = global as any;
13
13
 
14
14
  describe('tokenStore', () => {
15
15
  beforeEach(() => {
16
- jest.useFakeTimers();
17
- jest.resetAllMocks();
16
+ vi.useFakeTimers();
17
+ vi.resetAllMocks();
18
18
  tokenStore.reset();
19
19
 
20
20
  // Clean up DOM globals
@@ -23,10 +23,10 @@ describe('tokenStore', () => {
23
23
  });
24
24
 
25
25
  afterEach(() => {
26
- jest.clearAllTimers();
27
- jest.useRealTimers();
26
+ vi.clearAllTimers();
27
+ vi.useRealTimers();
28
28
  tokenStore.reset();
29
- jest.restoreAllMocks();
29
+ vi.restoreAllMocks();
30
30
 
31
31
  // Clean up DOM globals
32
32
  delete _global.document;
@@ -213,7 +213,7 @@ describe('tokenStore', () => {
213
213
  };
214
214
  const validToken = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.${btoa(JSON.stringify(validPayload))}.mock-signature`;
215
215
 
216
- const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
216
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout');
217
217
  mockGetAccessTokenAction.mockResolvedValue(validToken);
218
218
 
219
219
  await tokenStore.getAccessTokenSilently();
@@ -227,7 +227,7 @@ describe('tokenStore', () => {
227
227
 
228
228
  describe('subscriber management', () => {
229
229
  it('should notify subscribers when state changes', () => {
230
- const listener = jest.fn();
230
+ const listener = vi.fn();
231
231
  const unsubscribe = tokenStore.subscribe(listener);
232
232
 
233
233
  // Trigger a state change
@@ -256,14 +256,14 @@ describe('tokenStore', () => {
256
256
  mockGetAccessTokenAction.mockResolvedValue(validToken);
257
257
 
258
258
  // Subscribe to create a listener
259
- const listener = jest.fn();
259
+ const listener = vi.fn();
260
260
  const unsubscribe = tokenStore.subscribe(listener);
261
261
 
262
262
  // Get token to schedule a refresh
263
263
  await tokenStore.getAccessTokenSilently();
264
264
 
265
265
  // Spy on clearTimeout
266
- const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout');
266
+ const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
267
267
 
268
268
  // Unsubscribe the last (only) subscriber - should clear timeout
269
269
  unsubscribe();
@@ -354,7 +354,7 @@ describe('tokenStore', () => {
354
354
 
355
355
  it('should consume eager auth cookie on first getAccessToken call', async () => {
356
356
  const eagerToken = 'eager-auth-token';
357
- const mockCookieSetter = jest.fn();
357
+ const mockCookieSetter = vi.fn();
358
358
 
359
359
  // Mock document.cookie with both getter and setter
360
360
  let cookieValue = `workos-access-token=${eagerToken};`;
@@ -404,7 +404,7 @@ describe('tokenStore', () => {
404
404
  iat: now - 40,
405
405
  };
406
406
  const fastToken = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.${btoa(JSON.stringify(fastPayload))}.mock-signature`;
407
- const mockCookieSetter = jest.fn();
407
+ const mockCookieSetter = vi.fn();
408
408
 
409
409
  let cookieValue = `workos-access-token=${fastToken};`;
410
410
 
@@ -433,7 +433,7 @@ describe('tokenStore', () => {
433
433
  configurable: true,
434
434
  });
435
435
 
436
- const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
436
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout');
437
437
 
438
438
  // Call getAccessTokenSilently to trigger fast cookie consumption and refresh scheduling
439
439
  const token = await tokenStore.getAccessTokenSilently();
@@ -480,7 +480,7 @@ describe('tokenStore', () => {
480
480
 
481
481
  it('should handle HTTP protocol for cookie deletion', async () => {
482
482
  const eagerToken = 'http-token';
483
- const mockCookieSetter = jest.fn();
483
+ const mockCookieSetter = vi.fn();
484
484
 
485
485
  let cookieValue = `workos-access-token=${eagerToken};`;
486
486
 
@@ -624,7 +624,7 @@ describe('tokenStore', () => {
624
624
  await tokenStore.getAccessTokenSilently();
625
625
 
626
626
  // Spy on clearTimeout
627
- const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout');
627
+ const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
628
628
 
629
629
  // Clear token should clear the refresh timeout
630
630
  tokenStore.clearToken();
@@ -738,7 +738,7 @@ describe('tokenStore', () => {
738
738
  mockGetAccessTokenAction.mockClear();
739
739
  mockRefreshAccessTokenAction.mockResolvedValue(existingToken); // Same token
740
740
 
741
- const listener = jest.fn();
741
+ const listener = vi.fn();
742
742
  tokenStore.subscribe(listener);
743
743
 
744
744
  // Force a silent refresh that returns the same token
@@ -752,7 +752,7 @@ describe('tokenStore', () => {
752
752
 
753
753
  describe('TokenStore constructor', () => {
754
754
  const setupMockEnv = (cookieValue = '', protocol = 'https:') => {
755
- const mockCookieSetter = jest.fn();
755
+ const mockCookieSetter = vi.fn();
756
756
 
757
757
  Object.defineProperty(_global, 'document', {
758
758
  value: { cookie: cookieValue },