diff --git a/packages/reference/src/resources/MultipleResourceReferenceEditor.spec.tsx b/packages/reference/src/resources/MultipleResourceReferenceEditor.spec.tsx index 0f414f40c..af910e0b8 100644 --- a/packages/reference/src/resources/MultipleResourceReferenceEditor.spec.tsx +++ b/packages/reference/src/resources/MultipleResourceReferenceEditor.spec.tsx @@ -1,10 +1,11 @@ -import * as React from 'react'; - import { FieldAppSDK } from '@contentful/app-sdk'; import '@testing-library/jest-dom/extend-expect'; import { fireEvent, render, screen } from '@testing-library/react'; +import * as React from 'react'; + import { useResource } from '../common/EntityStore'; +import { useEditorPermissions } from '../common/useEditorPermissions'; import { MultipleResourceReferenceEditor } from './MultipleResourceReferenceEditor'; import { createFakeEntryResource, mockSdkForField } from './testHelpers/resourceEditorHelpers'; @@ -23,6 +24,12 @@ jest.mock('../common/EntityStore', () => { }; }); +jest.mock('../common/useEditorPermissions', () => { + return { + useEditorPermissions: jest.fn(), + }; +}); + jest.mock('react-intersection-observer', () => { const module = jest.requireActual('react-intersection-observer'); @@ -49,6 +56,12 @@ const fieldDefinition = { validations: [], }; +const mockedUseEditorPermissions = useEditorPermissions as jest.Mock; + +beforeEach(() => { + mockedUseEditorPermissions.mockImplementation(() => ({ canLinkEntity: true })); +}); + describe('Multiple resource editor', () => { it('renders the action button when no value is set', async () => { const sdk: FieldAppSDK = mockSdkForField(fieldDefinition); @@ -76,6 +89,26 @@ describe('Multiple resource editor', () => { }); }); + it('hides the action button when insufficient permissions', async () => { + mockedUseEditorPermissions.mockImplementation(() => ({ canLinkEntity: false })); + + const sdk: FieldAppSDK = mockSdkForField(fieldDefinition); + render( + ''} + isInitiallyDisabled={false} + sdk={sdk} + hasCardEditActions={true} + viewType="card" + // @ts-expect-error unused... + parameters={{}} + /> + ); + + const noPermission = await screen.findByText(/You don't have permission to view this content/); + expect(noPermission).toBeDefined(); + }); + it('renders custom actions when passed', async () => { const sdk: FieldAppSDK = mockSdkForField(fieldDefinition); render( diff --git a/packages/reference/src/resources/MultipleResourceReferenceEditor.tsx b/packages/reference/src/resources/MultipleResourceReferenceEditor.tsx index 66972616e..4d049a115 100644 --- a/packages/reference/src/resources/MultipleResourceReferenceEditor.tsx +++ b/packages/reference/src/resources/MultipleResourceReferenceEditor.tsx @@ -28,11 +28,10 @@ type ChildProps = { type EditorProps = ReferenceEditorProps & Omit & { children: (props: ReferenceEditorProps & ChildProps) => React.ReactElement; - apiUrl: string; }; function ResourceEditor(props: EditorProps) { - const { setValue, items, apiUrl } = props; + const { setValue, items } = props; const onSortStart = () => noop(); const onSortEnd = useCallback( @@ -57,11 +56,9 @@ function ResourceEditor(props: EditorProps) { [items, setValue] ); - const { dialogs, field } = props.sdk; const linkActionsProps = useResourceLinkActions({ - dialogs, - field, - apiUrl, + sdk: props.sdk, + parameters: props.parameters, }); return ( diff --git a/packages/reference/src/resources/SingleResourceReferenceEditor.spec.tsx b/packages/reference/src/resources/SingleResourceReferenceEditor.spec.tsx index 59a85bba9..41a72384a 100644 --- a/packages/reference/src/resources/SingleResourceReferenceEditor.spec.tsx +++ b/packages/reference/src/resources/SingleResourceReferenceEditor.spec.tsx @@ -5,6 +5,7 @@ import '@testing-library/jest-dom/extend-expect'; import { fireEvent, render, screen } from '@testing-library/react'; import { useResource } from '../common/EntityStore'; +import { useEditorPermissions } from '../common/useEditorPermissions'; import { SingleResourceReferenceEditor } from './SingleResourceReferenceEditor'; import { createFakeEntryResource, mockSdkForField } from './testHelpers/resourceEditorHelpers'; @@ -31,6 +32,12 @@ jest.mock('react-intersection-observer', () => { }; }); +jest.mock('../common/useEditorPermissions', () => { + return { + useEditorPermissions: jest.fn(), + }; +}); + const fieldDefinition = { type: 'ResourceLink', id: 'foo', @@ -44,6 +51,13 @@ const fieldDefinition = { required: true, validations: [], }; + +const mockedUseEditorPermissions = useEditorPermissions as jest.Mock; + +beforeEach(() => { + mockedUseEditorPermissions.mockImplementation(() => ({ canLinkEntity: true })); +}); + describe('Single resource editor', () => { it('renders the action button when no value is set', async () => { const sdk: FieldAppSDK = mockSdkForField(fieldDefinition); @@ -71,6 +85,25 @@ describe('Single resource editor', () => { }); }); + it('renders no the action button when permissions insufficient', async () => { + mockedUseEditorPermissions.mockImplementation(() => ({ canLinkEntity: false })); + + const sdk: FieldAppSDK = mockSdkForField(fieldDefinition); + render( + + ); + + const noPermission = await screen.findByText(/You don't have permission to view this content/); + expect(noPermission).toBeDefined(); + }); + it('renders custom actions when passed', async () => { const sdk: FieldAppSDK = mockSdkForField(fieldDefinition); render( diff --git a/packages/reference/src/resources/SingleResourceReferenceEditor.tsx b/packages/reference/src/resources/SingleResourceReferenceEditor.tsx index aca28ba5b..60f4a50ef 100644 --- a/packages/reference/src/resources/SingleResourceReferenceEditor.tsx +++ b/packages/reference/src/resources/SingleResourceReferenceEditor.tsx @@ -13,14 +13,11 @@ import { useResourceLinkActions } from './useResourceLinkActions'; export function SingleResourceReferenceEditor( props: ReferenceEditorProps & { getEntryRouteHref: (entryRoute: EntryRoute) => string; - apiUrl: string; } ) { - const { dialogs, field } = props.sdk; const linkActionsProps = useResourceLinkActions({ - dialogs, - field, - apiUrl: props.apiUrl, + sdk: props.sdk, + parameters: props.parameters, }); return ( diff --git a/packages/reference/src/resources/useResourceLinkActions.ts b/packages/reference/src/resources/useResourceLinkActions.ts index 7bd645644..663b6d16a 100644 --- a/packages/reference/src/resources/useResourceLinkActions.ts +++ b/packages/reference/src/resources/useResourceLinkActions.ts @@ -1,8 +1,9 @@ import { useMemo } from 'react'; -import type { FieldAPI, FieldAppSDK } from '@contentful/app-sdk'; +import type { FieldAPI } from '@contentful/app-sdk'; import type { ResourceLink } from 'contentful-management'; +import { EditorPermissionsProps, useEditorPermissions } from '../common/useEditorPermissions'; import { LinkActionsProps } from '../components'; const getUpdatedValue = ( @@ -18,12 +19,14 @@ const getUpdatedValue = ( } }; +type ResourceLinkActionProps = Pick; + export function useResourceLinkActions({ - dialogs, - field, -}: Pick & { - apiUrl: string; -}): LinkActionsProps { + parameters, + sdk, +}: ResourceLinkActionProps): LinkActionsProps { + const { field, dialogs } = sdk; + const onLinkedExisting = useMemo(() => { return ( links: ResourceLink<'Contentful:Entry'>[] | [ResourceLink<'Contentful:Entry'> | null] @@ -58,6 +61,13 @@ export function useResourceLinkActions({ // @ts-expect-error wait for update of app-sdk version }, [dialogs, field.allowedResources, multiple, onLinkedExisting]); + const { canLinkEntity } = useEditorPermissions({ + entityType: 'Entry', + allContentTypes: [], + sdk, + parameters, + }); + return { onLinkExisting, // @ts-expect-error @@ -67,7 +77,7 @@ export function useResourceLinkActions({ contentTypes: [], canCreateEntity: false, canLinkMultiple: multiple, - canLinkEntity: true, + canLinkEntity, isDisabled: false, isEmpty: false, isFull: false,