@crossmint/client-sdk-react-ui 1.3.24 → 1.4.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.
@@ -18,6 +18,9 @@ class MockSDK {
18
18
  somethingThatUpdatesJWT(newJWT: string) {
19
19
  this.crossmint.jwt = newJWT;
20
20
  }
21
+ somethingThatUpdatesRefreshToken(newRefreshToken: string) {
22
+ this.crossmint.refreshToken = newRefreshToken;
23
+ }
21
24
  }
22
25
 
23
26
  function renderCrossmintProvider({ children }: { children: JSX.Element }) {
@@ -30,16 +33,23 @@ describe("CrossmintProvider", () => {
30
33
  vi.mocked(createCrossmint).mockImplementation(() => ({
31
34
  apiKey: MOCK_API_KEY,
32
35
  jwt: "",
36
+ refreshToken: "",
33
37
  }));
34
38
  });
35
39
 
36
- it("provides initial JWT value", () => {
40
+ it("provides initial JWT and refreshToken values", () => {
37
41
  const TestComponent = () => {
38
42
  const { crossmint } = useCrossmint();
39
- return <div data-testid="jwt">{crossmint.jwt}</div>;
43
+ return (
44
+ <div>
45
+ <div data-testid="jwt">{crossmint.jwt}</div>
46
+ <div data-testid="refreshToken">{crossmint.refreshToken}</div>
47
+ </div>
48
+ );
40
49
  };
41
50
  const { getByTestId } = renderCrossmintProvider({ children: <TestComponent /> });
42
51
  expect(getByTestId("jwt").textContent).toBe("");
52
+ expect(getByTestId("refreshToken").textContent).toBe("");
43
53
  });
44
54
 
45
55
  it("updates JWT using setJwt", () => {
@@ -57,30 +67,54 @@ describe("CrossmintProvider", () => {
57
67
  expect(getByTestId("jwt").textContent).toBe("new_jwt");
58
68
  });
59
69
 
60
- it("updates JWT using WalletSDK", () => {
70
+ it("updates refreshToken using setRefreshToken", () => {
71
+ const TestComponent = () => {
72
+ const { crossmint, setRefreshToken } = useCrossmint();
73
+ return (
74
+ <div>
75
+ <div data-testid="refreshToken">{crossmint.refreshToken}</div>
76
+ <button onClick={() => setRefreshToken("new_refresh_token")}>Update Refresh Token</button>
77
+ </div>
78
+ );
79
+ };
80
+ const { getByTestId, getByText } = renderCrossmintProvider({ children: <TestComponent /> });
81
+ fireEvent.click(getByText("Update Refresh Token"));
82
+ expect(getByTestId("refreshToken").textContent).toBe("new_refresh_token");
83
+ });
84
+
85
+ it("updates JWT and refreshToken using WalletSDK", () => {
61
86
  const TestComponent = () => {
62
87
  const { crossmint } = useCrossmint();
63
88
  useEffect(() => {
64
89
  const wallet = new MockSDK(crossmint);
65
90
  wallet.somethingThatUpdatesJWT("sdk_jwt");
91
+ wallet.somethingThatUpdatesRefreshToken("sdk_refresh_token");
66
92
  }, []);
67
- return <div data-testid="jwt">{crossmint.jwt}</div>;
93
+ return (
94
+ <div>
95
+ <div data-testid="jwt">{crossmint.jwt}</div>
96
+ <div data-testid="refreshToken">{crossmint.refreshToken}</div>
97
+ </div>
98
+ );
68
99
  };
69
100
  const { getByTestId } = renderCrossmintProvider({ children: <TestComponent /> });
70
101
  expect(getByTestId("jwt").textContent).toBe("sdk_jwt");
102
+ expect(getByTestId("refreshToken").textContent).toBe("sdk_refresh_token");
71
103
  });
72
104
 
73
- it("triggers re-render on JWT change", () => {
105
+ it("triggers re-render on JWT and refreshToken change", () => {
74
106
  const renderCount = vi.fn();
75
107
  const TestComponent = () => {
76
- const { crossmint, setJwt } = useCrossmint();
108
+ const { crossmint, setJwt, setRefreshToken } = useCrossmint();
77
109
  useEffect(() => {
78
110
  renderCount();
79
111
  });
80
112
  return (
81
113
  <div>
82
114
  <div data-testid="jwt">{crossmint.jwt}</div>
115
+ <div data-testid="refreshToken">{crossmint.refreshToken}</div>
83
116
  <button onClick={() => setJwt("new_jwt")}>Update JWT</button>
117
+ <button onClick={() => setRefreshToken("new_refresh_token")}>Update Refresh Token</button>
84
118
  </div>
85
119
  );
86
120
  };
@@ -90,7 +124,9 @@ describe("CrossmintProvider", () => {
90
124
  expect(renderCount).toHaveBeenCalledTimes(1);
91
125
 
92
126
  fireEvent.click(getByText("Update JWT"));
93
-
94
127
  expect(renderCount).toHaveBeenCalledTimes(2);
128
+
129
+ fireEvent.click(getByText("Update Refresh Token"));
130
+ expect(renderCount).toHaveBeenCalledTimes(3);
95
131
  });
96
132
  });
@@ -5,6 +5,7 @@ import { type Crossmint, createCrossmint } from "@crossmint/common-sdk-base";
5
5
  export interface CrossmintContext {
6
6
  crossmint: Crossmint;
7
7
  setJwt: (jwt: string | undefined) => void;
8
+ setRefreshToken: (refreshToken: string | undefined) => void;
8
9
  }
9
10
 
10
11
  const CrossmintContext = createContext<CrossmintContext | null>(null);
@@ -24,6 +25,9 @@ export function CrossmintProvider({
24
25
  if (prop === "jwt" && target.jwt !== value) {
25
26
  setVersion((v) => v + 1);
26
27
  }
28
+ if (prop === "refreshToken" && target.refreshToken !== value) {
29
+ setVersion((v) => v + 1);
30
+ }
27
31
  return Reflect.set(target, prop, value);
28
32
  },
29
33
  })
@@ -35,14 +39,21 @@ export function CrossmintProvider({
35
39
  }
36
40
  }, []);
37
41
 
42
+ const setRefreshToken = useCallback((refreshToken: string | undefined) => {
43
+ if (refreshToken !== crossmintRef.current.refreshToken) {
44
+ crossmintRef.current.refreshToken = refreshToken;
45
+ }
46
+ }, []);
47
+
38
48
  const value = useMemo(
39
49
  () => ({
40
50
  get crossmint() {
41
51
  return crossmintRef.current;
42
52
  },
43
53
  setJwt,
54
+ setRefreshToken,
44
55
  }),
45
- [setJwt, version]
56
+ [setJwt, setRefreshToken, version]
46
57
  );
47
58
 
48
59
  return <CrossmintContext.Provider value={value}>{children}</CrossmintContext.Provider>;
@@ -0,0 +1,142 @@
1
+ import { act, renderHook } from "@testing-library/react";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ import { type CrossmintAuthService, getJWTExpiration } from "@crossmint/client-sdk-auth-core/client";
5
+ import { queueTask } from "@crossmint/client-sdk-base";
6
+
7
+ import * as authCookies from "../utils/authCookies";
8
+ import { type AuthMaterial, useRefreshToken } from "./useRefreshToken";
9
+
10
+ vi.mock("@crossmint/client-sdk-auth-core", () => ({
11
+ CrossmintAuthService: vi.fn(),
12
+ getJWTExpiration: vi.fn(),
13
+ }));
14
+
15
+ vi.mock("../utils/authCookies", () => ({
16
+ getCookie: vi.fn(),
17
+ REFRESH_TOKEN_PREFIX: "crossmint-refresh-token",
18
+ }));
19
+
20
+ vi.mock("@crossmint/client-sdk-base", () => ({
21
+ queueTask: vi.fn(),
22
+ }));
23
+
24
+ describe("useRefreshToken", () => {
25
+ const mockCrossmintAuthService = {
26
+ refreshAuthMaterial: vi.fn(),
27
+ } as unknown as CrossmintAuthService;
28
+
29
+ const mockSetAuthMaterial = vi.fn();
30
+
31
+ beforeEach(() => {
32
+ vi.useFakeTimers();
33
+ vi.spyOn(console, "error").mockImplementation(() => {});
34
+ });
35
+
36
+ afterEach(() => {
37
+ vi.restoreAllMocks();
38
+ vi.useRealTimers();
39
+ });
40
+
41
+ it("should not refresh token if refresh token is not present", async () => {
42
+ vi.mocked(authCookies.getCookie).mockReturnValue(undefined);
43
+
44
+ renderHook(() =>
45
+ useRefreshToken({
46
+ crossmintAuthService: mockCrossmintAuthService,
47
+ setAuthMaterial: mockSetAuthMaterial,
48
+ logout: vi.fn(),
49
+ })
50
+ );
51
+
52
+ await act(async () => {
53
+ await vi.runAllTimersAsync();
54
+ });
55
+
56
+ expect(mockCrossmintAuthService.refreshAuthMaterial).not.toHaveBeenCalled();
57
+ expect(mockSetAuthMaterial).not.toHaveBeenCalled();
58
+ });
59
+
60
+ it("should refresh token if refresh token is present", async () => {
61
+ const mockRefreshToken = "mock-refresh-token";
62
+ const mockAuthMaterial: AuthMaterial = {
63
+ jwtToken: "mock-jwt-token",
64
+ refreshToken: {
65
+ secret: "mock-secret",
66
+ expiresAt: "2023-04-01T00:00:00Z",
67
+ },
68
+ };
69
+
70
+ vi.mocked(authCookies.getCookie).mockReturnValue(mockRefreshToken);
71
+ vi.mocked(mockCrossmintAuthService.refreshAuthMaterial).mockResolvedValue(mockAuthMaterial);
72
+ vi.mocked(getJWTExpiration).mockReturnValue(Date.now() / 1000 + 3600); // 1 hour from now
73
+
74
+ renderHook(() =>
75
+ useRefreshToken({
76
+ crossmintAuthService: mockCrossmintAuthService,
77
+ setAuthMaterial: mockSetAuthMaterial,
78
+ logout: vi.fn(),
79
+ })
80
+ );
81
+
82
+ await act(async () => {
83
+ await vi.runAllTimersAsync();
84
+ });
85
+
86
+ expect(mockCrossmintAuthService.refreshAuthMaterial).toHaveBeenCalledWith(mockRefreshToken);
87
+ expect(mockSetAuthMaterial).toHaveBeenCalledWith(mockAuthMaterial);
88
+ });
89
+
90
+ it("should schedule next refresh before token expiration", async () => {
91
+ const mockRefreshToken = "mock-refresh-token";
92
+ const mockAuthMaterial: AuthMaterial = {
93
+ jwtToken: "mock-jwt-token",
94
+ refreshToken: {
95
+ secret: "mock-secret",
96
+ expiresAt: "2023-04-01T00:00:00Z",
97
+ },
98
+ };
99
+
100
+ vi.mocked(authCookies.getCookie).mockReturnValue(mockRefreshToken);
101
+ vi.mocked(mockCrossmintAuthService.refreshAuthMaterial).mockResolvedValue(mockAuthMaterial);
102
+ vi.mocked(getJWTExpiration).mockReturnValue(Date.now() / 1000 + 3600); // 1 hour from now
103
+
104
+ renderHook(() =>
105
+ useRefreshToken({
106
+ crossmintAuthService: mockCrossmintAuthService,
107
+ setAuthMaterial: mockSetAuthMaterial,
108
+ logout: vi.fn(),
109
+ })
110
+ );
111
+
112
+ await act(async () => {
113
+ await vi.runAllTimersAsync();
114
+ });
115
+
116
+ expect(vi.mocked(queueTask)).toHaveBeenCalledTimes(1);
117
+ expect(vi.mocked(queueTask)).toHaveBeenCalledWith(expect.any(Function), expect.any(Number));
118
+ });
119
+
120
+ it("should handle errors during token refresh", async () => {
121
+ const mockRefreshToken = "mock-refresh-token";
122
+ const mockError = new Error("Refresh failed");
123
+
124
+ vi.mocked(authCookies.getCookie).mockReturnValue(mockRefreshToken);
125
+ vi.mocked(mockCrossmintAuthService.refreshAuthMaterial).mockRejectedValue(mockError);
126
+
127
+ renderHook(() =>
128
+ useRefreshToken({
129
+ crossmintAuthService: mockCrossmintAuthService,
130
+ setAuthMaterial: mockSetAuthMaterial,
131
+ logout: vi.fn(),
132
+ })
133
+ );
134
+
135
+ await act(async () => {
136
+ await vi.runAllTimersAsync();
137
+ });
138
+
139
+ expect(console.error).toHaveBeenCalledWith(mockError);
140
+ expect(mockSetAuthMaterial).not.toHaveBeenCalled();
141
+ });
142
+ });
@@ -0,0 +1,74 @@
1
+ import { useCallback, useEffect, useRef } from "react";
2
+
3
+ import type { CrossmintAuthService } from "@crossmint/client-sdk-auth-core/client";
4
+ import { getJWTExpiration } from "@crossmint/client-sdk-auth-core/client";
5
+ import { queueTask, type CancellableTask } from "@crossmint/client-sdk-base";
6
+
7
+ import { REFRESH_TOKEN_PREFIX, getCookie } from "../utils/authCookies";
8
+
9
+ // 2 minutes before jwt expiration
10
+ const TIME_BEFORE_EXPIRING_JWT_IN_SECONDS = 120;
11
+
12
+ export type AuthMaterial = {
13
+ jwtToken: string;
14
+ refreshToken: {
15
+ secret: string;
16
+ expiresAt: string;
17
+ };
18
+ };
19
+
20
+ type UseAuthTokenRefreshProps = {
21
+ crossmintAuthService: CrossmintAuthService;
22
+ setAuthMaterial: (authMaterial: AuthMaterial) => void;
23
+ logout: () => void;
24
+ };
25
+
26
+ // Makes sure that everything inside the async IIFE has finished running before it can be called again.
27
+ // The actual promise just holds that IIFE until it has finished running and it's then set to null
28
+ let refreshPromise: Promise<void> | null = null;
29
+
30
+ export function useRefreshToken({ crossmintAuthService, setAuthMaterial, logout }: UseAuthTokenRefreshProps) {
31
+ const refreshTaskRef = useRef<CancellableTask | null>(null);
32
+
33
+ const refreshAuthMaterial = useCallback(() => {
34
+ if (refreshPromise != null) {
35
+ return refreshPromise;
36
+ }
37
+
38
+ const refreshToken = getCookie(REFRESH_TOKEN_PREFIX);
39
+ if (refreshToken != null) {
40
+ refreshPromise = (async () => {
41
+ try {
42
+ const result = await crossmintAuthService.refreshAuthMaterial(refreshToken);
43
+ setAuthMaterial(result);
44
+ const jwtExpiration = getJWTExpiration(result.jwtToken);
45
+
46
+ if (jwtExpiration == null) {
47
+ throw new Error("Invalid JWT");
48
+ }
49
+
50
+ const currentTime = Date.now() / 1000;
51
+ const timeToExpire = jwtExpiration - currentTime - TIME_BEFORE_EXPIRING_JWT_IN_SECONDS;
52
+ if (timeToExpire > 0) {
53
+ const endTime = Date.now() + timeToExpire * 1000;
54
+ refreshTaskRef.current = queueTask(refreshAuthMaterial, endTime);
55
+ }
56
+ } catch (error) {
57
+ logout();
58
+ console.error(error);
59
+ } finally {
60
+ refreshPromise = null;
61
+ }
62
+ })();
63
+ }
64
+ }, []);
65
+
66
+ useEffect(() => {
67
+ refreshAuthMaterial();
68
+ return () => {
69
+ if (refreshTaskRef.current) {
70
+ refreshTaskRef.current.cancel();
71
+ }
72
+ };
73
+ }, []);
74
+ }
package/src/index.ts CHANGED
@@ -13,7 +13,6 @@ export * from "./providers";
13
13
  export { CrossmintEvents, useCrossmintEvents } from "@crossmint/client-sdk-base";
14
14
  export {
15
15
  type EVMSmartWallet,
16
- type ExternalSigner,
17
16
  type PasskeySigner,
18
17
  Chain,
19
18
  SmartWalletError,
@@ -1,8 +1,10 @@
1
+ import { deleteCookie, REFRESH_TOKEN_PREFIX, SESSION_PREFIX } from "@/utils/authCookies";
1
2
  import { fireEvent, render } from "@testing-library/react";
2
- import type { ReactNode } from "react";
3
+ import { type ReactNode, act } from "react";
3
4
  import { beforeEach, describe, expect, vi } from "vitest";
4
5
  import { mock } from "vitest-mock-extended";
5
6
 
7
+ import { CrossmintAuthService, getJWTExpiration } from "@crossmint/client-sdk-auth-core/client";
6
8
  import { type EVMSmartWallet, SmartWalletSDK } from "@crossmint/client-sdk-smart-wallet";
7
9
  import { createCrossmint } from "@crossmint/common-sdk-base";
8
10
 
@@ -11,8 +13,6 @@ import { CrossmintProvider, useCrossmint } from "../hooks/useCrossmint";
11
13
  import { MOCK_API_KEY, waitForSettledState } from "../testUtils";
12
14
  import { CrossmintAuthProvider, type CrossmintAuthWalletConfig } from "./CrossmintAuthProvider";
13
15
 
14
- const SESSION_PREFIX = "crossmint-session";
15
-
16
16
  vi.mock("@crossmint/client-sdk-smart-wallet", async () => {
17
17
  const actual = await vi.importActual("@crossmint/client-sdk-smart-wallet");
18
18
  return {
@@ -32,6 +32,23 @@ vi.mock("@crossmint/common-sdk-base", async () => {
32
32
  };
33
33
  });
34
34
 
35
+ vi.mock("@crossmint/client-sdk-auth-core/client", async () => {
36
+ const actual = await vi.importActual("@crossmint/client-sdk-auth-core/client");
37
+ return {
38
+ ...actual,
39
+ getJWTExpiration: vi.fn(),
40
+ CrossmintAuthService: vi.fn().mockImplementation(() => ({
41
+ refreshAuthMaterial: vi.fn().mockResolvedValue({
42
+ jwtToken: "new-mock-jwt",
43
+ refreshToken: {
44
+ secret: "new-mock-refresh-token",
45
+ expiresAt: new Date(Date.now() + 1000 * 60 * 60).toISOString(),
46
+ },
47
+ }),
48
+ })),
49
+ };
50
+ });
51
+
35
52
  function renderAuthProvider({
36
53
  children,
37
54
  embeddedWallets,
@@ -47,23 +64,30 @@ function renderAuthProvider({
47
64
  }
48
65
 
49
66
  function TestComponent() {
50
- const { setJwt } = useCrossmint();
67
+ const { setJwt, setRefreshToken } = useCrossmint();
51
68
  const { wallet, status: walletStatus, error } = useWallet();
52
- const { status: authStatus } = useAuth();
53
-
69
+ const { status: authStatus, refreshToken } = useAuth();
54
70
  return (
55
71
  <div>
56
72
  <div data-testid="error">{error?.message ?? "No Error"}</div>
57
73
  <div data-testid="wallet-status">{walletStatus}</div>
58
74
  <div data-testid="auth-status">{authStatus}</div>
59
75
  <div data-testid="wallet">{wallet ? "Wallet Loaded" : "No Wallet"}</div>
76
+
60
77
  <button data-testid="jwt-input" onClick={() => setJwt("mock-jwt")}>
61
78
  Set JWT
62
79
  </button>
63
-
64
80
  <button data-testid="clear-jwt-button" onClick={() => setJwt(undefined)}>
65
81
  Clear JWT
66
82
  </button>
83
+
84
+ <div data-testid="refresh-token">{refreshToken ?? "No Refresh Token"}</div>
85
+ <button data-testid="set-refresh-token" onClick={() => setRefreshToken("mock-refresh-token")}>
86
+ Set Refresh Token
87
+ </button>
88
+ <button data-testid="clear-refresh-token-button" onClick={() => setRefreshToken(undefined)}>
89
+ Clear Refresh Token
90
+ </button>
67
91
  </div>
68
92
  );
69
93
  }
@@ -72,6 +96,7 @@ describe("CrossmintAuthProvider", () => {
72
96
  let mockSDK: SmartWalletSDK;
73
97
  let mockWallet: EVMSmartWallet;
74
98
  let embeddedWallets: CrossmintAuthWalletConfig;
99
+ let mockCrossmintAuthService: { refreshAuthMaterial: ReturnType<typeof vi.fn> };
75
100
 
76
101
  beforeEach(() => {
77
102
  vi.resetAllMocks();
@@ -81,6 +106,7 @@ describe("CrossmintAuthProvider", () => {
81
106
  mockWallet = mock<EVMSmartWallet>();
82
107
  vi.mocked(SmartWalletSDK.init).mockReturnValue(mockSDK);
83
108
  vi.mocked(mockSDK.getOrCreateWallet).mockResolvedValue(mockWallet);
109
+ vi.mocked(getJWTExpiration).mockReturnValue(1000);
84
110
 
85
111
  embeddedWallets = {
86
112
  defaultChain: "polygon",
@@ -88,14 +114,27 @@ describe("CrossmintAuthProvider", () => {
88
114
  type: "evm-smart-wallet",
89
115
  };
90
116
 
91
- Object.defineProperty(document, "cookie", {
92
- writable: true,
93
- value: "",
94
- });
117
+ deleteCookie(REFRESH_TOKEN_PREFIX);
118
+ deleteCookie(SESSION_PREFIX);
119
+
120
+ mockCrossmintAuthService = {
121
+ refreshAuthMaterial: vi.fn().mockResolvedValue({
122
+ jwtToken: "new-mock-jwt",
123
+ refreshToken: {
124
+ secret: "new-mock-refresh-token",
125
+ expiresAt: new Date(Date.now() + 1000 * 60 * 60).toISOString(),
126
+ },
127
+ }),
128
+ };
129
+ vi.mocked(CrossmintAuthService).mockImplementation(() => mockCrossmintAuthService as any);
95
130
  });
96
131
 
97
132
  test("Happy path", async () => {
98
- document.cookie = `${SESSION_PREFIX}=mock-jwt; path=/;SameSite=Lax;`;
133
+ await act(() => {
134
+ document.cookie = `${REFRESH_TOKEN_PREFIX}=mock-refresh-token; path=/; SameSite=Lax;`;
135
+ document.cookie = `${SESSION_PREFIX}=mock-jwt; path=/; SameSite=Lax;`;
136
+ });
137
+
99
138
  const { getByTestId } = renderAuthProvider({
100
139
  children: <TestComponent />,
101
140
  embeddedWallets,
@@ -103,16 +142,19 @@ describe("CrossmintAuthProvider", () => {
103
142
 
104
143
  expect(getByTestId("wallet-status").textContent).toBe("in-progress");
105
144
  expect(getByTestId("auth-status").textContent).toBe("logged-in");
145
+ expect(getByTestId("refresh-token").textContent).toBe("No Refresh Token");
106
146
  expect(getByTestId("wallet").textContent).toBe("No Wallet");
107
147
  expect(getByTestId("error").textContent).toBe("No Error");
108
148
 
109
149
  await waitForSettledState(() => {
110
150
  expect(getByTestId("wallet-status").textContent).toBe("loaded");
111
151
  expect(getByTestId("auth-status").textContent).toBe("logged-in");
152
+ expect(getByTestId("refresh-token").textContent).toBe("new-mock-refresh-token");
112
153
  expect(getByTestId("wallet").textContent).toBe("Wallet Loaded");
113
154
  expect(getByTestId("error").textContent).toBe("No Error");
114
155
  });
115
156
 
157
+ expect(mockCrossmintAuthService.refreshAuthMaterial).toHaveBeenCalledOnce();
116
158
  expect(vi.mocked(mockSDK.getOrCreateWallet)).toHaveBeenCalledOnce();
117
159
  });
118
160
 
@@ -190,4 +232,52 @@ describe("CrossmintAuthProvider", () => {
190
232
  expect(getByTestId("auth-status").textContent).toBe("logged-in");
191
233
  });
192
234
  });
235
+
236
+ test("Setting and clearing refresh token", async () => {
237
+ const { getByTestId } = renderAuthProvider({
238
+ children: <TestComponent />,
239
+ embeddedWallets,
240
+ });
241
+
242
+ expect(getByTestId("refresh-token").textContent).toBe("No Refresh Token");
243
+
244
+ fireEvent.click(getByTestId("set-refresh-token"));
245
+
246
+ await waitForSettledState(() => {
247
+ expect(getByTestId("refresh-token").textContent).toBe("mock-refresh-token");
248
+ });
249
+
250
+ fireEvent.click(getByTestId("clear-refresh-token-button"));
251
+
252
+ await waitForSettledState(() => {
253
+ expect(getByTestId("refresh-token").textContent).toBe("No Refresh Token");
254
+ });
255
+ });
256
+
257
+ test("Logout clears both JWT and refresh token", async () => {
258
+ await act(() => {
259
+ document.cookie = `${REFRESH_TOKEN_PREFIX}=mock-refresh-token; path=/; SameSite=Lax;`;
260
+ document.cookie = `${SESSION_PREFIX}=mock-jwt; path=/; SameSite=Lax;`;
261
+ });
262
+
263
+ const { getByTestId } = renderAuthProvider({
264
+ children: <TestComponent />,
265
+ embeddedWallets,
266
+ });
267
+
268
+ await waitForSettledState(() => {
269
+ expect(getByTestId("auth-status").textContent).toBe("logged-in");
270
+ expect(getByTestId("refresh-token").textContent).toBe("new-mock-refresh-token");
271
+ });
272
+
273
+ await act(() => {
274
+ fireEvent.click(getByTestId("clear-jwt-button"));
275
+ fireEvent.click(getByTestId("clear-refresh-token-button"));
276
+ });
277
+
278
+ await waitForSettledState(() => {
279
+ expect(getByTestId("auth-status").textContent).toBe("logged-out");
280
+ expect(getByTestId("refresh-token").textContent).toBe("No Refresh Token");
281
+ });
282
+ });
193
283
  });