Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(clerk-js): Hide Add domain button when user is missing org:sys_domains:manage #2240

Merged
merged 1 commit into from
Dec 4, 2023
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' })],
dimkl marked this conversation as resolved.
Show resolved Hide resolved
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: ['[email protected]'],
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: ['[email protected]'],
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: ['[email protected]'],
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: ['[email protected]'],
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
Loading