Skip to content

Commit b059722

Browse files
nicknisiPaulAsjes
andauthored
Add client component signout method (#183)
* Add signOut for client component * fix act warnings in new signout tests * add a test for calling the stop impersonating button --------- Co-authored-by: Paul Asjes <p.asjes@gmail.com>
1 parent 332d405 commit b059722

File tree

6 files changed

+92
-12
lines changed

6 files changed

+92
-12
lines changed

__tests__/authkit-provider.spec.tsx

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import React from 'react';
22
import { render, waitFor, act } from '@testing-library/react';
33
import '@testing-library/jest-dom';
44
import { AuthKitProvider, useAuth } from '../src/components/authkit-provider.js';
5-
import { checkSessionAction, getAuthAction, refreshAuthAction } from '../src/actions.js';
5+
import { checkSessionAction, getAuthAction, refreshAuthAction, handleSignOutAction } from '../src/actions.js';
66

77
jest.mock('../src/actions', () => ({
88
checkSessionAction: jest.fn(),
99
getAuthAction: jest.fn(),
1010
refreshAuthAction: jest.fn(),
11+
handleSignOutAction: jest.fn(),
1112
}));
1213

1314
describe('AuthKitProvider', () => {
@@ -331,7 +332,7 @@ describe('useAuth', () => {
331332
});
332333
});
333334

334-
it('should receive an error when refreshAuth fails with an errstringor', async () => {
335+
it('should receive an error when refreshAuth fails with a string error', async () => {
335336
(refreshAuthAction as jest.Mock).mockRejectedValueOnce('Refresh failed');
336337

337338
let error: string | undefined;
@@ -367,4 +368,56 @@ describe('useAuth', () => {
367368
expect(error).toBe('Refresh failed');
368369
});
369370
});
371+
372+
it('should call handleSignOutAction when signOut is called', async () => {
373+
(handleSignOutAction as jest.Mock).mockResolvedValueOnce({});
374+
375+
const TestComponent = () => {
376+
const auth = useAuth();
377+
return (
378+
<div>
379+
<div data-testid="session">{auth.sessionId}</div>
380+
<button onClick={() => auth.signOut()}>Sign out</button>
381+
</div>
382+
);
383+
};
384+
385+
const { getByRole } = render(
386+
<AuthKitProvider>
387+
<TestComponent />
388+
</AuthKitProvider>,
389+
);
390+
391+
await act(async () => {
392+
getByRole('button').click();
393+
});
394+
395+
expect(handleSignOutAction).toHaveBeenCalled();
396+
});
397+
398+
it('should pass returnTo parameter to handleSignOutAction', async () => {
399+
(handleSignOutAction as jest.Mock).mockResolvedValueOnce({});
400+
401+
const TestComponent = () => {
402+
const auth = useAuth();
403+
return (
404+
<div>
405+
<div data-testid="session">{auth.sessionId}</div>
406+
<button onClick={() => auth.signOut({ returnTo: '/home' })}>Sign out</button>
407+
</div>
408+
);
409+
};
410+
411+
const { getByRole } = render(
412+
<AuthKitProvider>
413+
<TestComponent />
414+
</AuthKitProvider>,
415+
);
416+
417+
await act(async () => {
418+
getByRole('button').click();
419+
});
420+
421+
expect(handleSignOutAction).toHaveBeenCalledWith({ returnTo: '/home' });
422+
});
370423
});

__tests__/impersonation.spec.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { render, act } from '@testing-library/react';
1+
import { render, act, screen } from '@testing-library/react';
22
import '@testing-library/jest-dom';
33
import { Impersonation } from '../src/components/impersonation.js';
44
import { useAuth } from '../src/components/authkit-provider.js';
55
import { getOrganizationAction } from '../src/actions.js';
66
import * as React from 'react';
7+
import { handleSignOutAction } from '../src/actions.js';
78

89
// Mock the useAuth hook
910
jest.mock('../src/components/authkit-provider', () => ({
@@ -116,4 +117,18 @@ describe('Impersonation', () => {
116117
const root = container.querySelector('[data-workos-impersonation-root]');
117118
expect(root).toHaveStyle({ backgroundColor: 'red' });
118119
});
120+
121+
it('should should sign out when the Stop button is called', async () => {
122+
(useAuth as jest.Mock).mockReturnValue({
123+
impersonator: { email: 'admin@example.com' },
124+
user: { id: '123', email: 'user@example.com' },
125+
organizationId: null,
126+
loading: false,
127+
});
128+
129+
render(<Impersonation />);
130+
const stopButton = await screen.findByText('Stop');
131+
stopButton.click();
132+
expect(handleSignOutAction).toHaveBeenCalled();
133+
});
119134
});

__tests__/min-max-button.spec.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { render, fireEvent } from '@testing-library/react';
1+
import { render, act } from '@testing-library/react';
22
import { MinMaxButton } from '../src/components/min-max-button.js';
33
import * as React from 'react';
44
import '@testing-library/jest-dom';
@@ -19,8 +19,9 @@ describe('MinMaxButton', () => {
1919
it('sets minimized value when clicked', () => {
2020
const { getByRole } = render(<MinMaxButton minimizedValue="1">Minimize</MinMaxButton>);
2121

22-
const button = getByRole('button');
23-
fireEvent.click(button);
22+
act(() => {
23+
getByRole('button').click();
24+
});
2425

2526
const root = document.querySelector('[data-workos-impersonation-root]');
2627
expect(root).toHaveStyle({ '--wi-minimized': '1' });
@@ -34,8 +35,10 @@ describe('MinMaxButton', () => {
3435
// Mock querySelector to return null for this test
3536
jest.spyOn(document, 'querySelector').mockReturnValue(null);
3637

37-
const button = getByRole('button');
38-
fireEvent.click(button);
38+
act(() => {
39+
getByRole('button').click();
40+
});
41+
3942
expect(root).not.toHaveStyle({ '--wi-minimized': '1' });
4043
});
4144

src/actions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ export const checkSessionAction = async () => {
1313
return true;
1414
};
1515

16-
export const handleSignOutAction = async () => {
17-
await signOut();
16+
export const handleSignOutAction = async ({ returnTo }: { returnTo?: string } = {}) => {
17+
await signOut({ returnTo });
1818
};
1919

2020
export const getOrganizationAction = async (organizationId: string) => {

src/components/authkit-provider.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import React, { createContext, ReactNode, useContext, useEffect, useState } from 'react';
4-
import { checkSessionAction, getAuthAction, refreshAuthAction } from '../actions.js';
4+
import { checkSessionAction, getAuthAction, handleSignOutAction, refreshAuthAction } from '../actions.js';
55
import type { Impersonator, User } from '@workos-inc/node';
66

77
type AuthContextType = {
@@ -16,6 +16,7 @@ type AuthContextType = {
1616
loading: boolean;
1717
getAuth: (options?: { ensureSignedIn?: boolean }) => Promise<void>;
1818
refreshAuth: (options?: { ensureSignedIn?: boolean; organizationId?: string }) => Promise<void | { error: string }>;
19+
signOut: (options?: { returnTo?: string }) => Promise<void>;
1920
};
2021

2122
const AuthContext = createContext<AuthContextType | undefined>(undefined);
@@ -88,6 +89,10 @@ export const AuthKitProvider = ({ children, onSessionExpired }: AuthKitProviderP
8889
}
8990
};
9091

92+
const signOut = async ({ returnTo }: { returnTo?: string } = {}) => {
93+
await handleSignOutAction({ returnTo });
94+
};
95+
9196
useEffect(() => {
9297
getAuth();
9398

@@ -153,6 +158,7 @@ export const AuthKitProvider = ({ children, onSessionExpired }: AuthKitProviderP
153158
loading,
154159
getAuth,
155160
refreshAuth,
161+
signOut,
156162
}}
157163
>
158164
{children}

src/components/impersonation.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ export function Impersonation({ side = 'bottom', ...props }: ImpersonationProps)
7676
}}
7777
>
7878
<form
79-
onSubmit={handleSignOutAction}
79+
onSubmit={async (event) => {
80+
event.preventDefault();
81+
await handleSignOutAction();
82+
}}
8083
style={{
8184
display: 'flex',
8285
alignItems: 'baseline',

0 commit comments

Comments
 (0)