@contentful/field-editor-reference 6.3.3 → 6.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,39 +9,45 @@ Object.defineProperty(exports, "useContentTypePermissions", {
9
9
  }
10
10
  });
11
11
  const _react = require("react");
12
+ const _lodash = require("lodash");
12
13
  const _useAccessApi = require("./useAccessApi");
13
14
  async function filter(arr, predicate) {
14
15
  const fail = Symbol();
15
16
  const results = await Promise.all(arr.map(async (item)=>await predicate(item) ? item : fail));
16
17
  return results.filter((x)=>x !== fail);
17
18
  }
18
- function useContentTypePermissions(props) {
19
+ function useContentTypePermissions({ entityType, validations, sdk, allContentTypes }) {
19
20
  const availableContentTypes = (0, _react.useMemo)(()=>{
20
- if (props.entityType === 'Asset') {
21
+ if (entityType === 'Asset') {
21
22
  return [];
22
23
  }
23
- if (props.validations.contentTypes) {
24
- return props.allContentTypes.filter((ct)=>props.validations.contentTypes?.includes(ct.sys.id));
24
+ if (validations.contentTypes) {
25
+ return allContentTypes.filter((ct)=>validations.contentTypes?.includes(ct.sys.id));
25
26
  }
26
- return props.allContentTypes;
27
+ return allContentTypes;
27
28
  }, [
28
- props.allContentTypes,
29
- props.validations.contentTypes,
30
- props.entityType
29
+ allContentTypes,
30
+ validations.contentTypes,
31
+ entityType
31
32
  ]);
32
33
  const [creatableContentTypes, setCreatableContentTypes] = (0, _react.useState)(availableContentTypes);
33
- const { canPerformActionOnEntryOfType } = (0, _useAccessApi.useAccessApi)(props.sdk.access);
34
+ const { canPerformActionOnEntryOfType } = (0, _useAccessApi.useAccessApi)(sdk.access);
34
35
  (0, _react.useEffect)(()=>{
35
36
  function getContentTypes(action) {
36
37
  return filter(availableContentTypes, (ct)=>canPerformActionOnEntryOfType(action, ct.sys.id));
37
38
  }
38
39
  async function checkContentTypeAccess() {
39
40
  const creatable = await getContentTypes('create');
40
- setCreatableContentTypes(creatable);
41
+ if (!(0, _lodash.isEqual)(creatable, creatableContentTypes)) {
42
+ setCreatableContentTypes(creatable);
43
+ }
44
+ }
45
+ if (availableContentTypes.length > 0) {
46
+ void checkContentTypeAccess();
41
47
  }
42
- void checkContentTypeAccess();
43
48
  }, [
44
- availableContentTypes
49
+ availableContentTypes,
50
+ creatableContentTypes
45
51
  ]);
46
52
  return {
47
53
  creatableContentTypes,
@@ -12,16 +12,18 @@ const _react = require("react");
12
12
  const _fromFieldValidations = require("../utils/fromFieldValidations");
13
13
  const _useAccessApi = require("./useAccessApi");
14
14
  const _useContentTypePermissions = require("./useContentTypePermissions");
15
- function useEditorPermissions(props) {
16
- const { sdk, entityType, parameters } = props;
17
- const validations = (0, _react.useMemo)(()=>(0, _fromFieldValidations.fromFieldValidations)(props.sdk.field), [
18
- props.sdk.field
15
+ function useEditorPermissions({ sdk, entityType, parameters, allContentTypes }) {
16
+ const validations = (0, _react.useMemo)(()=>(0, _fromFieldValidations.fromFieldValidations)(sdk.field), [
17
+ sdk.field
19
18
  ]);
20
19
  const [canCreateEntity, setCanCreateEntity] = (0, _react.useState)(true);
21
20
  const [canLinkEntity, setCanLinkEntity] = (0, _react.useState)(true);
22
21
  const { creatableContentTypes, availableContentTypes } = (0, _useContentTypePermissions.useContentTypePermissions)({
23
- ...props,
24
- validations
22
+ entityType,
23
+ validations,
24
+ sdk,
25
+ allContentTypes,
26
+ parameters
25
27
  });
26
28
  const { canPerformAction } = (0, _useAccessApi.useAccessApi)(sdk.access);
27
29
  (0, _react.useEffect)(()=>{
@@ -18,7 +18,7 @@ describe('useEditorPermissions', ()=>{
18
18
  id
19
19
  }
20
20
  });
21
- const renderEditorPermissions = async ({ entityType, params = {}, allContentTypes = [], customizeMock, customizeSdk })=>{
21
+ const renderEditorPermissions = async ({ entityType, params = {}, allContentTypes = [], customizeMock, customizeSdk, waitForUpdate = false })=>{
22
22
  const sdk = makeFieldAppSDK(customizeMock);
23
23
  customizeSdk?.(sdk);
24
24
  const renderResult = (0, _reacthooks.renderHook)(()=>(0, _useEditorPermissions.useEditorPermissions)({
@@ -29,7 +29,9 @@ describe('useEditorPermissions', ()=>{
29
29
  instance: params
30
30
  }
31
31
  }));
32
- await renderResult.waitForNextUpdate();
32
+ if (waitForUpdate) {
33
+ await renderResult.waitForNextUpdate();
34
+ }
33
35
  return {
34
36
  ...renderResult,
35
37
  sdk
@@ -102,7 +104,8 @@ describe('useEditorPermissions', ()=>{
102
104
  allContentTypes,
103
105
  customizeSdk: (sdk)=>{
104
106
  allowContentTypes(sdk, 'create', 'one');
105
- }
107
+ },
108
+ waitForUpdate: true
106
109
  });
107
110
  expect(result.current.canCreateEntity).toBe(true);
108
111
  });
@@ -130,7 +133,8 @@ describe('useEditorPermissions', ()=>{
130
133
  allContentTypes,
131
134
  customizeSdk: (sdk)=>{
132
135
  allowContentTypes(sdk, 'read', 'one');
133
- }
136
+ },
137
+ waitForUpdate: true
134
138
  });
135
139
  expect(result.current.canLinkEntity).toBe(true);
136
140
  });
@@ -64,7 +64,7 @@ function _interop_require_wildcard(obj, nodeInterop) {
64
64
  return newObj;
65
65
  }
66
66
  function ResourceEditor(props) {
67
- const { setValue, items, apiUrl } = props;
67
+ const { setValue, items } = props;
68
68
  const onSortStart = ()=>(0, _noop.default)();
69
69
  const onSortEnd = (0, _react.useCallback)(({ oldIndex, newIndex })=>{
70
70
  const newItems = (0, _sortable.arrayMove)(items, oldIndex, newIndex);
@@ -86,11 +86,9 @@ function ResourceEditor(props) {
86
86
  items,
87
87
  setValue
88
88
  ]);
89
- const { dialogs, field } = props.sdk;
90
89
  const linkActionsProps = (0, _useResourceLinkActions.useResourceLinkActions)({
91
- dialogs,
92
- field,
93
- apiUrl
90
+ sdk: props.sdk,
91
+ parameters: props.parameters
94
92
  });
95
93
  return _react.createElement(_react.Fragment, null, props.children({
96
94
  ...props,
@@ -2,10 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- const _react = _interop_require_wildcard(require("react"));
6
5
  require("@testing-library/jest-dom/extend-expect");
7
- const _react1 = require("@testing-library/react");
6
+ const _react = require("@testing-library/react");
7
+ const _react1 = _interop_require_wildcard(require("react"));
8
8
  const _EntityStore = require("../common/EntityStore");
9
+ const _useEditorPermissions = require("../common/useEditorPermissions");
9
10
  const _MultipleResourceReferenceEditor = require("./MultipleResourceReferenceEditor");
10
11
  const _resourceEditorHelpers = require("./testHelpers/resourceEditorHelpers");
11
12
  function _getRequireWildcardCache(nodeInterop) {
@@ -61,6 +62,11 @@ jest.mock('../common/EntityStore', ()=>{
61
62
  }))
62
63
  };
63
64
  });
65
+ jest.mock('../common/useEditorPermissions', ()=>{
66
+ return {
67
+ useEditorPermissions: jest.fn()
68
+ };
69
+ });
64
70
  jest.mock('react-intersection-observer', ()=>{
65
71
  const module = jest.requireActual('react-intersection-observer');
66
72
  return {
@@ -88,19 +94,25 @@ const fieldDefinition = {
88
94
  required: true,
89
95
  validations: []
90
96
  };
97
+ const mockedUseEditorPermissions = _useEditorPermissions.useEditorPermissions;
98
+ beforeEach(()=>{
99
+ mockedUseEditorPermissions.mockImplementation(()=>({
100
+ canLinkEntity: true
101
+ }));
102
+ });
91
103
  describe('Multiple resource editor', ()=>{
92
104
  it('renders the action button when no value is set', async ()=>{
93
105
  const sdk = (0, _resourceEditorHelpers.mockSdkForField)(fieldDefinition);
94
- (0, _react1.render)(_react.createElement(_MultipleResourceReferenceEditor.MultipleResourceReferenceEditor, {
106
+ (0, _react.render)(_react1.createElement(_MultipleResourceReferenceEditor.MultipleResourceReferenceEditor, {
95
107
  isInitiallyDisabled: false,
96
108
  sdk: sdk,
97
109
  hasCardEditActions: true,
98
110
  viewType: "card",
99
111
  parameters: {}
100
112
  }));
101
- const button = await _react1.screen.findByText('Add existing content');
113
+ const button = await _react.screen.findByText('Add existing content');
102
114
  expect(button).toBeDefined();
103
- _react1.fireEvent.click(button);
115
+ _react.fireEvent.click(button);
104
116
  const dialogFn = sdk.dialogs.selectMultipleResourceEntities;
105
117
  expect(dialogFn).toHaveBeenCalledTimes(1);
106
118
  const options = dialogFn.mock.calls[0][0];
@@ -108,19 +120,35 @@ describe('Multiple resource editor', ()=>{
108
120
  allowedResources: fieldDefinition.allowedResources
109
121
  });
110
122
  });
123
+ it('hides the action button when insufficient permissions', async ()=>{
124
+ mockedUseEditorPermissions.mockImplementation(()=>({
125
+ canLinkEntity: false
126
+ }));
127
+ const sdk = (0, _resourceEditorHelpers.mockSdkForField)(fieldDefinition);
128
+ (0, _react.render)(_react1.createElement(_MultipleResourceReferenceEditor.MultipleResourceReferenceEditor, {
129
+ getEntryRouteHref: ()=>'',
130
+ isInitiallyDisabled: false,
131
+ sdk: sdk,
132
+ hasCardEditActions: true,
133
+ viewType: "card",
134
+ parameters: {}
135
+ }));
136
+ const noPermission = await _react.screen.findByText(/You don't have permission to view this content/);
137
+ expect(noPermission).toBeDefined();
138
+ });
111
139
  it('renders custom actions when passed', async ()=>{
112
140
  const sdk = (0, _resourceEditorHelpers.mockSdkForField)(fieldDefinition);
113
- (0, _react1.render)(_react.createElement(_MultipleResourceReferenceEditor.MultipleResourceReferenceEditor, {
141
+ (0, _react.render)(_react1.createElement(_MultipleResourceReferenceEditor.MultipleResourceReferenceEditor, {
114
142
  isInitiallyDisabled: false,
115
143
  sdk: sdk,
116
144
  hasCardEditActions: true,
117
145
  viewType: "card",
118
146
  parameters: {},
119
- renderCustomActions: ()=>_react.createElement("div", {
147
+ renderCustomActions: ()=>_react1.createElement("div", {
120
148
  "data-testid": "custom-actions"
121
149
  })
122
150
  }));
123
- const customActions = await _react1.screen.findByTestId('custom-actions');
151
+ const customActions = await _react.screen.findByTestId('custom-actions');
124
152
  expect(customActions).toBeDefined();
125
153
  });
126
154
  describe('with value', ()=>{
@@ -131,7 +159,7 @@ describe('Multiple resource editor', ()=>{
131
159
  mockedResources[`${link.sys.linkType}.${link.sys.urn}`] = entryInfos[spaceId];
132
160
  }
133
161
  const sdk = (0, _resourceEditorHelpers.mockSdkForField)(fieldDefinition, Object.values(entryLinks));
134
- (0, _react1.render)(_react.createElement(_MultipleResourceReferenceEditor.MultipleResourceReferenceEditor, {
162
+ (0, _react.render)(_react1.createElement(_MultipleResourceReferenceEditor.MultipleResourceReferenceEditor, {
135
163
  isInitiallyDisabled: false,
136
164
  sdk: sdk,
137
165
  hasCardEditActions: true,
@@ -158,7 +186,7 @@ describe('Multiple resource editor', ()=>{
158
186
  mockedResources[`${link.sys.linkType}.${link.sys.urn}`] = entryInfos[spaceId];
159
187
  }
160
188
  const sdk = (0, _resourceEditorHelpers.mockSdkForField)(fieldDefinition, Object.values(entryLinks));
161
- (0, _react1.render)(_react.createElement(_MultipleResourceReferenceEditor.MultipleResourceReferenceEditor, {
189
+ (0, _react.render)(_react1.createElement(_MultipleResourceReferenceEditor.MultipleResourceReferenceEditor, {
162
190
  isInitiallyDisabled: false,
163
191
  sdk: sdk,
164
192
  hasCardEditActions: true,
@@ -167,7 +195,7 @@ describe('Multiple resource editor', ()=>{
167
195
  getEntryRouteHref: ()=>'',
168
196
  parameters: {}
169
197
  }));
170
- const linkExistingBtn = _react1.screen.queryByText('Add existing content');
198
+ const linkExistingBtn = _react.screen.queryByText('Add existing content');
171
199
  expect(linkExistingBtn).toBeInTheDocument();
172
200
  const entriesArray = Object.values(entryInfos);
173
201
  const firstItem = entriesArray[0];
@@ -184,7 +212,7 @@ describe('Multiple resource editor', ()=>{
184
212
  mockedResources[`${link.sys.linkType}.${link.sys.urn}`] = entryInfos[spaceId];
185
213
  }
186
214
  const sdk = (0, _resourceEditorHelpers.mockSdkForField)(fieldDefinition, Object.values(entryLinks));
187
- (0, _react1.render)(_react.createElement(_MultipleResourceReferenceEditor.MultipleResourceReferenceEditor, {
215
+ (0, _react.render)(_react1.createElement(_MultipleResourceReferenceEditor.MultipleResourceReferenceEditor, {
188
216
  isInitiallyDisabled: false,
189
217
  sdk: sdk,
190
218
  hasCardEditActions: true,
@@ -193,7 +221,7 @@ describe('Multiple resource editor', ()=>{
193
221
  getEntryRouteHref: ()=>'',
194
222
  parameters: {}
195
223
  }));
196
- const linkExistingBtn = _react1.screen.queryByText('Add existing content');
224
+ const linkExistingBtn = _react.screen.queryByText('Add existing content');
197
225
  expect(linkExistingBtn).toBeInTheDocument();
198
226
  const entriesArray = Object.values(entryInfos);
199
227
  const lastItem = entriesArray[entriesArray.length - 1];
@@ -210,7 +238,7 @@ describe('Multiple resource editor', ()=>{
210
238
  mockedResources[`${link.sys.linkType}.${link.sys.urn}`] = entryInfos[spaceId];
211
239
  }
212
240
  const sdk = (0, _resourceEditorHelpers.mockSdkForField)(fieldDefinition, Object.values(entryLinks));
213
- (0, _react1.render)(_react.createElement(_MultipleResourceReferenceEditor.MultipleResourceReferenceEditor, {
241
+ (0, _react.render)(_react1.createElement(_MultipleResourceReferenceEditor.MultipleResourceReferenceEditor, {
214
242
  isInitiallyDisabled: false,
215
243
  sdk: sdk,
216
244
  hasCardEditActions: true,
@@ -219,15 +247,15 @@ describe('Multiple resource editor', ()=>{
219
247
  getEntryRouteHref: ()=>'',
220
248
  parameters: {}
221
249
  }));
222
- const linkExistingBtn = _react1.screen.queryByText('Add existing content');
250
+ const linkExistingBtn = _react.screen.queryByText('Add existing content');
223
251
  expect(linkExistingBtn).toBeInTheDocument();
224
252
  const entriesArray = Object.values(entryInfos);
225
253
  for (const info of entriesArray){
226
254
  await clickCardActionsButton(info);
227
- const removeBtn = await _react1.screen.findByText('Remove', {
255
+ const removeBtn = await _react.screen.findByText('Remove', {
228
256
  selector: '[role="menuitem"]'
229
257
  });
230
- _react1.fireEvent.click(removeBtn);
258
+ _react.fireEvent.click(removeBtn);
231
259
  }
232
260
  expect(sdk.field.setValue).toHaveBeenCalledTimes(3);
233
261
  expect(sdk.field.setValue).toHaveBeenCalledWith([]);
@@ -237,28 +265,28 @@ describe('Multiple resource editor', ()=>{
237
265
  });
238
266
  async function expectToHaveMoveButton(info, buttonString) {
239
267
  await clickCardActionsButton(info);
240
- await _react1.screen.findByText(buttonString, {
268
+ await _react.screen.findByText(buttonString, {
241
269
  selector: '[role="menuitem"]'
242
270
  });
243
271
  }
244
272
  async function expectToNotHaveMoveButton(info, buttonString) {
245
273
  await clickCardActionsButton(info);
246
- expect(_react1.screen.queryByText(buttonString, {
274
+ expect(_react.screen.queryByText(buttonString, {
247
275
  selector: '[role="menuitem"]'
248
276
  })).toBeNull();
249
277
  }
250
278
  async function clickCardActionsButton(info) {
251
- _react1.fireEvent.click(document.body);
279
+ _react.fireEvent.click(document.body);
252
280
  const entryTitle = info.resource.fields.title[info.defaultLocaleCode];
253
281
  const spaceName = info.space.name;
254
282
  const card = await expectEntryCard(entryTitle, spaceName);
255
283
  const actionsBtn = card.querySelector('[data-test-id="cf-ui-card-actions"]');
256
284
  expect(actionsBtn).toBeInTheDocument();
257
- _react1.fireEvent.click(actionsBtn);
285
+ _react.fireEvent.click(actionsBtn);
258
286
  }
259
287
  async function expectEntryCard(entryTitle, spaceName) {
260
- const title = await _react1.screen.findByText(entryTitle);
261
- await _react1.screen.findByText(spaceName);
288
+ const title = await _react.screen.findByText(entryTitle);
289
+ await _react.screen.findByText(spaceName);
262
290
  const theCard = title.closest('[data-test-id="cf-ui-entry-card"]');
263
291
  expect(theCard).toBeDefined();
264
292
  const actionsBtn = theCard?.querySelector('[data-test-id="cf-ui-card-actions"]');
@@ -56,11 +56,9 @@ function _interop_require_wildcard(obj, nodeInterop) {
56
56
  return newObj;
57
57
  }
58
58
  function SingleResourceReferenceEditor(props) {
59
- const { dialogs, field } = props.sdk;
60
59
  const linkActionsProps = (0, _useResourceLinkActions.useResourceLinkActions)({
61
- dialogs,
62
- field,
63
- apiUrl: props.apiUrl
60
+ sdk: props.sdk,
61
+ parameters: props.parameters
64
62
  });
65
63
  return _react.createElement(_EntityStore.EntityProvider, {
66
64
  sdk: props.sdk
@@ -6,6 +6,7 @@ const _react = _interop_require_wildcard(require("react"));
6
6
  require("@testing-library/jest-dom/extend-expect");
7
7
  const _react1 = require("@testing-library/react");
8
8
  const _EntityStore = require("../common/EntityStore");
9
+ const _useEditorPermissions = require("../common/useEditorPermissions");
9
10
  const _SingleResourceReferenceEditor = require("./SingleResourceReferenceEditor");
10
11
  const _resourceEditorHelpers = require("./testHelpers/resourceEditorHelpers");
11
12
  function _getRequireWildcardCache(nodeInterop) {
@@ -69,6 +70,11 @@ jest.mock('react-intersection-observer', ()=>{
69
70
  })
70
71
  };
71
72
  });
73
+ jest.mock('../common/useEditorPermissions', ()=>{
74
+ return {
75
+ useEditorPermissions: jest.fn()
76
+ };
77
+ });
72
78
  const fieldDefinition = {
73
79
  type: 'ResourceLink',
74
80
  id: 'foo',
@@ -84,6 +90,12 @@ const fieldDefinition = {
84
90
  required: true,
85
91
  validations: []
86
92
  };
93
+ const mockedUseEditorPermissions = _useEditorPermissions.useEditorPermissions;
94
+ beforeEach(()=>{
95
+ mockedUseEditorPermissions.mockImplementation(()=>({
96
+ canLinkEntity: true
97
+ }));
98
+ });
87
99
  describe('Single resource editor', ()=>{
88
100
  it('renders the action button when no value is set', async ()=>{
89
101
  const sdk = (0, _resourceEditorHelpers.mockSdkForField)(fieldDefinition);
@@ -104,6 +116,21 @@ describe('Single resource editor', ()=>{
104
116
  allowedResources: fieldDefinition.allowedResources
105
117
  });
106
118
  });
119
+ it('renders no the action button when permissions insufficient', async ()=>{
120
+ mockedUseEditorPermissions.mockImplementation(()=>({
121
+ canLinkEntity: false
122
+ }));
123
+ const sdk = (0, _resourceEditorHelpers.mockSdkForField)(fieldDefinition);
124
+ (0, _react1.render)(_react.createElement(_SingleResourceReferenceEditor.SingleResourceReferenceEditor, {
125
+ isInitiallyDisabled: false,
126
+ sdk: sdk,
127
+ hasCardEditActions: true,
128
+ viewType: "card",
129
+ parameters: {}
130
+ }));
131
+ const noPermission = await _react1.screen.findByText(/You don't have permission to view this content/);
132
+ expect(noPermission).toBeDefined();
133
+ });
107
134
  it('renders custom actions when passed', async ()=>{
108
135
  const sdk = (0, _resourceEditorHelpers.mockSdkForField)(fieldDefinition);
109
136
  (0, _react1.render)(_react.createElement(_SingleResourceReferenceEditor.SingleResourceReferenceEditor, {
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "useResourceLinkActions", {
9
9
  }
10
10
  });
11
11
  const _react = require("react");
12
+ const _useEditorPermissions = require("../common/useEditorPermissions");
12
13
  const getUpdatedValue = (field, linkItems)=>{
13
14
  const multiple = field.type === 'Array';
14
15
  if (multiple) {
@@ -21,7 +22,8 @@ const getUpdatedValue = (field, linkItems)=>{
21
22
  return linkItems[0];
22
23
  }
23
24
  };
24
- function useResourceLinkActions({ dialogs, field }) {
25
+ function useResourceLinkActions({ parameters, sdk }) {
26
+ const { field, dialogs } = sdk;
25
27
  const onLinkedExisting = (0, _react.useMemo)(()=>{
26
28
  return (links)=>{
27
29
  const updatedValue = getUpdatedValue(field, links);
@@ -50,6 +52,12 @@ function useResourceLinkActions({ dialogs, field }) {
50
52
  multiple,
51
53
  onLinkedExisting
52
54
  ]);
55
+ const { canLinkEntity } = (0, _useEditorPermissions.useEditorPermissions)({
56
+ entityType: 'Entry',
57
+ allContentTypes: [],
58
+ sdk,
59
+ parameters
60
+ });
53
61
  return {
54
62
  onLinkExisting,
55
63
  onLinkedExisting,
@@ -57,7 +65,7 @@ function useResourceLinkActions({ dialogs, field }) {
57
65
  contentTypes: [],
58
66
  canCreateEntity: false,
59
67
  canLinkMultiple: multiple,
60
- canLinkEntity: true,
68
+ canLinkEntity,
61
69
  isDisabled: false,
62
70
  isEmpty: false,
63
71
  isFull: false,
@@ -1,37 +1,43 @@
1
1
  import { useEffect, useMemo, useState } from 'react';
2
+ import { isEqual } from 'lodash';
2
3
  import { useAccessApi } from './useAccessApi';
3
4
  async function filter(arr, predicate) {
4
5
  const fail = Symbol();
5
6
  const results = await Promise.all(arr.map(async (item)=>await predicate(item) ? item : fail));
6
7
  return results.filter((x)=>x !== fail);
7
8
  }
8
- export function useContentTypePermissions(props) {
9
+ export function useContentTypePermissions({ entityType, validations, sdk, allContentTypes }) {
9
10
  const availableContentTypes = useMemo(()=>{
10
- if (props.entityType === 'Asset') {
11
+ if (entityType === 'Asset') {
11
12
  return [];
12
13
  }
13
- if (props.validations.contentTypes) {
14
- return props.allContentTypes.filter((ct)=>props.validations.contentTypes?.includes(ct.sys.id));
14
+ if (validations.contentTypes) {
15
+ return allContentTypes.filter((ct)=>validations.contentTypes?.includes(ct.sys.id));
15
16
  }
16
- return props.allContentTypes;
17
+ return allContentTypes;
17
18
  }, [
18
- props.allContentTypes,
19
- props.validations.contentTypes,
20
- props.entityType
19
+ allContentTypes,
20
+ validations.contentTypes,
21
+ entityType
21
22
  ]);
22
23
  const [creatableContentTypes, setCreatableContentTypes] = useState(availableContentTypes);
23
- const { canPerformActionOnEntryOfType } = useAccessApi(props.sdk.access);
24
+ const { canPerformActionOnEntryOfType } = useAccessApi(sdk.access);
24
25
  useEffect(()=>{
25
26
  function getContentTypes(action) {
26
27
  return filter(availableContentTypes, (ct)=>canPerformActionOnEntryOfType(action, ct.sys.id));
27
28
  }
28
29
  async function checkContentTypeAccess() {
29
30
  const creatable = await getContentTypes('create');
30
- setCreatableContentTypes(creatable);
31
+ if (!isEqual(creatable, creatableContentTypes)) {
32
+ setCreatableContentTypes(creatable);
33
+ }
34
+ }
35
+ if (availableContentTypes.length > 0) {
36
+ void checkContentTypeAccess();
31
37
  }
32
- void checkContentTypeAccess();
33
38
  }, [
34
- availableContentTypes
39
+ availableContentTypes,
40
+ creatableContentTypes
35
41
  ]);
36
42
  return {
37
43
  creatableContentTypes,
@@ -2,16 +2,18 @@ import { useEffect, useMemo, useState } from 'react';
2
2
  import { fromFieldValidations } from '../utils/fromFieldValidations';
3
3
  import { useAccessApi } from './useAccessApi';
4
4
  import { useContentTypePermissions } from './useContentTypePermissions';
5
- export function useEditorPermissions(props) {
6
- const { sdk, entityType, parameters } = props;
7
- const validations = useMemo(()=>fromFieldValidations(props.sdk.field), [
8
- props.sdk.field
5
+ export function useEditorPermissions({ sdk, entityType, parameters, allContentTypes }) {
6
+ const validations = useMemo(()=>fromFieldValidations(sdk.field), [
7
+ sdk.field
9
8
  ]);
10
9
  const [canCreateEntity, setCanCreateEntity] = useState(true);
11
10
  const [canLinkEntity, setCanLinkEntity] = useState(true);
12
11
  const { creatableContentTypes, availableContentTypes } = useContentTypePermissions({
13
- ...props,
14
- validations
12
+ entityType,
13
+ validations,
14
+ sdk,
15
+ allContentTypes,
16
+ parameters
15
17
  });
16
18
  const { canPerformAction } = useAccessApi(sdk.access);
17
19
  useEffect(()=>{
@@ -14,7 +14,7 @@ describe('useEditorPermissions', ()=>{
14
14
  id
15
15
  }
16
16
  });
17
- const renderEditorPermissions = async ({ entityType, params = {}, allContentTypes = [], customizeMock, customizeSdk })=>{
17
+ const renderEditorPermissions = async ({ entityType, params = {}, allContentTypes = [], customizeMock, customizeSdk, waitForUpdate = false })=>{
18
18
  const sdk = makeFieldAppSDK(customizeMock);
19
19
  customizeSdk?.(sdk);
20
20
  const renderResult = renderHook(()=>useEditorPermissions({
@@ -25,7 +25,9 @@ describe('useEditorPermissions', ()=>{
25
25
  instance: params
26
26
  }
27
27
  }));
28
- await renderResult.waitForNextUpdate();
28
+ if (waitForUpdate) {
29
+ await renderResult.waitForNextUpdate();
30
+ }
29
31
  return {
30
32
  ...renderResult,
31
33
  sdk
@@ -98,7 +100,8 @@ describe('useEditorPermissions', ()=>{
98
100
  allContentTypes,
99
101
  customizeSdk: (sdk)=>{
100
102
  allowContentTypes(sdk, 'create', 'one');
101
- }
103
+ },
104
+ waitForUpdate: true
102
105
  });
103
106
  expect(result.current.canCreateEntity).toBe(true);
104
107
  });
@@ -126,7 +129,8 @@ describe('useEditorPermissions', ()=>{
126
129
  allContentTypes,
127
130
  customizeSdk: (sdk)=>{
128
131
  allowContentTypes(sdk, 'read', 'one');
129
- }
132
+ },
133
+ waitForUpdate: true
130
134
  });
131
135
  expect(result.current.canLinkEntity).toBe(true);
132
136
  });
@@ -9,7 +9,7 @@ import { CombinedLinkEntityActions } from '../components/LinkActions/LinkEntityA
9
9
  import { ResourceCard } from './Cards/ResourceCard';
10
10
  import { useResourceLinkActions } from './useResourceLinkActions';
11
11
  function ResourceEditor(props) {
12
- const { setValue, items, apiUrl } = props;
12
+ const { setValue, items } = props;
13
13
  const onSortStart = ()=>noop();
14
14
  const onSortEnd = useCallback(({ oldIndex, newIndex })=>{
15
15
  const newItems = arrayMove(items, oldIndex, newIndex);
@@ -31,11 +31,9 @@ function ResourceEditor(props) {
31
31
  items,
32
32
  setValue
33
33
  ]);
34
- const { dialogs, field } = props.sdk;
35
34
  const linkActionsProps = useResourceLinkActions({
36
- dialogs,
37
- field,
38
- apiUrl
35
+ sdk: props.sdk,
36
+ parameters: props.parameters
39
37
  });
40
38
  return React.createElement(React.Fragment, null, props.children({
41
39
  ...props,
@@ -1,7 +1,8 @@
1
- import * as React from 'react';
2
1
  import '@testing-library/jest-dom/extend-expect';
3
2
  import { fireEvent, render, screen } from '@testing-library/react';
3
+ import * as React from 'react';
4
4
  import { useResource } from '../common/EntityStore';
5
+ import { useEditorPermissions } from '../common/useEditorPermissions';
5
6
  import { MultipleResourceReferenceEditor } from './MultipleResourceReferenceEditor';
6
7
  import { createFakeEntryResource, mockSdkForField } from './testHelpers/resourceEditorHelpers';
7
8
  let mockedResources = {};
@@ -16,6 +17,11 @@ jest.mock('../common/EntityStore', ()=>{
16
17
  }))
17
18
  };
18
19
  });
20
+ jest.mock('../common/useEditorPermissions', ()=>{
21
+ return {
22
+ useEditorPermissions: jest.fn()
23
+ };
24
+ });
19
25
  jest.mock('react-intersection-observer', ()=>{
20
26
  const module = jest.requireActual('react-intersection-observer');
21
27
  return {
@@ -43,6 +49,12 @@ const fieldDefinition = {
43
49
  required: true,
44
50
  validations: []
45
51
  };
52
+ const mockedUseEditorPermissions = useEditorPermissions;
53
+ beforeEach(()=>{
54
+ mockedUseEditorPermissions.mockImplementation(()=>({
55
+ canLinkEntity: true
56
+ }));
57
+ });
46
58
  describe('Multiple resource editor', ()=>{
47
59
  it('renders the action button when no value is set', async ()=>{
48
60
  const sdk = mockSdkForField(fieldDefinition);
@@ -63,6 +75,22 @@ describe('Multiple resource editor', ()=>{
63
75
  allowedResources: fieldDefinition.allowedResources
64
76
  });
65
77
  });
78
+ it('hides the action button when insufficient permissions', async ()=>{
79
+ mockedUseEditorPermissions.mockImplementation(()=>({
80
+ canLinkEntity: false
81
+ }));
82
+ const sdk = mockSdkForField(fieldDefinition);
83
+ render(React.createElement(MultipleResourceReferenceEditor, {
84
+ getEntryRouteHref: ()=>'',
85
+ isInitiallyDisabled: false,
86
+ sdk: sdk,
87
+ hasCardEditActions: true,
88
+ viewType: "card",
89
+ parameters: {}
90
+ }));
91
+ const noPermission = await screen.findByText(/You don't have permission to view this content/);
92
+ expect(noPermission).toBeDefined();
93
+ });
66
94
  it('renders custom actions when passed', async ()=>{
67
95
  const sdk = mockSdkForField(fieldDefinition);
68
96
  render(React.createElement(MultipleResourceReferenceEditor, {
@@ -5,11 +5,9 @@ import { CombinedLinkEntityActions } from '../components/LinkActions/LinkEntityA
5
5
  import { ResourceCard } from './Cards/ResourceCard';
6
6
  import { useResourceLinkActions } from './useResourceLinkActions';
7
7
  export function SingleResourceReferenceEditor(props) {
8
- const { dialogs, field } = props.sdk;
9
8
  const linkActionsProps = useResourceLinkActions({
10
- dialogs,
11
- field,
12
- apiUrl: props.apiUrl
9
+ sdk: props.sdk,
10
+ parameters: props.parameters
13
11
  });
14
12
  return React.createElement(EntityProvider, {
15
13
  sdk: props.sdk
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
  import '@testing-library/jest-dom/extend-expect';
3
3
  import { fireEvent, render, screen } from '@testing-library/react';
4
4
  import { useResource } from '../common/EntityStore';
5
+ import { useEditorPermissions } from '../common/useEditorPermissions';
5
6
  import { SingleResourceReferenceEditor } from './SingleResourceReferenceEditor';
6
7
  import { createFakeEntryResource, mockSdkForField } from './testHelpers/resourceEditorHelpers';
7
8
  const mockedResources = {};
@@ -24,6 +25,11 @@ jest.mock('react-intersection-observer', ()=>{
24
25
  })
25
26
  };
26
27
  });
28
+ jest.mock('../common/useEditorPermissions', ()=>{
29
+ return {
30
+ useEditorPermissions: jest.fn()
31
+ };
32
+ });
27
33
  const fieldDefinition = {
28
34
  type: 'ResourceLink',
29
35
  id: 'foo',
@@ -39,6 +45,12 @@ const fieldDefinition = {
39
45
  required: true,
40
46
  validations: []
41
47
  };
48
+ const mockedUseEditorPermissions = useEditorPermissions;
49
+ beforeEach(()=>{
50
+ mockedUseEditorPermissions.mockImplementation(()=>({
51
+ canLinkEntity: true
52
+ }));
53
+ });
42
54
  describe('Single resource editor', ()=>{
43
55
  it('renders the action button when no value is set', async ()=>{
44
56
  const sdk = mockSdkForField(fieldDefinition);
@@ -59,6 +71,21 @@ describe('Single resource editor', ()=>{
59
71
  allowedResources: fieldDefinition.allowedResources
60
72
  });
61
73
  });
74
+ it('renders no the action button when permissions insufficient', async ()=>{
75
+ mockedUseEditorPermissions.mockImplementation(()=>({
76
+ canLinkEntity: false
77
+ }));
78
+ const sdk = mockSdkForField(fieldDefinition);
79
+ render(React.createElement(SingleResourceReferenceEditor, {
80
+ isInitiallyDisabled: false,
81
+ sdk: sdk,
82
+ hasCardEditActions: true,
83
+ viewType: "card",
84
+ parameters: {}
85
+ }));
86
+ const noPermission = await screen.findByText(/You don't have permission to view this content/);
87
+ expect(noPermission).toBeDefined();
88
+ });
62
89
  it('renders custom actions when passed', async ()=>{
63
90
  const sdk = mockSdkForField(fieldDefinition);
64
91
  render(React.createElement(SingleResourceReferenceEditor, {
@@ -1,4 +1,5 @@
1
1
  import { useMemo } from 'react';
2
+ import { useEditorPermissions } from '../common/useEditorPermissions';
2
3
  const getUpdatedValue = (field, linkItems)=>{
3
4
  const multiple = field.type === 'Array';
4
5
  if (multiple) {
@@ -11,7 +12,8 @@ const getUpdatedValue = (field, linkItems)=>{
11
12
  return linkItems[0];
12
13
  }
13
14
  };
14
- export function useResourceLinkActions({ dialogs, field }) {
15
+ export function useResourceLinkActions({ parameters, sdk }) {
16
+ const { field, dialogs } = sdk;
15
17
  const onLinkedExisting = useMemo(()=>{
16
18
  return (links)=>{
17
19
  const updatedValue = getUpdatedValue(field, links);
@@ -40,6 +42,12 @@ export function useResourceLinkActions({ dialogs, field }) {
40
42
  multiple,
41
43
  onLinkedExisting
42
44
  ]);
45
+ const { canLinkEntity } = useEditorPermissions({
46
+ entityType: 'Entry',
47
+ allContentTypes: [],
48
+ sdk,
49
+ parameters
50
+ });
43
51
  return {
44
52
  onLinkExisting,
45
53
  onLinkedExisting,
@@ -47,7 +55,7 @@ export function useResourceLinkActions({ dialogs, field }) {
47
55
  contentTypes: [],
48
56
  canCreateEntity: false,
49
57
  canLinkMultiple: multiple,
50
- canLinkEntity: true,
58
+ canLinkEntity,
51
59
  isDisabled: false,
52
60
  isEmpty: false,
53
61
  isFull: false,
@@ -12,5 +12,5 @@ type ContentTypePermissions = {
12
12
  creatableContentTypes: ContentType[];
13
13
  availableContentTypes: ContentType[];
14
14
  };
15
- export declare function useContentTypePermissions(props: ContentTypePermissionsProps): ContentTypePermissions;
15
+ export declare function useContentTypePermissions({ entityType, validations, sdk, allContentTypes, }: ContentTypePermissionsProps): ContentTypePermissions;
16
16
  export {};
@@ -6,7 +6,7 @@ export type EditorPermissionsProps = {
6
6
  parameters: ReferenceEditorProps['parameters'];
7
7
  allContentTypes: ContentType[];
8
8
  };
9
- export declare function useEditorPermissions(props: EditorPermissionsProps): {
9
+ export declare function useEditorPermissions({ sdk, entityType, parameters, allContentTypes, }: EditorPermissionsProps): {
10
10
  canCreateEntity: boolean;
11
11
  canLinkEntity: boolean;
12
12
  creatableContentTypes: ContentType[];
@@ -1,5 +1,5 @@
1
- import type { FieldAppSDK } from '@contentful/app-sdk';
1
+ import { EditorPermissionsProps } from '../common/useEditorPermissions';
2
2
  import { LinkActionsProps } from '../components';
3
- export declare function useResourceLinkActions({ dialogs, field, }: Pick<FieldAppSDK, 'field' | 'dialogs'> & {
4
- apiUrl: string;
5
- }): LinkActionsProps;
3
+ type ResourceLinkActionProps = Pick<EditorPermissionsProps, 'parameters' | 'sdk'>;
4
+ export declare function useResourceLinkActions({ parameters, sdk, }: ResourceLinkActionProps): LinkActionsProps;
5
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/field-editor-reference",
3
- "version": "6.3.3",
3
+ "version": "6.4.2",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/esm/index.js",
6
6
  "types": "dist/types/index.d.ts",
@@ -64,5 +64,5 @@
64
64
  "publishConfig": {
65
65
  "registry": "https://npm.pkg.github.com/"
66
66
  },
67
- "gitHead": "bf46c70f90d83e11e01091e8f0f50dfa27258bdf"
67
+ "gitHead": "6eb05bdb86788fa95e73f9ac8ee846fae69876a1"
68
68
  }