@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
@@ -1,12 +1,12 @@
1
1
  import _ from "lodash/fp";
2
- import { lazy, useEffect, useState } from "react";
2
+ import { lazy, useEffect, useRef, useState } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { connect } from "react-redux";
5
5
  import { Button, Form, Segment } from "semantic-ui-react";
6
6
  import { useIntl } from "react-intl";
7
7
  import { HistoryBackButton } from "@truedat/core/components";
8
8
  import { selectTemplate, selectDomains } from "@truedat/df/routines";
9
- import { applyTemplate, validateContent } from "@truedat/df/utils";
9
+ import { applyTemplate } from "@truedat/df/utils";
10
10
  import SuggestionsWidget from "@truedat/ai/components/suggestions/SuggestionsWidget";
11
11
  import { doStructureNoteAction } from "../routines";
12
12
  import { getLatestDfContent } from "../selectors/getSortedStructureNotes";
@@ -17,6 +17,16 @@ const TemplateLoader = lazy(
17
17
  () => import("@truedat/df/templates/components/TemplateLoader"),
18
18
  );
19
19
 
20
+ const isInformed = _.flow(
21
+ _.cond([
22
+ [_.isArray, _.isEmpty],
23
+ [_.isObject, _.isEmpty],
24
+ [_.isString, _.isEmpty],
25
+ [_.stubTrue, _.isNil],
26
+ ]),
27
+ (value) => !value,
28
+ );
29
+
20
30
  export const StructureNotesEdit = ({
21
31
  applyTemplate,
22
32
  structure,
@@ -28,8 +38,9 @@ export const StructureNotesEdit = ({
28
38
  latestDfContent,
29
39
  }) => {
30
40
  const [content, setContent] = useState(latestDfContent);
31
-
41
+ const explicitEmptiesRef = useRef({});
32
42
  const isModification = !_.isEmpty(latestDfContent);
43
+ const initializedRef = useRef(false);
33
44
 
34
45
  useEffect(() => {
35
46
  selectDomains(structure.domain_ids);
@@ -37,18 +48,37 @@ export const StructureNotesEdit = ({
37
48
  }, [template, structure, selectTemplate, selectDomains]);
38
49
 
39
50
  useEffect(() => {
40
- if (template && latestDfContent) {
51
+ if (!initializedRef.current && template && latestDfContent) {
52
+ initializedRef.current = true;
41
53
  setContent(applyTemplate(latestDfContent, structure.domain_id));
42
54
  }
43
55
  }, [applyTemplate, template, latestDfContent, structure.domain_id]);
44
56
 
45
57
  const { formatMessage, locale } = useIntl();
46
58
 
47
- const submitDisabled =
48
- !template || _.size(validateContent(template)(content)) > 0;
59
+ const submitDisabled = !template;
60
+
61
+ const handleFormChange = (newContent) => {
62
+ const clearedFields = _.flow(
63
+ _.pickBy((value) => isInformed(value.value)),
64
+ _.omit(Object.keys(newContent)),
65
+ _.mapValues(() => ({ value: "", origin: "user" })),
66
+ )(content);
67
+
68
+ explicitEmptiesRef.current = {
69
+ ..._.omit(
70
+ Object.keys(_.pickBy((value) => isInformed(value.value))(newContent)),
71
+ )(explicitEmptiesRef.current),
72
+ ...clearedFields,
73
+ };
74
+ setContent(newContent);
75
+ };
49
76
 
50
77
  const handleSubmit = () => {
51
- const df_content = applyTemplate(content, structure.domain_id);
78
+ const df_content = {
79
+ ...applyTemplate(content, structure.domain_id),
80
+ ...explicitEmptiesRef.current,
81
+ };
52
82
  const draftNoteAction = _.flow(
53
83
  _.get("structureNotes"),
54
84
  _.find({ status: "draft" }),
@@ -96,7 +126,7 @@ export const StructureNotesEdit = ({
96
126
  />
97
127
  <Form>
98
128
  <DynamicForm
99
- onChange={setContent}
129
+ onChange={handleFormChange}
100
130
  content={content}
101
131
  isModification={isModification}
102
132
  />
@@ -84,7 +84,7 @@ export const StructuresUpdateOption = ({
84
84
  onClick={navigateToBulkUpdate}
85
85
  disabled={disabled}
86
86
  content={
87
- <>
87
+ <div>
88
88
  <span>
89
89
  {formatMessage({ id: "structures.actions.bulk_update" })}
90
90
  </span>
@@ -93,7 +93,7 @@ export const StructuresUpdateOption = ({
93
93
  {formatMessage({ id: "structures.actions.bulk_update.popup" })}
94
94
  </p>
95
95
  )}
96
- </>
96
+ </div>
97
97
  }
98
98
  />
99
99
  )
@@ -1,4 +1,3 @@
1
- import _ from "lodash/fp";
2
1
  import { useEffect } from "react";
3
2
  import { useSelector, useDispatch } from "react-redux";
4
3
  import { Grid, Segment } from "semantic-ui-react";
@@ -13,8 +12,9 @@ export default function SystemStructures() {
13
12
  const { id: systemId } = useParams();
14
13
  const dispatch = useDispatch();
15
14
 
16
- const { navFilter, systemStructuresLoading } = useSelector(
17
- _.pick(["navFilter", "systemStructuresLoading"]),
15
+ const navFilter = useSelector((state) => state.navFilter);
16
+ const systemStructuresLoading = useSelector(
17
+ (state) => state.systemStructuresLoading,
18
18
  );
19
19
  const system = useSelector((state) => getSystem(state, systemId));
20
20
 
@@ -29,6 +29,8 @@ jest.mock("react-router", () => ({
29
29
  }));
30
30
 
31
31
  describe("<CatalogViewConfigFormLoader />", () => {
32
+ beforeEach(() => jest.useRealTimers());
33
+
32
34
  it("Saves the configuration", async () => {
33
35
  const dispatch = jest.fn();
34
36
  const rendered = render(<CatalogViewConfigFormLoader />, {
@@ -4,7 +4,9 @@ import { render, waitForLoad } from "@truedat/test/render";
4
4
  import { domainsMock } from "@truedat/test/mocks";
5
5
  import { EditDomainForm } from "../EditDomainForm";
6
6
 
7
- const mocks = [domainsMock({ action: "manageStructuresDomain" })];
7
+ const mocks = Array.from({ length: 3 }, () =>
8
+ domainsMock({ action: "manageStructuresDomain" }),
9
+ );
8
10
 
9
11
  describe("<EditDomainForm />", () => {
10
12
  const updateStructure = jest.fn();
@@ -125,3 +125,174 @@ describe("<Grant />", () => {
125
125
  ).not.toBeInTheDocument();
126
126
  });
127
127
  });
128
+
129
+ describe("group and deleted", () => {
130
+ const baseGrant = {
131
+ data_structure: {
132
+ id: "2",
133
+ },
134
+ id: "1",
135
+ data_structure_version: {
136
+ name: "qux",
137
+ },
138
+ detail: {},
139
+ start_date: "2018-12-01",
140
+ end_date: "2029-12-31",
141
+ inserted_at: "2022-12-12T12:49:09.789722Z",
142
+ pending_removal: false,
143
+ };
144
+
145
+ const defaultProps = {
146
+ actions: {},
147
+ loading: false,
148
+ onStatusChange: jest.fn(),
149
+ };
150
+
151
+ const messages = {
152
+ en: {
153
+ "grant.header.user": "User",
154
+ "grant.header.group": "User group",
155
+ "deleted.user": "Deleted user",
156
+ "deleted.group": "Deleted group",
157
+ },
158
+ };
159
+
160
+ it("shows accordion with group members when grant is for a group", async () => {
161
+ const user = userEvent.setup({ delay: null });
162
+ const groupGrant = {
163
+ ...baseGrant,
164
+ user: null,
165
+ user_group_id: 10,
166
+ user_group: {
167
+ id: 10,
168
+ name: "foo",
169
+ alias: "bar",
170
+ users: [
171
+ { id: 1, full_name: "foo user" },
172
+ { id: 2, full_name: "bar user" },
173
+ ],
174
+ },
175
+ };
176
+
177
+ const rendered = render(<Grant {...defaultProps} grant={groupGrant} />, {
178
+ messages,
179
+ });
180
+ await waitForLoad(rendered);
181
+
182
+ const item = rendered.getByText(/^bar$/i).closest(".item");
183
+ expect(item.querySelector(".header")).toHaveTextContent(/^user group$/i);
184
+ expect(item.querySelector(".header")).not.toHaveTextContent(/^user$/i);
185
+ expect(item.querySelector(".angle.right.icon")).toBeInTheDocument();
186
+ await user.click(item.querySelector(".angle.right.icon"));
187
+ expect(item.querySelector(".angle.down.icon")).toBeInTheDocument();
188
+ expect(rendered.getByText(/foo user/i)).toBeInTheDocument();
189
+ expect(rendered.getByText(/bar user/i)).toBeInTheDocument();
190
+ });
191
+
192
+ it("shows group name when alias is empty", async () => {
193
+ const groupGrant = {
194
+ ...baseGrant,
195
+ user: null,
196
+ user_group_id: 10,
197
+ user_group: {
198
+ id: 10,
199
+ name: "foo",
200
+ alias: "",
201
+ users: [],
202
+ },
203
+ };
204
+
205
+ const rendered = render(<Grant {...defaultProps} grant={groupGrant} />, {
206
+ messages,
207
+ });
208
+ await waitForLoad(rendered);
209
+
210
+ const item = rendered.getByText(/^foo$/i).closest(".item");
211
+ expect(item.querySelector(".header")).toHaveTextContent(/^user group$/i);
212
+ });
213
+
214
+ it("shows Deleted group without accordion when the group is missing", async () => {
215
+ const deletedGroupGrant = {
216
+ ...baseGrant,
217
+ user: null,
218
+ user_group_id: 10,
219
+ user_group: null,
220
+ };
221
+
222
+ const rendered = render(
223
+ <Grant {...defaultProps} grant={deletedGroupGrant} />,
224
+ { messages },
225
+ );
226
+ await waitForLoad(rendered);
227
+
228
+ const item = rendered.getByText(/^deleted group$/i).closest(".item");
229
+ expect(item.querySelector(".header")).toHaveTextContent(/^user group$/i);
230
+ expect(item.querySelector(".header")).not.toHaveTextContent(/^user$/i);
231
+ expect(item.querySelector(".angle.right.icon")).not.toBeInTheDocument();
232
+ expect(item.querySelector(".angle.down.icon")).not.toBeInTheDocument();
233
+ expect(item.querySelector("table")).not.toBeInTheDocument();
234
+ });
235
+
236
+ it("shows Deleted user when the user is missing", async () => {
237
+ const deletedUserGrant = {
238
+ ...baseGrant,
239
+ user_id: 7,
240
+ user: null,
241
+ };
242
+
243
+ const rendered = render(
244
+ <Grant {...defaultProps} grant={deletedUserGrant} />,
245
+ { messages },
246
+ );
247
+ await waitForLoad(rendered);
248
+
249
+ const item = rendered.getByText(/^deleted user$/i).closest(".item");
250
+ expect(item.querySelector(".header")).toHaveTextContent(/^user$/i);
251
+ expect(item.querySelector(".header")).not.toHaveTextContent(/user group/i);
252
+ expect(item.querySelector(".angle.right.icon")).not.toBeInTheDocument();
253
+ expect(item.querySelector(".angle.down.icon")).not.toBeInTheDocument();
254
+ });
255
+
256
+ it("shows user full name without group accordion when grant is for a user", async () => {
257
+ const userGrant = {
258
+ ...baseGrant,
259
+ user: {
260
+ full_name: "foo user",
261
+ email: "foo@ext",
262
+ },
263
+ };
264
+
265
+ const rendered = render(<Grant {...defaultProps} grant={userGrant} />, {
266
+ messages,
267
+ });
268
+ await waitForLoad(rendered);
269
+
270
+ const item = rendered.getByText(/^foo user$/i).closest(".item");
271
+ expect(item.querySelector(".header")).toHaveTextContent(/^user$/i);
272
+ expect(item.querySelector(".header")).not.toHaveTextContent(/user group/i);
273
+ expect(item.querySelector(".angle.right.icon")).not.toBeInTheDocument();
274
+ expect(item.querySelector(".angle.down.icon")).not.toBeInTheDocument();
275
+ });
276
+
277
+ it("shows User group header for source_group_name without accordion", async () => {
278
+ const sourceGroupGrant = {
279
+ ...baseGrant,
280
+ user: null,
281
+ user_group: null,
282
+ source_group_name: "baz",
283
+ };
284
+
285
+ const rendered = render(
286
+ <Grant {...defaultProps} grant={sourceGroupGrant} />,
287
+ { messages },
288
+ );
289
+ await waitForLoad(rendered);
290
+
291
+ const item = rendered.getByText(/^baz$/i).closest(".item");
292
+ expect(item.querySelector(".header")).toHaveTextContent(/^user group$/i);
293
+ expect(item.querySelector(".header")).not.toHaveTextContent(/^user$/i);
294
+ expect(item.querySelector(".angle.right.icon")).not.toBeInTheDocument();
295
+ expect(item.querySelector(".angle.down.icon")).not.toBeInTheDocument();
296
+ expect(item.querySelector("table")).not.toBeInTheDocument();
297
+ });
298
+ });
@@ -44,6 +44,8 @@ const approvalRule = {
44
44
  };
45
45
 
46
46
  describe("<GrantApprovalRuleForm />", () => {
47
+ beforeEach(() => jest.useRealTimers());
48
+
47
49
  it("matches the last snapshot", async () => {
48
50
  const props = {
49
51
  onSubmit: jest.fn(),
@@ -1,5 +1,6 @@
1
1
  import { render, waitForLoad } from "@truedat/test/render";
2
2
  import { domainsMock, rolesMock } from "@truedat/test/mocks";
3
+ import { CURRENT_ROLES } from "@truedat/core/api/queries";
3
4
  import { GrantApprovalRuleNew } from "../GrantApprovalRuleNew";
4
5
 
5
6
  const createGrantApprovalRule = jest.fn();
@@ -8,6 +9,13 @@ const renderOpts = {
8
9
  mocks: [
9
10
  domainsMock({ action: "approveGrantRequests" }),
10
11
  rolesMock({ domainId: "1", permission: "approve_grant_request" }),
12
+ {
13
+ request: {
14
+ query: CURRENT_ROLES,
15
+ variables: { domainId: null, permission: "approve_grant_request" },
16
+ },
17
+ result: { data: { currentRoles: [] } },
18
+ },
11
19
  ],
12
20
  };
13
21
 
@@ -4,6 +4,8 @@ import GrantApprovalRules from "../GrantApprovalRules";
4
4
  import { GRANT_APPROVAL_RULES_QUERY } from "../../api/queries";
5
5
  import en from "../../messages/en";
6
6
 
7
+ jest.mock("@truedat/bg/hooks/useDomains");
8
+
7
9
  const grantApprovalRules = {
8
10
  grantApprovalRules: [
9
11
  {
@@ -15,6 +17,7 @@ const grantApprovalRules = {
15
17
  domains: [
16
18
  {
17
19
  __typename: "Domain",
20
+ id: "1",
18
21
  name: "domain_test",
19
22
  },
20
23
  ],
@@ -28,6 +31,7 @@ const grantApprovalRules = {
28
31
  domains: [
29
32
  {
30
33
  __typename: "Domain",
34
+ id: "1",
31
35
  name: "domain_test",
32
36
  },
33
37
  ],
@@ -481,8 +481,8 @@ describe("<GrantRequest />", () => {
481
481
  en: {
482
482
  "grantRequest.header.user": "User",
483
483
  "grantRequest.header.group": "User group",
484
- "grantRequest.deleted.user": "Deleted user",
485
- "grantRequest.deleted.group": "Deleted group",
484
+ "deleted.user": "Deleted user",
485
+ "deleted.group": "Deleted group",
486
486
  },
487
487
  },
488
488
  },
@@ -519,8 +519,8 @@ describe("<GrantRequest />", () => {
519
519
  en: {
520
520
  "grantRequest.header.user": "User",
521
521
  "grantRequest.header.group": "User group",
522
- "grantRequest.deleted.user": "Deleted user",
523
- "grantRequest.deleted.group": "Deleted group",
522
+ "deleted.user": "Deleted user",
523
+ "deleted.group": "Deleted group",
524
524
  },
525
525
  },
526
526
  },
@@ -1,5 +1,4 @@
1
1
  import userEvent from "@testing-library/user-event";
2
- import { act } from "@testing-library/react";
3
2
  import { render, waitForLoad } from "@truedat/test/render";
4
3
  import SearchContext from "@truedat/core/search/SearchContext";
5
4
  import GrantRequestBulkActions from "../GrantRequestBulkActions";
@@ -142,8 +141,6 @@ describe("<GrantRequestBulkActions />", () => {
142
141
  await user.click(await rendered.findByText(/approve_request/i));
143
142
  await user.type(await rendered.getByRole("textbox"), "Approve comment");
144
143
 
145
- await act(
146
- async () => await user.click(rendered.getByTestId("approve-button-id")),
147
- );
144
+ await user.click(rendered.getByTestId("approve-button-id"));
148
145
  });
149
146
  });
@@ -3,6 +3,8 @@ import { render, waitForLoad } from "@truedat/test/render";
3
3
  import SearchContext from "@truedat/core/search/SearchContext";
4
4
  import GrantRequestsSearchResults from "../GrantRequestsSearchResults";
5
5
 
6
+ jest.mock("@truedat/core/hooks/useUserFilters");
7
+
6
8
  describe("<GrantRequestsSearchResults />", () => {
7
9
  const grantRequests = [{ id: 1 }, { id: 2 }];
8
10
  const rolesPermission = ["foo", "bar"];
@@ -1,5 +1,6 @@
1
- import { render, waitForLoad } from "@truedat/test/render";
2
1
  import { screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { render, waitForLoad } from "@truedat/test/render";
3
4
  import SearchContext from "@truedat/core/search/SearchContext";
4
5
  import { GrantsDownloadButton } from "../GrantsDownloadButton";
5
6
 
@@ -139,7 +140,8 @@ describe("<GrantsDownloadButton />", () => {
139
140
  );
140
141
 
141
142
  const button = await screen.findByRole("button");
142
- button.click();
143
+ const user = userEvent.setup({ delay: null });
144
+ await user.click(button);
143
145
 
144
146
  expect(triggerMock).toHaveBeenCalledWith(
145
147
  expect.objectContaining({
@@ -193,4 +195,58 @@ describe("<GrantsDownloadButton />", () => {
193
195
 
194
196
  expect(setDownloadingMock).toHaveBeenCalledWith(true);
195
197
  });
198
+
199
+ it("sends User / Group in header_labels for user_name", async () => {
200
+ const triggerMock = jest.fn();
201
+
202
+ jest
203
+ .mocked(require("@truedat/dd/hooks/useGrant").useGrantDownload)
204
+ .mockReturnValue({
205
+ trigger: triggerMock,
206
+ isMutating: false,
207
+ });
208
+
209
+ const props = {
210
+ hasFilterApplied: true,
211
+ onlyMyGrants: false,
212
+ setDownloading: jest.fn(),
213
+ };
214
+
215
+ const rendered = render(
216
+ <SearchContext value={searchContext}>
217
+ <GrantsDownloadButton {...props} />
218
+ </SearchContext>,
219
+ {
220
+ state: {
221
+ userPermissions: { update: true },
222
+ },
223
+ messages: {
224
+ en: {
225
+ "grants.actions.download.tooltip": "Download to XLSX",
226
+ "grants.props.user_name": "User",
227
+ "grants.props.user_or_group": "User / Group",
228
+ "grants.props.data_structure_name": "Structure",
229
+ "grants.props.domain_name": "Domain",
230
+ "grants.props.end_date": "End date",
231
+ "grants.props.grant_details": "Details",
232
+ "grants.props.start_date": "Start date",
233
+ "grants.props.structure_path": "Path",
234
+ "grants.props.system_name": "System",
235
+ },
236
+ },
237
+ },
238
+ );
239
+
240
+ await waitForLoad(rendered);
241
+ const user = userEvent.setup({ delay: null });
242
+ await user.click(await rendered.findByRole("button"));
243
+
244
+ expect(triggerMock).toHaveBeenCalledWith(
245
+ expect.objectContaining({
246
+ header_labels: expect.objectContaining({
247
+ user_name: "User / Group",
248
+ }),
249
+ }),
250
+ );
251
+ });
196
252
  });
@@ -2,7 +2,7 @@ import userEvent from "@testing-library/user-event";
2
2
  import { render, waitForLoad } from "@truedat/test/render";
3
3
  import SearchContext from "@truedat/core/search/SearchContext";
4
4
  import { GrantsTable } from "../GrantsTable";
5
- import { defaultGrantsTableColumns } from "../../selectors/getGrantsColumns";
5
+ import { getGrantsTableColumns } from "../../selectors/getGrantsColumns";
6
6
 
7
7
  describe("<GrantsTable />", () => {
8
8
  const renderOpts = {
@@ -79,7 +79,7 @@ describe("<GrantsTable />", () => {
79
79
  const sortGrants = jest.fn();
80
80
  const grantsLoading = false;
81
81
  const props = {
82
- columns: defaultGrantsTableColumns,
82
+ columns: getGrantsTableColumns({ authentication: {} }),
83
83
  setDownloading: jest.fn(),
84
84
  };
85
85
 
@@ -100,6 +100,7 @@ describe("<GrantsTable />", () => {
100
100
  renderOpts,
101
101
  );
102
102
  await waitForLoad(rendered);
103
+ expect(rendered.getByText("user_full_name_1")).toBeInTheDocument();
103
104
  expect(rendered.container).toMatchSnapshot();
104
105
  });
105
106
 
@@ -137,4 +138,23 @@ describe("<GrantsTable />", () => {
137
138
  await user.click(headers[0]);
138
139
  expect(handleSortSelection).toHaveBeenCalled();
139
140
  });
141
+
142
+ it("shows User / Group header for the user column", async () => {
143
+ const rendered = render(
144
+ <SearchContext value={searchContext}>
145
+ <GrantsTable {...props} />
146
+ </SearchContext>,
147
+ {
148
+ ...renderOpts,
149
+ messages: {
150
+ en: {
151
+ "grant.header.user": "User",
152
+ "grants.props.user_or_group": "User / Group",
153
+ },
154
+ },
155
+ },
156
+ );
157
+ await waitForLoad(rendered);
158
+ expect(rendered.getByText(/user \/ group/i)).toBeInTheDocument();
159
+ });
140
160
  });
@@ -9,7 +9,12 @@ describe("<LineageDefs />", () => {
9
9
  };
10
10
 
11
11
  it("matches the latest snapshot", async () => {
12
- const rendered = render(<LineageDefs />, renderOpts);
12
+ const rendered = render(
13
+ <svg>
14
+ <LineageDefs />
15
+ </svg>,
16
+ renderOpts,
17
+ );
13
18
  await waitForLoad(rendered);
14
19
  expect(rendered.container).toMatchSnapshot();
15
20
  });
@@ -74,6 +74,7 @@ describe("<LineageForm />", () => {
74
74
  };
75
75
 
76
76
  beforeEach(() => {
77
+ jest.useRealTimers();
77
78
  localStorage.clear();
78
79
  });
79
80
 
@@ -1,4 +1,4 @@
1
- import { fireEvent, screen } from "@testing-library/react";
1
+ import { act, fireEvent, screen } from "@testing-library/react";
2
2
  import { render } from "@truedat/test/render";
3
3
  import { LineageGraphToolbar, visibleLegendKeys } from "../LineageGraphToolbar";
4
4
 
@@ -45,12 +45,12 @@ const renderToolbar = (props = {}, state = { graph: { id: "graph-1" } }) =>
45
45
  describe("<LineageGraphToolbar />", () => {
46
46
  beforeEach(() => jest.clearAllMocks());
47
47
 
48
- it("opens the legend from the empty context slot", () => {
48
+ it("opens the legend from the empty context slot", async () => {
49
49
  renderToolbar({ context: null });
50
50
  const button = screen.getByRole("button", { name: "Lineage legend" });
51
51
  expect(button).toBeEnabled();
52
52
  expect(button).toHaveAttribute("aria-expanded", "false");
53
- fireEvent.click(button);
53
+ await act(async () => fireEvent.click(button));
54
54
  expect(defaultProps.onPanelChange).toHaveBeenCalledWith("legend");
55
55
  });
56
56
 
@@ -98,7 +98,7 @@ describe("<LineageGraphToolbar />", () => {
98
98
  ).toHaveAttribute("title", "Search nodes");
99
99
  });
100
100
 
101
- it("changes visible depth independently through the accessible selector", () => {
101
+ it("changes visible depth independently through the accessible selector", async () => {
102
102
  renderToolbar();
103
103
 
104
104
  const trigger = screen.getByRole("button", { name: "Visible levels" });
@@ -111,7 +111,7 @@ describe("<LineageGraphToolbar />", () => {
111
111
  trigger.closest(".lineage-graph__dock-tooltip-trigger"),
112
112
  ).not.toBeInTheDocument();
113
113
 
114
- fireEvent.click(trigger);
114
+ await act(async () => fireEvent.click(trigger));
115
115
 
116
116
  expect(trigger).toHaveAttribute("aria-expanded", "true");
117
117
  expect(trigger).toHaveClass("lineage-graph__dock-button--active");
@@ -249,17 +249,19 @@ describe("<LineageGraphToolbar />", () => {
249
249
  expect(defaultProps.onUnpin).toHaveBeenCalledTimes(1);
250
250
  });
251
251
 
252
- it("forwards search, collapse and expand interactions", () => {
253
- renderToolbar({ activePanel: "search" });
252
+ it("forwards search, collapse and expand interactions", async () => {
253
+ await act(async () => renderToolbar({ activePanel: "search" }));
254
254
 
255
- fireEvent.change(
256
- screen.getByRole("textbox", { name: "Search graph nodes" }),
257
- {
258
- target: { value: "orders" },
259
- },
260
- );
261
- fireEvent.click(screen.getByRole("button", { name: "Collapse all" }));
262
- fireEvent.click(screen.getByRole("button", { name: "Expand all" }));
255
+ await act(async () => {
256
+ fireEvent.change(
257
+ screen.getByRole("textbox", { name: "Search graph nodes" }),
258
+ {
259
+ target: { value: "orders" },
260
+ },
261
+ );
262
+ fireEvent.click(screen.getByRole("button", { name: "Collapse all" }));
263
+ fireEvent.click(screen.getByRole("button", { name: "Expand all" }));
264
+ });
263
265
 
264
266
  expect(defaultProps.onSearchInputChange).toHaveBeenCalledWith("orders");
265
267
  expect(defaultProps.onCollapseAll).toHaveBeenCalledTimes(1);
@@ -288,7 +290,7 @@ describe("<LineageGraphToolbar />", () => {
288
290
  ).not.toBeInTheDocument();
289
291
  });
290
292
 
291
- it("toggles whether a double click expands or collapses nodes", () => {
293
+ it("toggles whether a double click expands or collapses nodes", async () => {
292
294
  const onDoubleClickExpandsNodesChange = jest.fn();
293
295
  renderToolbar({ onDoubleClickExpandsNodesChange });
294
296
 
@@ -299,7 +301,7 @@ describe("<LineageGraphToolbar />", () => {
299
301
  expect(
300
302
  settingsButton.querySelector(".lucide-settings"),
301
303
  ).toBeInTheDocument();
302
- fireEvent.click(settingsButton);
304
+ await act(async () => fireEvent.click(settingsButton));
303
305
 
304
306
  const toggle = screen.getByRole("checkbox", {
305
307
  name: "Double-click expands or collapses nodes",