@truedat/dd 8.12.2 → 8.12.5

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.
Files changed (91) hide show
  1. package/package.json +3 -3
  2. package/src/api/__tests__/queries.spec.js +55 -0
  3. package/src/api/queries.js +31 -0
  4. package/src/components/Grant.js +106 -16
  5. package/src/components/GrantRequest.js +2 -2
  6. package/src/components/GrantRequestBulkApprovalPopup.js +0 -1
  7. package/src/components/GrantsDownloadButton.js +9 -1
  8. package/src/components/GrantsTable.js +4 -1
  9. package/src/components/StructureFields.js +7 -4
  10. package/src/components/StructureGrantCartUserSelector.js +29 -4
  11. package/src/components/StructureGrantDropdown.js +7 -1
  12. package/src/components/StructureGrants.js +4 -1
  13. package/src/components/StructureNoteActions.js +10 -1
  14. package/src/components/StructureNotes.js +42 -6
  15. package/src/components/StructureNotesEdit.js +38 -8
  16. package/src/components/StructuresUpdateOption.js +2 -2
  17. package/src/components/SystemStructures.js +3 -3
  18. package/src/components/__tests__/CatalogViewConfigForm.spec.js +2 -0
  19. package/src/components/__tests__/EditDomainForm.spec.js +3 -1
  20. package/src/components/__tests__/Grant.spec.js +171 -0
  21. package/src/components/__tests__/GrantApprovalRuleForm.spec.js +2 -0
  22. package/src/components/__tests__/GrantApprovalRuleNew.spec.js +8 -0
  23. package/src/components/__tests__/GrantApprovalRules.spec.js +4 -0
  24. package/src/components/__tests__/GrantRequest.spec.js +4 -4
  25. package/src/components/__tests__/GrantRequestBulkActions.spec.js +1 -4
  26. package/src/components/__tests__/GrantRequestsSearchResults.spec.js +2 -0
  27. package/src/components/__tests__/GrantsDownloadButton.spec.js +58 -2
  28. package/src/components/__tests__/GrantsTable.spec.js +22 -2
  29. package/src/components/__tests__/LineageDefs.spec.js +6 -1
  30. package/src/components/__tests__/LineageForm.spec.js +1 -0
  31. package/src/components/__tests__/LineageGraphToolbar.spec.js +19 -17
  32. package/src/components/__tests__/LineageInspectorPanel.spec.js +17 -9
  33. package/src/components/__tests__/ProfileGroup.spec.js +7 -1
  34. package/src/components/__tests__/ReferenceDatasetForm.spec.js +2 -0
  35. package/src/components/__tests__/ReferenceDatasetRoutes.spec.js +9 -0
  36. package/src/components/__tests__/StructureGrantCartCheckout.spec.js +39 -1
  37. package/src/components/__tests__/StructureGrantCartInformation.spec.js +5 -2
  38. package/src/components/__tests__/StructureGrantCartUserSelector.spec.js +128 -0
  39. package/src/components/__tests__/StructureGrantDropdown.spec.js +92 -0
  40. package/src/components/__tests__/StructureGrantListButton.spec.js +70 -0
  41. package/src/components/__tests__/StructureGrantSummaryButton.spec.js +36 -1
  42. package/src/components/__tests__/StructureGrants.spec.js +12 -0
  43. package/src/components/__tests__/StructureNoteActions.spec.js +96 -0
  44. package/src/components/__tests__/StructureNotes.spec.js +170 -1
  45. package/src/components/__tests__/StructureNotesEdit.spec.js +185 -1
  46. package/src/components/__tests__/StructureRelationRow.spec.js +72 -4
  47. package/src/components/__tests__/StructureRelations.spec.js +18 -0
  48. package/src/components/__tests__/StructureRoles.spec.js +9 -0
  49. package/src/components/__tests__/StructureRow.spec.js +13 -4
  50. package/src/components/__tests__/StructureSelector.spec.js +2 -0
  51. package/src/components/__tests__/StructureTag.spec.js +2 -0
  52. package/src/components/__tests__/StructureTagForm.spec.js +2 -0
  53. package/src/components/__tests__/StructureType.spec.js +3 -1
  54. package/src/components/__tests__/StructureTypeForm.spec.js +3 -1
  55. package/src/components/__tests__/StructureView.spec.js +17 -1
  56. package/src/components/__tests__/StructuresGrantRequestView.spec.js +2 -0
  57. package/src/components/__tests__/StructuresView.spec.js +2 -1
  58. package/src/components/__tests__/Tag.spec.js +4 -1
  59. package/src/components/__tests__/Tags.spec.js +7 -1
  60. package/src/components/__tests__/__snapshots__/GrantsTable.spec.js.snap +34 -2
  61. package/src/components/__tests__/__snapshots__/LineageDefs.spec.js.snap +32 -30
  62. package/src/components/__tests__/__snapshots__/StructureFields.spec.js.snap +1 -16
  63. package/src/components/__tests__/__snapshots__/StructureNoteActions.spec.js.snap +2 -1
  64. package/src/components/__tests__/__snapshots__/StructureNotesEdit.spec.js.snap +3 -0
  65. package/src/components/__tests__/__snapshots__/StructureRelationRow.spec.js.snap +23 -19
  66. package/src/components/__tests__/__snapshots__/StructureRow.spec.js.snap +28 -24
  67. package/src/components/__tests__/__snapshots__/StructuresUpdateOption.spec.js.snap +7 -3
  68. package/src/messages/en.js +2 -2
  69. package/src/messages/es.js +2 -2
  70. package/src/reducers/__tests__/ddMessage.spec.js +48 -0
  71. package/src/reducers/__tests__/grantRequestsCart.spec.js +17 -0
  72. package/src/reducers/__tests__/structureRedirect.spec.js +22 -0
  73. package/src/reducers/__tests__/structureRelations.spec.js +28 -0
  74. package/src/reducers/ddMessage.js +32 -0
  75. package/src/reducers/grantRequestsCart.js +3 -0
  76. package/src/reducers/structureRedirect.js +7 -2
  77. package/src/selectors/__tests__/__snapshots__/getGrantsColumns.spec.js.snap +1 -9
  78. package/src/selectors/__tests__/getGrantRequestsSearchColumns.spec.js +11 -4
  79. package/src/selectors/__tests__/getGrantsColumns.spec.js +295 -0
  80. package/src/selectors/__tests__/messages.spec.js +14 -0
  81. package/src/selectors/getBusinessConceptsLinksToStructuresColumnsSelector.js +3 -5
  82. package/src/selectors/getGrantRequestsSearchColumns.js +3 -5
  83. package/src/selectors/getGrantsColumns.js +85 -11
  84. package/src/selectors/getLinkedImplementationsToStructuresColumns.js +3 -7
  85. package/src/selectors/getStructureFieldColumns.js +7 -6
  86. package/src/selectors/getStructureProperties.js +2 -5
  87. package/src/selectors/getStructuresUploadEventColumns.js +2 -7
  88. package/src/selectors/messages.js +30 -0
  89. package/src/selectors/structureColumnsSelector.js +13 -16
  90. package/src/utils/__tests__/structureNoteActions.spec.js +196 -0
  91. package/src/utils/structureNoteActions.js +22 -0
@@ -202,4 +202,100 @@ describe("<StructureNoteActions />", () => {
202
202
  .querySelector(".clipboard.check.icon"),
203
203
  ).toBeInTheDocument();
204
204
  });
205
+
206
+ it("disables 'pending_approval' when mandatory fields are missing", async () => {
207
+ const doStructureNoteAction = jest.fn();
208
+ const template = {
209
+ content: [
210
+ {
211
+ name: "g1",
212
+ fields: [{ name: "required", type: "string", cardinality: "1" }],
213
+ },
214
+ ],
215
+ };
216
+ const props = {
217
+ doStructureNoteAction,
218
+ actions: {
219
+ pending_approval: {
220
+ href: "url",
221
+ input: { status: "pending_approval" },
222
+ method: "PATCH",
223
+ },
224
+ },
225
+ structure: { id: 1 },
226
+ template,
227
+ content: {},
228
+ };
229
+
230
+ const rendered = render(<StructureNoteActions {...props} />);
231
+ await waitForLoad(rendered);
232
+
233
+ const item = (await rendered.findByText(/pending_approval/i)).closest(
234
+ ".item",
235
+ );
236
+ expect(item).toHaveClass("disabled");
237
+ expect(doStructureNoteAction).not.toHaveBeenCalled();
238
+ });
239
+
240
+ it("disables 'published' when mandatory fields are missing", async () => {
241
+ const doStructureNoteAction = jest.fn();
242
+ const template = {
243
+ content: [
244
+ {
245
+ name: "g1",
246
+ fields: [{ name: "required", type: "string", cardinality: "1" }],
247
+ },
248
+ ],
249
+ };
250
+ const props = {
251
+ doStructureNoteAction,
252
+ actions: {
253
+ published: {
254
+ href: "url",
255
+ input: { status: "published" },
256
+ method: "PATCH",
257
+ },
258
+ },
259
+ structure: { id: 1 },
260
+ template,
261
+ content: {},
262
+ };
263
+
264
+ const rendered = render(<StructureNoteActions {...props} />);
265
+ await waitForLoad(rendered);
266
+
267
+ const item = (await rendered.findByText(/published/i)).closest(".item");
268
+ expect(item).toHaveClass("disabled");
269
+ expect(doStructureNoteAction).not.toHaveBeenCalled();
270
+ });
271
+
272
+ it("keeps 'edited' enabled when mandatory fields are missing", async () => {
273
+ const doStructureNoteAction = jest.fn();
274
+ const template = {
275
+ content: [
276
+ {
277
+ name: "g1",
278
+ fields: [{ name: "required", type: "string", cardinality: "1" }],
279
+ },
280
+ ],
281
+ };
282
+ const props = {
283
+ doStructureNoteAction,
284
+ actions: {
285
+ edited: {
286
+ href: "edit-url",
287
+ method: "GET",
288
+ },
289
+ },
290
+ structure: { id: 1 },
291
+ template,
292
+ content: {},
293
+ };
294
+
295
+ const rendered = render(<StructureNoteActions {...props} />);
296
+ await waitForLoad(rendered);
297
+
298
+ const item = (await rendered.findByText(/edited/i)).closest(".item");
299
+ expect(item).not.toHaveClass("disabled");
300
+ });
205
301
  });
@@ -1,6 +1,6 @@
1
1
  import userEvent from "@testing-library/user-event";
2
2
  import { render, waitForLoad } from "@truedat/test/render";
3
- import { StructureNotes } from "../StructureNotes";
3
+ import { StructureNotes, findTemplate } from "../StructureNotes";
4
4
 
5
5
  jest.mock("@truedat/df/components/DynamicFormViewer", () => {
6
6
  const DynamicFormViewer = () => <div>DynamicFormViewer</div>;
@@ -111,4 +111,173 @@ describe("<StructureNotes />", () => {
111
111
  );
112
112
  expect(rendered.container).toMatchSnapshot();
113
113
  });
114
+
115
+ it("disables publish/pending_approval when df_content has missing mandatory fields", async () => {
116
+ const props = {
117
+ structure: { id: 1 },
118
+ structureNotes: { _actions: {}, loading: false },
119
+ activeStructureNotes: [
120
+ {
121
+ id: 4,
122
+ status: "draft",
123
+ dynamic_content: null,
124
+ df_content: null,
125
+ version: "4",
126
+ _actions: {
127
+ pending_approval: {
128
+ href: "url",
129
+ input: { status: "pending_approval" },
130
+ method: "PATCH",
131
+ },
132
+ published: {
133
+ href: "url",
134
+ input: { status: "published" },
135
+ method: "PATCH",
136
+ },
137
+ },
138
+ },
139
+ ],
140
+ versionedStructureNotes: [],
141
+ template: {
142
+ id: 2,
143
+ content: [
144
+ {
145
+ name: "g1",
146
+ fields: [{ name: "required", type: "string", cardinality: "1" }],
147
+ },
148
+ ],
149
+ scope: "dd",
150
+ },
151
+ doStructureNoteAction: () => {},
152
+ };
153
+
154
+ const rendered = render(<StructureNotes {...props} />);
155
+ await waitForLoad(rendered);
156
+
157
+ const pendingItem = (
158
+ await rendered.findByText(/pending_approval/i)
159
+ ).closest(".item");
160
+ expect(pendingItem).toHaveClass("disabled");
161
+
162
+ const publishedItem = (await rendered.findByText(/published/i)).closest(
163
+ ".item",
164
+ );
165
+ expect(publishedItem).toHaveClass("disabled");
166
+ });
167
+
168
+ it("shows missing mandatory label in header when draft has missing fields", async () => {
169
+ const props = {
170
+ structure: { id: 1 },
171
+ structureNotes: { _actions: {}, loading: false },
172
+ activeStructureNotes: [
173
+ {
174
+ id: 4,
175
+ status: "draft",
176
+ dynamic_content: null,
177
+ df_content: null,
178
+ version: "4",
179
+ _actions: {
180
+ published: {
181
+ href: "url",
182
+ input: { status: "published" },
183
+ method: "PATCH",
184
+ },
185
+ },
186
+ },
187
+ ],
188
+ versionedStructureNotes: [],
189
+ template: {
190
+ id: 2,
191
+ content: [
192
+ {
193
+ name: "g1",
194
+ fields: [{ name: "required", type: "string", cardinality: "1" }],
195
+ },
196
+ ],
197
+ scope: "dd",
198
+ },
199
+ doStructureNoteAction: () => {},
200
+ };
201
+
202
+ const rendered = render(<StructureNotes {...props} />);
203
+ await waitForLoad(rendered);
204
+
205
+ expect(
206
+ await rendered.findByText(/missing required fields/i),
207
+ ).toBeInTheDocument();
208
+ expect(
209
+ rendered.container.querySelector(".td-missing-mandatory-label"),
210
+ ).toBeInTheDocument();
211
+ });
212
+
213
+ it("does not show missing mandatory label when all fields are complete", async () => {
214
+ const props = {
215
+ structure: { id: 1 },
216
+ structureNotes: { _actions: {}, loading: false },
217
+ activeStructureNotes: [
218
+ {
219
+ id: 4,
220
+ status: "draft",
221
+ dynamic_content: { required: { value: "ok", origin: "user" } },
222
+ df_content: { required: { value: "ok", origin: "user" } },
223
+ version: "4",
224
+ _actions: {
225
+ published: {
226
+ href: "url",
227
+ input: { status: "published" },
228
+ method: "PATCH",
229
+ },
230
+ },
231
+ },
232
+ ],
233
+ versionedStructureNotes: [],
234
+ template: {
235
+ id: 2,
236
+ content: [
237
+ {
238
+ name: "g1",
239
+ fields: [{ name: "required", type: "string", cardinality: "1" }],
240
+ },
241
+ ],
242
+ scope: "dd",
243
+ },
244
+ doStructureNoteAction: () => {},
245
+ };
246
+
247
+ const rendered = render(<StructureNotes {...props} />);
248
+ await waitForLoad(rendered);
249
+
250
+ expect(
251
+ rendered.queryByText(/missing required fields/i),
252
+ ).not.toBeInTheDocument();
253
+ expect(
254
+ rendered.container.querySelector(".td-missing-mandatory-label"),
255
+ ).not.toBeInTheDocument();
256
+ });
257
+
258
+ it("resolves the template from structure_type.template_id", () => {
259
+ const templates = [
260
+ { id: 1, name: "other" },
261
+ { id: 2, name: "Business Concept", content: [] },
262
+ ];
263
+ expect(
264
+ findTemplate({
265
+ templates,
266
+ structure: { structure_type: { template_id: 2 } },
267
+ }),
268
+ ).toEqual(templates[1]);
269
+ expect(
270
+ findTemplate({
271
+ templates,
272
+ structure: { structure_type: { template_id: 1 } },
273
+ }),
274
+ ).toEqual(templates[0]);
275
+ expect(
276
+ findTemplate({
277
+ templates,
278
+ structure: { structure_type: { template: { id: 2 } } },
279
+ }),
280
+ ).toBeUndefined();
281
+ expect(findTemplate({ templates, structure: {} })).toBeUndefined();
282
+ });
114
283
  });
@@ -1,5 +1,5 @@
1
1
  import userEvent from "@testing-library/user-event";
2
- import { waitFor } from "@testing-library/react";
2
+ import { act, waitFor } from "@testing-library/react";
3
3
  import { render, waitForLoad } from "@truedat/test/render";
4
4
  import { applyTemplate } from "@truedat/df/utils";
5
5
  import SuggestionsWidget from "@truedat/ai/components/suggestions/SuggestionsWidget";
@@ -10,9 +10,23 @@ jest.mock("@truedat/ai/components/suggestions/SuggestionsWidget", () => ({
10
10
  default: jest.fn(() => null),
11
11
  }));
12
12
 
13
+ let dynamicFormOnChange;
14
+ let dynamicFormContent;
15
+
16
+ jest.mock("@truedat/df/components/DynamicForm", () => ({
17
+ __esModule: true,
18
+ default: ({ onChange, content }) => {
19
+ dynamicFormOnChange = onChange;
20
+ dynamicFormContent = content;
21
+ return <div data-testid="dynamic-form" />;
22
+ },
23
+ }));
24
+
13
25
  describe("<StructureNotesEdit />", () => {
14
26
  beforeEach(() => {
15
27
  jest.clearAllMocks();
28
+ dynamicFormOnChange = undefined;
29
+ dynamicFormContent = undefined;
16
30
  });
17
31
 
18
32
  it("matches the latest snapshot", async () => {
@@ -121,4 +135,174 @@ describe("<StructureNotesEdit />", () => {
121
135
  }),
122
136
  );
123
137
  });
138
+
139
+ it("preserves explicit empty fields when a field is cleared", async () => {
140
+ const doStructureNoteAction = jest.fn();
141
+ const template = {
142
+ id: 2,
143
+ content: [
144
+ {
145
+ name: "test-group",
146
+ fields: [
147
+ {
148
+ name: "foo",
149
+ type: "string",
150
+ values: null,
151
+ },
152
+ {
153
+ name: "bar",
154
+ type: "string",
155
+ values: null,
156
+ },
157
+ ],
158
+ },
159
+ ],
160
+ };
161
+
162
+ const props = {
163
+ structure: { id: 1 },
164
+ template,
165
+ doStructureNoteAction,
166
+ selectTemplate: () => {},
167
+ selectDomain: () => {},
168
+ structureNotes: {
169
+ structureNotes: [],
170
+ },
171
+ latestDfContent: {
172
+ foo: { value: "foo value", origin: "user" },
173
+ bar: { value: "bar value", origin: "user" },
174
+ },
175
+ applyTemplate: applyTemplate(template),
176
+ selectDomains: jest.fn(),
177
+ };
178
+
179
+ const rendered = render(<StructureNotesEdit {...props} />);
180
+ await waitForLoad(rendered);
181
+
182
+ await act(async () => {
183
+ dynamicFormOnChange({
184
+ foo: { value: "foo value", origin: "user" },
185
+ });
186
+ });
187
+
188
+ const user = userEvent.setup({ delay: null });
189
+ await user.click(await rendered.findByText(/save/i));
190
+
191
+ await waitFor(() => {
192
+ expect(doStructureNoteAction.mock.calls.length).toBe(1);
193
+ });
194
+ expect(doStructureNoteAction).toHaveBeenCalledWith(
195
+ expect.objectContaining({
196
+ action: {
197
+ input: {
198
+ df_content: {
199
+ foo: { value: "foo value", origin: "user" },
200
+ bar: { value: "", origin: "user" },
201
+ },
202
+ },
203
+ },
204
+ dataStructureId: 1,
205
+ redirect: true,
206
+ }),
207
+ );
208
+ });
209
+
210
+ it("does not reset local content when latestDfContent changes)", async () => {
211
+ const doStructureNoteAction = jest.fn();
212
+ const template = {
213
+ id: 2,
214
+ content: [
215
+ {
216
+ name: "test-group",
217
+ fields: [
218
+ {
219
+ name: "foo",
220
+ type: "string",
221
+ values: null,
222
+ },
223
+ ],
224
+ },
225
+ ],
226
+ };
227
+
228
+ const props = {
229
+ structure: { id: 1 },
230
+ template,
231
+ doStructureNoteAction,
232
+ selectTemplate: () => {},
233
+ selectDomain: () => {},
234
+ structureNotes: {
235
+ structureNotes: [],
236
+ },
237
+ latestDfContent: { foo: { value: "saved value", origin: "user" } },
238
+ applyTemplate: applyTemplate(template),
239
+ selectDomains: jest.fn(),
240
+ };
241
+
242
+ const rendered = render(<StructureNotesEdit {...props} />);
243
+ await waitForLoad(rendered);
244
+
245
+ await act(async () => {
246
+ dynamicFormOnChange({
247
+ foo: { value: "local edit", origin: "user" },
248
+ });
249
+ });
250
+ expect(dynamicFormContent).toEqual({
251
+ foo: { value: "local edit", origin: "user" },
252
+ });
253
+
254
+ const updatedProps = {
255
+ ...props,
256
+ latestDfContent: { foo: { value: "saved value", origin: "user" } },
257
+ };
258
+ await act(async () => {
259
+ rendered.rerender(<StructureNotesEdit {...updatedProps} />);
260
+ });
261
+
262
+ expect(dynamicFormContent).toEqual({
263
+ foo: { value: "local edit", origin: "user" },
264
+ });
265
+ });
266
+
267
+ it("Save button is enabled when mandatory fields are missing", async () => {
268
+ const doStructureNoteAction = jest.fn();
269
+ const template = {
270
+ id: 2,
271
+ content: [
272
+ {
273
+ name: "test-group",
274
+ fields: [
275
+ {
276
+ name: "required_field",
277
+ type: "string",
278
+ cardinality: "1",
279
+ values: null,
280
+ },
281
+ ],
282
+ },
283
+ ],
284
+ };
285
+
286
+ const props = {
287
+ structure: { id: 1 },
288
+ template,
289
+ doStructureNoteAction,
290
+ selectTemplate: () => {},
291
+ selectDomain: () => {},
292
+ structureNotes: {
293
+ structureNotes: [],
294
+ },
295
+ latestDfContent: {},
296
+ applyTemplate: applyTemplate(template),
297
+ selectDomains: jest.fn(),
298
+ };
299
+
300
+ const rendered = render(<StructureNotesEdit {...props} />);
301
+ await waitForLoad(rendered);
302
+
303
+ const saveButton = await rendered.findByRole("button", {
304
+ name: /actions\.save/i,
305
+ });
306
+ expect(saveButton).not.toBeDisabled();
307
+ });
124
308
  });
@@ -3,6 +3,16 @@ import { waitFor } from "@testing-library/react";
3
3
  import { render, waitForLoad } from "@truedat/test/render";
4
4
  import _ from "lodash/fp";
5
5
  import { StructureRelationRow } from "../StructureRelationRow";
6
+ import { linksDecorator } from "../../selectors/getStructureFieldColumns";
7
+
8
+ const renderRow = (props) =>
9
+ render(
10
+ <table>
11
+ <tbody>
12
+ <StructureRelationRow {...props} />
13
+ </tbody>
14
+ </table>,
15
+ );
6
16
 
7
17
  describe("<StructureRelationRow />", () => {
8
18
  const structure = { id: 1, name: "Structure 1", type: "Some Type 1" };
@@ -35,7 +45,7 @@ describe("<StructureRelationRow />", () => {
35
45
 
36
46
  it("matches the latest snapshot", async () => {
37
47
  const props = { relation, columns };
38
- const rendered = render(<StructureRelationRow {...props} />);
48
+ const rendered = renderRow(props);
39
49
  await waitForLoad(rendered);
40
50
  expect(rendered.container).toMatchSnapshot();
41
51
  });
@@ -43,7 +53,7 @@ describe("<StructureRelationRow />", () => {
43
53
  it("clicking the row will pass the relation to onClick()", async () => {
44
54
  const onClick = jest.fn();
45
55
  const props = { relation, columns, onClick };
46
- const rendered = render(<StructureRelationRow {...props} />);
56
+ const rendered = renderRow(props);
47
57
  await waitForLoad(rendered);
48
58
  const user = userEvent.setup({ delay: null });
49
59
 
@@ -58,7 +68,7 @@ describe("<StructureRelationRow />", () => {
58
68
  const value = _.path("structure.name")(relation);
59
69
  const onClick = jest.fn();
60
70
  const props = { relation, columns: [_.head(columns)], onClick };
61
- const rendered = render(<StructureRelationRow {...props} />);
71
+ const rendered = renderRow(props);
62
72
  await waitForLoad(rendered);
63
73
 
64
74
  expect(rendered.container.querySelector("td")).toHaveTextContent(value);
@@ -71,11 +81,69 @@ describe("<StructureRelationRow />", () => {
71
81
  _.set("fieldDecorator", fieldDecorator),
72
82
  )(columns);
73
83
  const props = { relation, columns: [column] };
74
- const rendered = render(<StructureRelationRow {...props} />);
84
+ const rendered = renderRow(props);
75
85
  await waitForLoad(rendered);
76
86
 
77
87
  expect(rendered.container.querySelector("td")).toHaveTextContent(
78
88
  "used_by_decorated",
79
89
  );
80
90
  });
91
+
92
+ it("row prints concept links", async () => {
93
+ const relation = {
94
+ id: 1,
95
+ relation_type,
96
+ structure,
97
+ links: [
98
+ { name: "foo concept", resource_id: "1" },
99
+ { name: "bar concept", resource_id: "2" },
100
+ ],
101
+ };
102
+ const props = {
103
+ relation,
104
+ columns: [{ name: "links", fieldDecorator: linksDecorator }],
105
+ };
106
+ const rendered = render(<StructureRelationRow {...props} />);
107
+ await waitForLoad(rendered);
108
+
109
+ expect(rendered.getByText(/foo concept/i)).toBeInTheDocument();
110
+ expect(rendered.getByText(/bar concept/i)).toBeInTheDocument();
111
+ expect(rendered.getAllByRole("link")).toHaveLength(2);
112
+ });
113
+
114
+ it("row skips empty or invalid concept links", async () => {
115
+ const relation = {
116
+ id: 1,
117
+ relation_type,
118
+ structure,
119
+ links: [null, {}, { name: "foo concept", resource_id: "1" }],
120
+ };
121
+ const props = {
122
+ relation,
123
+ columns: [{ name: "links", fieldDecorator: linksDecorator }],
124
+ };
125
+ const rendered = render(<StructureRelationRow {...props} />);
126
+ await waitForLoad(rendered);
127
+
128
+ expect(rendered.getByText(/foo concept/i)).toBeInTheDocument();
129
+ expect(rendered.getAllByRole("link")).toHaveLength(1);
130
+ });
131
+
132
+ it("row prints nothing when concept links are missing", async () => {
133
+ const relation = {
134
+ id: 1,
135
+ relation_type,
136
+ structure,
137
+ links: [],
138
+ };
139
+ const props = {
140
+ relation,
141
+ columns: [{ name: "links", fieldDecorator: linksDecorator }],
142
+ };
143
+ const rendered = render(<StructureRelationRow {...props} />);
144
+ await waitForLoad(rendered);
145
+
146
+ expect(rendered.queryByRole("link")).not.toBeInTheDocument();
147
+ expect(rendered.container.querySelector("td")).toBeEmptyDOMElement();
148
+ });
81
149
  });
@@ -64,4 +64,22 @@ describe("<StructureRelations />", () => {
64
64
  relations.length,
65
65
  );
66
66
  });
67
+
68
+ it("renders concept links column header", async () => {
69
+ const relations = [
70
+ {
71
+ id: 1,
72
+ relation_type,
73
+ structure: { id: 1, name: "foo structure", type: "foo" },
74
+ links: [{ name: "foo concept", resource_id: "1" }],
75
+ },
76
+ ];
77
+ const columnsWithLinks = [...columns, { name: "links" }];
78
+ const rendered = render(
79
+ <StructureRelations relations={relations} columns={columnsWithLinks} />,
80
+ );
81
+ await waitForLoad(rendered);
82
+
83
+ expect(rendered.getByText(/structure.relation.links/i)).toBeInTheDocument();
84
+ });
67
85
  });
@@ -1,6 +1,15 @@
1
1
  import { render, waitForLoad } from "@truedat/test/render";
2
2
  import StructureRoles from "../StructureRoles";
3
3
 
4
+ jest.mock("@truedat/core/hooks/useAclEntries", () => ({
5
+ ...jest.requireActual("@truedat/core/hooks/useAclEntries"),
6
+ useAclEntries: () => ({
7
+ data: { aclEntries: [], actions: { canCreate: false } },
8
+ loading: false,
9
+ mutate: jest.fn(),
10
+ }),
11
+ }));
12
+
4
13
  jest.mock("react-router", () => ({
5
14
  ...jest.requireActual("react-router"),
6
15
  useParams: jest.fn().mockReturnValue({ id: "1" }),
@@ -4,6 +4,15 @@ import { render, waitForLoad } from "@truedat/test/render";
4
4
  import _ from "lodash/fp";
5
5
  import { StructureRow } from "../StructureRow";
6
6
 
7
+ const renderRow = (props) =>
8
+ render(
9
+ <table>
10
+ <tbody>
11
+ <StructureRow {...props} />
12
+ </tbody>
13
+ </table>,
14
+ );
15
+
7
16
  describe("<StructureRow />", () => {
8
17
  const structure = {
9
18
  id: 1,
@@ -22,7 +31,7 @@ describe("<StructureRow />", () => {
22
31
 
23
32
  it("matches the latest snapshot", async () => {
24
33
  const props = { structure, columns };
25
- const rendered = render(<StructureRow {...props} />);
34
+ const rendered = renderRow(props);
26
35
  await waitForLoad(rendered);
27
36
  expect(rendered.container).toMatchSnapshot();
28
37
  });
@@ -30,7 +39,7 @@ describe("<StructureRow />", () => {
30
39
  it("clicking the row will pass the structure to onClick()", async () => {
31
40
  const onClick = jest.fn();
32
41
  const props = { structure, columns, onClick };
33
- const rendered = render(<StructureRow {...props} />);
42
+ const rendered = renderRow(props);
34
43
  await waitForLoad(rendered);
35
44
  const user = userEvent.setup({ delay: null });
36
45
 
@@ -56,7 +65,7 @@ describe("<StructureRow />", () => {
56
65
  },
57
66
  ];
58
67
  const props = { structure: testStructure, columns: testColumns };
59
- const rendered = render(<StructureRow {...props} />);
68
+ const rendered = renderRow(props);
60
69
  await waitForLoad(rendered);
61
70
 
62
71
  expect(rendered.container.querySelector("td")).toHaveTextContent(testValue);
@@ -71,7 +80,7 @@ describe("<StructureRow />", () => {
71
80
  };
72
81
  const testColumns = [{ name: "testObject", fieldDecorator }];
73
82
  const props = { structure: testStructure, columns: testColumns };
74
- const rendered = render(<StructureRow {...props} />);
83
+ const rendered = renderRow(props);
75
84
  await waitForLoad(rendered);
76
85
 
77
86
  const decoratedValue = fieldDecorator(testValue);
@@ -1,6 +1,8 @@
1
1
  import { render, waitForLoad } from "@truedat/test/render";
2
2
  import { StructureSelector } from "../StructureSelector";
3
3
 
4
+ jest.mock("@truedat/core/hooks/useUserFilters");
5
+
4
6
  const data = {
5
7
  _actions: { bulkUpdate: {} },
6
8
  data: [