Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/strong-cows-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Hide "Add domain" button inside `<OrganizationProfile/>` when user is missing the `org:sys_domains:manage` permission.
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,13 @@ const OrganizationDomainsSection = () => {
>
<DomainList redirectSubPath={'domain'} />

<AddBlockButton
textLocalizationKey={localizationKeys('organizationProfile.profilePage.domainSection.primaryButton')}
id='addOrganizationDomain'
onClick={() => navigate('domain')}
/>
<Gate permission='org:sys_domains:manage'>
<AddBlockButton
textLocalizationKey={localizationKeys('organizationProfile.profilePage.domainSection.primaryButton')}
id='addOrganizationDomain'
onClick={() => navigate('domain')}
/>
</Gate>
</ProfileSection>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -30,12 +30,14 @@ describe('OrganizationSettings', () => {
);

const { getByText } = render(<OrganizationSettings />, { 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 () => {
Expand Down Expand Up @@ -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<OrganizationMembershipResource> = {
data: [createFakeMember({ id: '1', orgId: '1', role: 'admin' })],
total_count: 1,
};

const { wrapper, fixtures } = await createFixtures(f => {
f.withOrganizations();
Expand Down Expand Up @@ -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();
Expand All @@ -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(<OrganizationSettings />, { wrapper }));

await new Promise(r => setTimeout(r, 100));
expect(queryByText('Verified domains')).toBeInTheDocument();
expect(queryByText('Add domain')).toBeInTheDocument();
expect(fixtures.clerk.organization?.getDomains).toBeCalled();
});

Expand Down Expand Up @@ -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<OrganizationMembershipResource> = {
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();
Expand Down Expand Up @@ -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(<OrganizationSettings />, { 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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,13 @@ const OrganizationDomainsSection = () => {
>
<DomainList redirectSubPath={'domain'} />

<AddBlockButton
textLocalizationKey={localizationKeys('organizationProfile.profilePage.domainSection.primaryButton')}
id='addOrganizationDomain'
onClick={() => navigate('domain')}
/>
<Gate permission='org:sys_domains:manage'>
<AddBlockButton
textLocalizationKey={localizationKeys('organizationProfile.profilePage.domainSection.primaryButton')}
id='addOrganizationDomain'
onClick={() => navigate('domain')}
/>
</Gate>
</ProfileSection>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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<OrganizationMembershipResource> = {
data: [createFakeMember({ id: '1', orgId: '1', role: 'admin' })],
total_count: 1,
};

const { wrapper, fixtures } = await createFixtures(f => {
f.withOrganizations();
Expand Down Expand Up @@ -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();
Expand All @@ -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(<OrganizationSettings />, { wrapper }));

await new Promise(r => setTimeout(r, 100));
expect(queryByText('Verified domains')).toBeInTheDocument();
expect(queryByText('Add domain')).toBeInTheDocument();
expect(fixtures.clerk.organization?.getDomains).toBeCalled();
});

Expand Down Expand Up @@ -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<OrganizationMembershipResource> = {
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();
Expand Down Expand Up @@ -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(<OrganizationSettings />, { 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');
Expand Down