Skip to content

Commit 9ac22ff

Browse files
committed
refactor(ui): call __internal_useUserEnterpriseConnections directly in Select Provider step
The Select Provider step now calls __internal_useUserEnterpriseConnections, useUser, and useSession directly, wrapping the create logic with useReverification + useCallback inline. handleContinue's behaviour is unchanged from the caller's perspective. Test mocks updated to stub the underlying hook instead of an injected createConnection.
1 parent 4cdc54f commit 9ac22ff

2 files changed

Lines changed: 83 additions & 23 deletions

File tree

packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { iconImageUrl } from '@clerk/shared/constants';
2+
import { __internal_useUserEnterpriseConnections, useReverification, useSession, useUser } from '@clerk/shared/react';
23
import React from 'react';
34

45
import type { LocalizationKey } from '@/customizables';
@@ -45,9 +46,35 @@ const PROVIDER_GROUPS: ReadonlyArray<{
4546
export const SelectProviderStep = (): JSX.Element => {
4647
const card = useCardState();
4748
const { goNext } = useWizard();
48-
const { setProvider, createConnection } = useConfigureSSO();
49+
const { setProvider, enterpriseConnection } = useConfigureSSO();
50+
const { user } = useUser();
51+
const { session } = useSession();
52+
const { createEnterpriseConnection } = __internal_useUserEnterpriseConnections();
4953
const [selected, setSelected] = React.useState<ProviderType | null>(null);
5054

55+
const createConnection = useReverification(
56+
React.useCallback(
57+
async (selectedProvider: ProviderType) => {
58+
if (enterpriseConnection) {
59+
return;
60+
}
61+
if (!user?.primaryEmailAddress) {
62+
throw new Error('Primary email required');
63+
}
64+
65+
const emailDomain = user.primaryEmailAddress.emailAddress.split('@')[1];
66+
const organizationId = session?.lastActiveOrganizationId ?? null;
67+
68+
await createEnterpriseConnection({
69+
provider: selectedProvider,
70+
name: emailDomain,
71+
organizationId,
72+
});
73+
},
74+
[enterpriseConnection, user, session, createEnterpriseConnection],
75+
),
76+
);
77+
5178
const handleContinue = async () => {
5279
if (!selected) {
5380
return;

packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ClerkRuntimeError } from '@clerk/shared/error';
2+
import type * as ClerkSharedReact from '@clerk/shared/react';
23
import type { ReactElement } from 'react';
34
import { describe, expect, it, vi } from 'vitest';
45

@@ -27,19 +28,42 @@ vi.mock('../../elements/Wizard', () => ({
2728
}));
2829

2930
const setProvider = vi.fn();
30-
const clearProvider = vi.fn();
31-
const createConnection = vi.fn();
3231

3332
vi.mock('../../ConfigureSSOContext', () => ({
3433
useConfigureSSO: () => ({
3534
enterpriseConnection: undefined,
3635
provider: undefined,
3736
setProvider,
38-
clearProvider,
39-
createConnection,
37+
initialStepId: 'select-provider',
4038
}),
4139
}));
4240

41+
const createEnterpriseConnection = vi.fn();
42+
43+
vi.mock('@clerk/shared/react', async importOriginal => {
44+
const actual = await importOriginal<typeof ClerkSharedReact>();
45+
return {
46+
...actual,
47+
__internal_useUserEnterpriseConnections: () => ({
48+
data: undefined,
49+
error: null,
50+
isLoading: false,
51+
isFetching: false,
52+
createEnterpriseConnection,
53+
updateEnterpriseConnection: vi.fn(),
54+
deleteEnterpriseConnection: vi.fn(),
55+
revalidate: vi.fn(),
56+
}),
57+
useReverification: <T,>(fn: T): T => fn,
58+
useUser: () => ({
59+
user: { primaryEmailAddress: { emailAddress: 'iago@acme.com' } },
60+
}),
61+
useSession: () => ({
62+
session: { lastActiveOrganizationId: 'org_test' },
63+
}),
64+
};
65+
});
66+
4367
import { SelectProviderStep } from '../SelectProviderStep';
4468

4569
const { createFixtures } = bindCreateFixtures('ConfigureSSO');
@@ -55,9 +79,8 @@ const resetMocks = () => {
5579
goNext.mockReset();
5680
goPrev.mockReset();
5781
setProvider.mockReset();
58-
clearProvider.mockReset();
59-
createConnection.mockReset();
60-
createConnection.mockResolvedValue(undefined);
82+
createEnterpriseConnection.mockReset();
83+
createEnterpriseConnection.mockResolvedValue({});
6184
};
6285

6386
describe('SelectProviderStep', () => {
@@ -136,15 +159,15 @@ describe('SelectProviderStep', () => {
136159
expect(customSamlTile).toHaveAttribute('aria-pressed', 'true');
137160
});
138161

139-
it('calls setProvider, createConnection, then goNext when Continue is clicked', async () => {
162+
it('calls setProvider, createEnterpriseConnection, then goNext when Continue is clicked', async () => {
140163
resetMocks();
141164
const callOrder: string[] = [];
142165
setProvider.mockImplementation(() => {
143166
callOrder.push('setProvider');
144167
});
145-
createConnection.mockImplementation(() => {
146-
callOrder.push('createConnection');
147-
return Promise.resolve();
168+
createEnterpriseConnection.mockImplementation(() => {
169+
callOrder.push('createEnterpriseConnection');
170+
return Promise.resolve({});
148171
});
149172
goNext.mockImplementation(() => {
150173
callOrder.push('goNext');
@@ -161,9 +184,13 @@ describe('SelectProviderStep', () => {
161184
});
162185

163186
expect(setProvider).toHaveBeenCalledWith('saml_okta');
164-
expect(createConnection).toHaveBeenCalledTimes(1);
165-
expect(createConnection).toHaveBeenCalledWith('saml_okta');
166-
expect(callOrder).toEqual(['setProvider', 'createConnection', 'goNext']);
187+
expect(createEnterpriseConnection).toHaveBeenCalledTimes(1);
188+
expect(createEnterpriseConnection).toHaveBeenCalledWith({
189+
provider: 'saml_okta',
190+
name: 'acme.com',
191+
organizationId: 'org_test',
192+
});
193+
expect(callOrder).toEqual(['setProvider', 'createEnterpriseConnection', 'goNext']);
167194
});
168195

169196
it('forwards the Custom SAML backend provider id when selected', async () => {
@@ -179,14 +206,18 @@ describe('SelectProviderStep', () => {
179206
});
180207

181208
expect(setProvider).toHaveBeenCalledWith('saml_custom');
182-
expect(createConnection).toHaveBeenCalledTimes(1);
183-
expect(createConnection).toHaveBeenCalledWith('saml_custom');
209+
expect(createEnterpriseConnection).toHaveBeenCalledTimes(1);
210+
expect(createEnterpriseConnection).toHaveBeenCalledWith({
211+
provider: 'saml_custom',
212+
name: 'acme.com',
213+
organizationId: 'org_test',
214+
});
184215
});
185216

186-
it('shows loading state while createConnection is pending', async () => {
217+
it('shows loading state while createEnterpriseConnection is pending', async () => {
187218
resetMocks();
188219
let resolveCreate: () => void = () => undefined;
189-
createConnection.mockImplementation(
220+
createEnterpriseConnection.mockImplementation(
190221
() =>
191222
new Promise<void>(resolve => {
192223
resolveCreate = resolve;
@@ -203,7 +234,7 @@ describe('SelectProviderStep', () => {
203234
// While create is pending, Continue stays disabled and goNext hasn't fired.
204235
// The button's accessible name flips to the spinner's "Loading" label while pending.
205236
await waitFor(() => {
206-
expect(createConnection).toHaveBeenCalledTimes(1);
237+
expect(createEnterpriseConnection).toHaveBeenCalledTimes(1);
207238
});
208239
expect(continueButton).toBeDisabled();
209240
expect(goNext).not.toHaveBeenCalled();
@@ -215,9 +246,11 @@ describe('SelectProviderStep', () => {
215246
});
216247
});
217248

218-
it('does not advance and surfaces the error when createConnection rejects', async () => {
249+
it('does not advance and surfaces the error when createEnterpriseConnection rejects', async () => {
219250
resetMocks();
220-
createConnection.mockRejectedValue(new ClerkRuntimeError('Backend unavailable', { code: 'create_failed' }));
251+
createEnterpriseConnection.mockRejectedValue(
252+
new ClerkRuntimeError('Backend unavailable', { code: 'create_failed' }),
253+
);
221254

222255
const { wrapper } = await createFixtures();
223256
const { userEvent, container } = renderStep(wrapper);
@@ -226,7 +259,7 @@ describe('SelectProviderStep', () => {
226259
await userEvent.click(screen.getByRole('button', { name: /Continue/i }));
227260

228261
await waitFor(() => {
229-
expect(createConnection).toHaveBeenCalledTimes(1);
262+
expect(createEnterpriseConnection).toHaveBeenCalledTimes(1);
230263
});
231264

232265
expect(goNext).not.toHaveBeenCalled();

0 commit comments

Comments
 (0)