From 5b219e993c9669e214c5cb80c3cfad93a9eea7e7 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 22 Sep 2026 15:23:33 -0700 Subject: [PATCH 1/2] fix(ui): hide the empty scope list when only offline_access is requested --- .changeset/quiet-scopes-offline.md | 5 +++ .../components/OAuthConsent/OAuthConsent.tsx | 34 ++++++++++--------- .../__tests__/OAuthConsent.test.tsx | 23 +++++++++++++ .../OAuthDeviceVerification.tsx | 34 ++++++++++--------- .../OAuthDeviceVerification.test.tsx | 17 ++++++++++ 5 files changed, 81 insertions(+), 32 deletions(-) create mode 100644 .changeset/quiet-scopes-offline.md diff --git a/.changeset/quiet-scopes-offline.md b/.changeset/quiet-scopes-offline.md new file mode 100644 index 00000000000..8f2b0ac452b --- /dev/null +++ b/.changeset/quiet-scopes-offline.md @@ -0,0 +1,5 @@ +--- +'@clerk/ui': patch +--- + +`` and `` no longer show an empty permissions list when an OAuth client requests only the `offline_access` scope. diff --git a/packages/ui/src/components/OAuthConsent/OAuthConsent.tsx b/packages/ui/src/components/OAuthConsent/OAuthConsent.tsx index e85b0229dce..b9f6fcd00cb 100644 --- a/packages/ui/src/components/OAuthConsent/OAuthConsent.tsx +++ b/packages/ui/src/components/OAuthConsent/OAuthConsent.tsx @@ -263,22 +263,24 @@ function _OAuthConsent() { onChange={setSelectedOrg} /> )} - - - - - - {displayedScopes.map(item => ( - - {item.description || item.scope || ''} - - ))} - - + {displayedScopes.length > 0 && ( + + + + + + {displayedScopes.map(item => ( + + {item.description || item.scope || ''} + + ))} + + + )} { await waitFor(() => { expect(getByText('Clerk CLI')).toBeVisible(); + expect(getByText('This will allow Clerk CLI access to:')).toBeVisible(); expect(getByText('View your identity')).toBeVisible(); expect(getByText('Access your email address')).toBeVisible(); }); @@ -126,6 +127,28 @@ describe('OAuthConsent', () => { expect(queryByText('Your private metadata')).toBeNull(); }); + it('hides the scope list when only offline_access is requested', async () => { + const { wrapper, fixtures, props } = await createFixtures(f => { + f.withUser({ email_addresses: ['jane@example.com'] }); + }); + + props.setProps({ componentName: 'OAuthConsent' } as any); + mockOAuthApplication(fixtures.clerk, { + getConsentInfo: vi.fn().mockResolvedValue({ + ...fakeConsentInfo, + scopes: [{ scope: 'offline_access', description: 'Offline access', requiresConsent: true }], + }), + }); + + const { getByText, queryByText } = render(, { wrapper }); + + await waitFor(() => { + expect(getByText(/You'll stay signed in until you sign out or revoke access\./)).toBeVisible(); + }); + expect(queryByText('This will allow Clerk CLI access to:')).toBeNull(); + expect(queryByText('Offline access')).toBeNull(); + }); + it('supports localizing the private metadata scope description', async () => { const { wrapper: Wrapper, diff --git a/packages/ui/src/components/OAuthDeviceVerification/OAuthDeviceVerification.tsx b/packages/ui/src/components/OAuthDeviceVerification/OAuthDeviceVerification.tsx index bda29d436b3..54a436e6f15 100644 --- a/packages/ui/src/components/OAuthDeviceVerification/OAuthDeviceVerification.tsx +++ b/packages/ui/src/components/OAuthDeviceVerification/OAuthDeviceVerification.tsx @@ -313,22 +313,24 @@ function OAuthDeviceVerificationInternal() { onChange={setSelectedOrg} /> )} - - - - - - {displayedScopes.map(scope => ( - - {scope.description || scope.scope} - - ))} - - + {displayedScopes.length > 0 && ( + + + + + + {displayedScopes.map(scope => ( + + {scope.description || scope.scope} + + ))} + + + )} { expect(getByText("You'll stay signed in until you sign out or revoke access.")).toBeVisible(); }); + it('hides the scope list when only offline_access is requested', async () => { + Object.defineProperty(window, 'location', { + configurable: true, + value: { ...originalLocation, search: '?user_code=BFWS-ZBZM' }, + }); + const { wrapper } = await setup({ + ...verificationInfo, + scopes: [{ scope: 'offline_access', description: 'Offline access', requiresConsent: true }], + }); + const { getByText, queryByText } = render(, { wrapper }); + + await waitFor(() => expect(getByText('Allow TV App to access your account?')).toBeVisible()); + expect(getByText("You'll stay signed in until you sign out or revoke access.")).toBeVisible(); + expect(queryByText('This will allow TV App access to:')).toBeNull(); + expect(queryByText('Offline access')).toBeNull(); + }); + it('keeps an invalid URL prefill editable without calling FAPI', async () => { Object.defineProperty(window, 'location', { configurable: true, From de53659787600bd23c18a673c389da1e848e18cd Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 22 Sep 2026 15:26:25 -0700 Subject: [PATCH 2/2] chore: clean up --- .../src/components/OAuthConsent/__tests__/OAuthConsent.test.tsx | 1 - .../__tests__/OAuthDeviceVerification.test.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/ui/src/components/OAuthConsent/__tests__/OAuthConsent.test.tsx b/packages/ui/src/components/OAuthConsent/__tests__/OAuthConsent.test.tsx index 75ac17566c2..34377f75200 100644 --- a/packages/ui/src/components/OAuthConsent/__tests__/OAuthConsent.test.tsx +++ b/packages/ui/src/components/OAuthConsent/__tests__/OAuthConsent.test.tsx @@ -146,7 +146,6 @@ describe('OAuthConsent', () => { expect(getByText(/You'll stay signed in until you sign out or revoke access\./)).toBeVisible(); }); expect(queryByText('This will allow Clerk CLI access to:')).toBeNull(); - expect(queryByText('Offline access')).toBeNull(); }); it('supports localizing the private metadata scope description', async () => { diff --git a/packages/ui/src/components/OAuthDeviceVerification/__tests__/OAuthDeviceVerification.test.tsx b/packages/ui/src/components/OAuthDeviceVerification/__tests__/OAuthDeviceVerification.test.tsx index eca155fdf52..ad9d57c63ab 100644 --- a/packages/ui/src/components/OAuthDeviceVerification/__tests__/OAuthDeviceVerification.test.tsx +++ b/packages/ui/src/components/OAuthDeviceVerification/__tests__/OAuthDeviceVerification.test.tsx @@ -155,7 +155,6 @@ describe('OAuthDeviceVerification', () => { await waitFor(() => expect(getByText('Allow TV App to access your account?')).toBeVisible()); expect(getByText("You'll stay signed in until you sign out or revoke access.")).toBeVisible(); expect(queryByText('This will allow TV App access to:')).toBeNull(); - expect(queryByText('Offline access')).toBeNull(); }); it('keeps an invalid URL prefill editable without calling FAPI', async () => {