From 920c9e1b5d091472bce0ff00e410b3d79c88d930 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Mon, 4 Dec 2023 11:42:46 +0200 Subject: [PATCH] fix(clerk-js): Hide Add domain button when user is missing `org:sys_domains:manage` (#2240) --- .changeset/strong-cows-sit.md | 5 ++ .../OrganizationSettings.tsx | 12 +-- .../__tests__/OrganizationSettings.test.tsx | 77 +++++++++++++------ .../OrganizationSettings.tsx | 12 +-- .../__tests__/OrganizationSettings.test.tsx | 69 ++++++++++++----- 5 files changed, 120 insertions(+), 55 deletions(-) create mode 100644 .changeset/strong-cows-sit.md diff --git a/.changeset/strong-cows-sit.md b/.changeset/strong-cows-sit.md new file mode 100644 index 0000000000..249ad072f3 --- /dev/null +++ b/.changeset/strong-cows-sit.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Hide "Add domain" button inside `` when user is missing the `org:sys_domains:manage` permission. diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationSettings.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationSettings.tsx index 4af1571768..a0a935b8e2 100644 --- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationSettings.tsx +++ b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationSettings.tsx @@ -89,11 +89,13 @@ const OrganizationDomainsSection = () => { > - navigate('domain')} - /> + + navigate('domain')} + /> + ); }; diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/OrganizationSettings.test.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/OrganizationSettings.test.tsx index f17a1c2195..9034d215c8 100644 --- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/OrganizationSettings.test.tsx +++ b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/__tests__/OrganizationSettings.test.tsx @@ -1,4 +1,4 @@ -import type { OrganizationDomainResource, OrganizationMembershipResource } from '@clerk/types'; +import type { ClerkPaginatedResponse, OrganizationDomainResource, OrganizationMembershipResource } from '@clerk/types'; import { describe, it } from '@jest/globals'; import userEvent from '@testing-library/user-event'; @@ -30,12 +30,14 @@ describe('OrganizationSettings', () => { ); const { getByText } = render(, { wrapper }); + await waitFor(() => { expect(fixtures.clerk.organization?.getMemberships).toHaveBeenCalled(); - expect(getByText('Settings')).toBeDefined(); - expect(getByText('Org1', { exact: false }).closest('button')).not.toBeNull(); - expect(getByText(/leave organization/i, { exact: false }).closest('button')).toHaveAttribute('disabled'); }); + + expect(getByText('Settings')).toBeDefined(); + expect(getByText('Org1', { exact: false }).closest('button')).not.toBeNull(); + expect(getByText(/leave organization/i, { exact: false }).closest('button')).toHaveAttribute('disabled'); }); it('enables organization profile button and enables leave when user is admin and there is more', async () => { @@ -63,7 +65,10 @@ describe('OrganizationSettings', () => { }); it.skip('disables organization profile button and enables leave when user is not admin', async () => { - const adminsList: OrganizationMembershipResource[] = [createFakeMember({ id: '1', orgId: '1', role: 'admin' })]; + const adminsList: ClerkPaginatedResponse = { + data: [createFakeMember({ id: '1', orgId: '1', role: 'admin' })], + total_count: 1, + }; const { wrapper, fixtures } = await createFixtures(f => { f.withOrganizations(); @@ -98,7 +103,7 @@ describe('OrganizationSettings', () => { expect(fixtures.clerk.organization?.getDomains).not.toBeCalled(); }); - it('shows domains when `read` permission exists', async () => { + it('shows domains when `read` permission exists but hides the Add domain button', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withOrganizations(); f.withOrganizationDomains(); @@ -117,6 +122,30 @@ describe('OrganizationSettings', () => { await new Promise(r => setTimeout(r, 100)); expect(queryByText('Verified domains')).toBeInTheDocument(); + expect(queryByText('Add domain')).not.toBeInTheDocument(); + expect(fixtures.clerk.organization?.getDomains).toBeCalled(); + }); + + it('shows domains and shows the Add domain button when `org:sys_domains:manage` exists', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withOrganizations(); + f.withOrganizationDomains(); + f.withUser({ + email_addresses: ['test@clerk.dev'], + organization_memberships: [{ name: 'Org1', permissions: ['org:sys_domains:read', 'org:sys_domains:manage'] }], + }); + }); + fixtures.clerk.organization?.getDomains.mockReturnValue( + Promise.resolve({ + data: [], + total_count: 0, + }), + ); + const { queryByText } = await act(() => render(, { wrapper })); + + await new Promise(r => setTimeout(r, 100)); + expect(queryByText('Verified domains')).toBeInTheDocument(); + expect(queryByText('Add domain')).toBeInTheDocument(); expect(fixtures.clerk.organization?.getDomains).toBeCalled(); }); @@ -156,18 +185,21 @@ describe('OrganizationSettings', () => { }); it.skip('disabled leave organization button with delete organization button', async () => { - const adminsList: OrganizationMembershipResource[] = [ - createFakeMember({ - id: '1', - orgId: '1', - role: 'admin', - }), - createFakeMember({ - id: '2', - orgId: '1', - role: 'admin', - }), - ]; + const adminsList: ClerkPaginatedResponse = { + data: [ + createFakeMember({ + id: '1', + orgId: '1', + role: 'admin', + }), + createFakeMember({ + id: '2', + orgId: '1', + role: 'admin', + }), + ], + total_count: 2, + }; const { wrapper, fixtures } = await createFixtures(f => { f.withOrganizations(); @@ -212,21 +244,18 @@ describe('OrganizationSettings', () => { expect(fixtures.router.navigate).toHaveBeenCalledWith('profile'); }); - it('navigates to Leave Organization page when clicking on the respective button and user is not admin', async () => { - const adminsList: OrganizationMembershipResource[] = [createFakeMember({ id: '1', orgId: '1', role: 'admin' })]; - + // TODO(@panteliselef): Update this test to allow user to leave an org, only if there will be at least one person left with the minimum set of permissions + it('navigates to Leave Organization page when clicking on the respective button', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withOrganizations(); f.withUser({ email_addresses: ['test@clerk.com'], - organization_memberships: [{ name: 'Org1', role: 'basic_member' }], + organization_memberships: [{ name: 'Org1', permissions: [] }], }); }); - fixtures.clerk.organization?.getMemberships.mockReturnValue(Promise.resolve(adminsList)); const { findByText } = render(, { wrapper }); await waitFor(async () => { - // expect(fixtures.clerk.organization?.getMemberships).toHaveBeenCalled(); await userEvent.click(await findByText(/leave organization/i, { exact: false })); }); expect(fixtures.router.navigate).toHaveBeenCalledWith('leave'); diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationSettings.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationSettings.tsx index 4af1571768..a0a935b8e2 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationSettings.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationSettings.tsx @@ -89,11 +89,13 @@ const OrganizationDomainsSection = () => { > - navigate('domain')} - /> + + navigate('domain')} + /> + ); }; diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationSettings.test.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationSettings.test.tsx index a5a9469d6c..ce612bc2d2 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationSettings.test.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationSettings.test.tsx @@ -1,4 +1,4 @@ -import type { OrganizationDomainResource, OrganizationMembershipResource } from '@clerk/types'; +import type { ClerkPaginatedResponse, OrganizationDomainResource, OrganizationMembershipResource } from '@clerk/types'; import { describe, it } from '@jest/globals'; import userEvent from '@testing-library/user-event'; import React from 'react'; @@ -66,7 +66,10 @@ describe('OrganizationSettings', () => { }); it.skip('disables organization profile button and enables leave when user is not admin', async () => { - const adminsList: OrganizationMembershipResource[] = [createFakeMember({ id: '1', orgId: '1', role: 'admin' })]; + const adminsList: ClerkPaginatedResponse = { + data: [createFakeMember({ id: '1', orgId: '1', role: 'admin' })], + total_count: 1, + }; const { wrapper, fixtures } = await createFixtures(f => { f.withOrganizations(); @@ -101,7 +104,7 @@ describe('OrganizationSettings', () => { expect(fixtures.clerk.organization?.getDomains).not.toBeCalled(); }); - it('shows domains when `read` permission exists', async () => { + it('shows domains when `read` permission exists but hides the Add domain button', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withOrganizations(); f.withOrganizationDomains(); @@ -120,6 +123,30 @@ describe('OrganizationSettings', () => { await new Promise(r => setTimeout(r, 100)); expect(queryByText('Verified domains')).toBeInTheDocument(); + expect(queryByText('Add domain')).not.toBeInTheDocument(); + expect(fixtures.clerk.organization?.getDomains).toBeCalled(); + }); + + it('shows domains and shows the Add domain button when `org:sys_domains:manage` exists', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withOrganizations(); + f.withOrganizationDomains(); + f.withUser({ + email_addresses: ['test@clerk.dev'], + organization_memberships: [{ name: 'Org1', permissions: ['org:sys_domains:read', 'org:sys_domains:manage'] }], + }); + }); + fixtures.clerk.organization?.getDomains.mockReturnValue( + Promise.resolve({ + data: [], + total_count: 0, + }), + ); + const { queryByText } = await act(() => render(, { wrapper })); + + await new Promise(r => setTimeout(r, 100)); + expect(queryByText('Verified domains')).toBeInTheDocument(); + expect(queryByText('Add domain')).toBeInTheDocument(); expect(fixtures.clerk.organization?.getDomains).toBeCalled(); }); @@ -159,18 +186,21 @@ describe('OrganizationSettings', () => { }); it.skip('disabled leave organization button with delete organization button', async () => { - const adminsList: OrganizationMembershipResource[] = [ - createFakeMember({ - id: '1', - orgId: '1', - role: 'admin', - }), - createFakeMember({ - id: '2', - orgId: '1', - role: 'admin', - }), - ]; + const adminsList: ClerkPaginatedResponse = { + data: [ + createFakeMember({ + id: '1', + orgId: '1', + role: 'admin', + }), + createFakeMember({ + id: '2', + orgId: '1', + role: 'admin', + }), + ], + total_count: 2, + }; const { wrapper, fixtures } = await createFixtures(f => { f.withOrganizations(); @@ -215,21 +245,18 @@ describe('OrganizationSettings', () => { expect(fixtures.router.navigate).toHaveBeenCalledWith('profile'); }); - it('navigates to Leave Organization page when clicking on the respective button and user is not admin', async () => { - const adminsList: OrganizationMembershipResource[] = [createFakeMember({ id: '1', orgId: '1', role: 'admin' })]; - + // TODO(@panteliselef): Update this test to allow user to leave an org, only if there will be at least one person left with the minimum set of permissions + it('navigates to Leave Organization page when clicking on the respective button', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withOrganizations(); f.withUser({ email_addresses: ['test@clerk.com'], - organization_memberships: [{ name: 'Org1', role: 'basic_member' }], + organization_memberships: [{ name: 'Org1', permissions: [] }], }); }); - fixtures.clerk.organization?.getMemberships.mockReturnValue(Promise.resolve(adminsList)); const { findByText } = render(, { wrapper }); await waitFor(async () => { - // expect(fixtures.clerk.organization?.getMemberships).toHaveBeenCalled(); await userEvent.click(await findByText(/leave organization/i, { exact: false })); }); expect(fixtures.router.navigate).toHaveBeenCalledWith('leave');