From bea97d497d89ccb92dfef4650214db9be7a6b1cf Mon Sep 17 00:00:00 2001 From: tale-agent Date: Wed, 24 Jun 2026 00:54:50 +0000 Subject: [PATCH] fix(platform): seed enterprise sso form select/switch with defined defaults (#2095) --- .../components/enterprise-sso-form.test.tsx | 36 +++++++++++++++++++ .../components/enterprise-sso-form.tsx | 22 +++++++++--- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/services/platform/app/features/settings/enterprise-sso/components/enterprise-sso-form.test.tsx b/services/platform/app/features/settings/enterprise-sso/components/enterprise-sso-form.test.tsx index 4c23c43e5..9b8761187 100644 --- a/services/platform/app/features/settings/enterprise-sso/components/enterprise-sso-form.test.tsx +++ b/services/platform/app/features/settings/enterprise-sso/components/enterprise-sso-form.test.tsx @@ -319,6 +319,42 @@ describe('EnterpriseSsoForm validation + save', () => { }); }); + it('does not log uncontrolled→controlled warnings when config resolves after load', async () => { + // Regression (#2095): the Select/Switch fields must be controlled from the + // first render. When `config` is undefined the seeded `data` is undefined, + // so `field.value` is undefined — without a defined fallback the controls + // mount uncontrolled, then warn once `config` hydrates them. + // Radix's `useControllableState` reports the transition via `console.warn` + // (React's native uncontrolled→controlled warning targets DOM inputs; these + // are button-based Radix controls), so spy on `warn`. + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + try { + const { rerender } = renderForm(undefined); + + // Resolve the config — the form seeds its real values. + rerender( + + + + + + , + ); + + await screen.findByDisplayValue('Acme SSO'); + + const warned = warnSpy.mock.calls.some((call) => + call.some( + (arg) => + typeof arg === 'string' && /uncontrolled.*controlled/i.test(arg), + ), + ); + expect(warned).toBe(false); + } finally { + warnSpy.mockRestore(); + } + }); + it('blocks the test action and does not call testConnection when invalid', async () => { testConnMock.mockClear(); const { user } = renderForm(unconfigured); diff --git a/services/platform/app/features/settings/enterprise-sso/components/enterprise-sso-form.tsx b/services/platform/app/features/settings/enterprise-sso/components/enterprise-sso-form.tsx index 67155cbc0..5c0427e94 100644 --- a/services/platform/app/features/settings/enterprise-sso/components/enterprise-sso-form.tsx +++ b/services/platform/app/features/settings/enterprise-sso/components/enterprise-sso-form.tsx @@ -559,7 +559,10 @@ export function EnterpriseSsoForm({ organizationId, config }: Props) { id="sso-protocol" label={t('integrations.enterpriseSso.protocolLabel')} description={t('integrations.enterpriseSso.protocolHelp')} - value={field.value} + // Default to a defined value so the Select is controlled from + // the first render — `field.value` is undefined while `data` + // is still loading (avoids the uncontrolled→controlled warning). + value={field.value ?? ''} onValueChange={(value) => { const next = narrowStringUnion( value, @@ -670,7 +673,9 @@ export function EnterpriseSsoForm({ organizationId, config }: Props) { name="pkce" render={({ field }) => ( @@ -749,7 +754,10 @@ export function EnterpriseSsoForm({ organizationId, config }: Props) {