@truedat/dd 8.2.4 → 8.3.1

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 (29) hide show
  1. package/package.json +3 -3
  2. package/src/api.js +2 -0
  3. package/src/components/GrantRequestCartResult.js +132 -0
  4. package/src/components/GrantRoutes.js +5 -16
  5. package/src/components/StructureGrantCart.js +11 -6
  6. package/src/components/StructureGrantCartCheckout.js +357 -40
  7. package/src/components/StructureGrantCartInformation.js +6 -0
  8. package/src/components/StructureGrantCartUserSelector.js +23 -8
  9. package/src/components/__tests__/GrantRequestCartResult.spec.js +79 -0
  10. package/src/components/__tests__/StructureGrantCartCheckout.spec.js +110 -8
  11. package/src/components/__tests__/__snapshots__/GrantRequestCartResult.spec.js.snap +179 -0
  12. package/src/components/__tests__/__snapshots__/StructureGrantCartCheckout.spec.js.snap +294 -1
  13. package/src/hooks/useCheckoutGrantRequest.js +16 -0
  14. package/src/hooks/useGrantRequestGroupsByIds.js +22 -0
  15. package/src/hooks/useSystems.js +27 -0
  16. package/src/hooks/useTemplatesByIds.js +66 -0
  17. package/src/messages/en.js +14 -0
  18. package/src/messages/es.js +14 -0
  19. package/src/reducers/__tests__/grantRequestsCart.spec.js +70 -3
  20. package/src/reducers/ddMessage.js +31 -23
  21. package/src/reducers/grantRequestsCart.js +35 -1
  22. package/src/reducers/structureRedirect.js +11 -1
  23. package/src/routines.js +7 -0
  24. package/src/sagas/index.js +0 -3
  25. package/src/selectors/__tests__/messages.spec.js +56 -0
  26. package/src/selectors/getParsedEvents.js +3 -1
  27. package/src/selectors/messages.js +49 -49
  28. package/src/styles/grantCartUserSelector.less +3 -0
  29. package/src/sagas/operateGrantRequestCart.js +0 -26
@@ -1,46 +1,284 @@
1
1
  import _ from "lodash/fp";
2
2
  import PropTypes from "prop-types";
3
+ import { useEffect, useMemo, Suspense } from "react";
3
4
  import { Header, Segment, Grid, Button, GridColumn } from "semantic-ui-react";
4
5
  import { bindActionCreators } from "redux";
5
- import { connect } from "react-redux";
6
+ import { connect, useDispatch } from "react-redux";
6
7
  import { HistoryBackButton } from "@truedat/core/components";
7
8
  import { useIntl, FormattedMessage } from "react-intl";
8
- import { checkoutGrantRequest } from "../routines";
9
+ import { lazy } from "react";
10
+ import {
11
+ checkoutGrantRequest,
12
+ initGroupTemplates,
13
+ selectGrantRequestTemplate,
14
+ selectGrantRequestTemplateForGroup,
15
+ updateGrantRequestTemplateContentForGroup,
16
+ } from "../routines";
9
17
  import StructureGrantCart from "./StructureGrantCart";
10
18
  import StructureGrantCartInformation from "./StructureGrantCartInformation";
11
- import { useState } from "react";
19
+ import StructureGrantCartUserSelector from "./StructureGrantCartUserSelector";
20
+ import { useWebContext } from "@truedat/core/webContext";
21
+ import { useTemplateRelations } from "@truedat/df/hooks/useTemplateRelations";
22
+ import { useCheckoutGrantRequest } from "../hooks/useCheckoutGrantRequest";
23
+ import { useTemplatesByIds } from "../hooks/useTemplatesByIds";
24
+
25
+ const SelectableDynamicForm = lazy(
26
+ () => import("@truedat/df/components/SelectableDynamicForm")
27
+ );
28
+
29
+ const getGroupedStructuresByTemplate = (
30
+ structures,
31
+ templateResourceRelations,
32
+ defaultTemplateId
33
+ ) => {
34
+ const systemToTemplateId = _.fromPairs(
35
+ (templateResourceRelations ?? []).map((r) => [
36
+ r.resource_id != null ? Number(r.resource_id) || r.resource_id : null,
37
+ r.template_id != null ? Number(r.template_id) || r.template_id : null,
38
+ ]).filter(([sid]) => sid != null)
39
+ );
40
+ return _.reduce(
41
+ (acc, structure) => {
42
+ const systemId = structure.system_id ?? structure.system?.id ?? null;
43
+ const normalizedSystemId =
44
+ systemId != null ? Number(systemId) || systemId : null;
45
+ const templateId =
46
+ normalizedSystemId != null
47
+ ? systemToTemplateId[normalizedSystemId] ?? defaultTemplateId
48
+ : defaultTemplateId;
49
+ const key =
50
+ templateId != null ? String(templateId) : "others";
51
+ const list = acc[key] ?? [];
52
+ return { ...acc, [key]: [...list, structure] };
53
+ },
54
+ {},
55
+ structures ?? []
56
+ );
57
+ };
12
58
 
13
59
  export const StructureGrantCartCheckout = ({
14
60
  grantRequestsCart,
15
- checkoutGrantRequest,
61
+ initGroupTemplates,
62
+ selectGrantRequestTemplate,
63
+ selectGrantRequestTemplateForGroup,
64
+ updateGrantRequestTemplateContentForGroup,
16
65
  }) => {
66
+ const dispatch = useDispatch();
67
+ const { trigger, isMutating } = useCheckoutGrantRequest();
68
+ const { resource: templateResourceRelations, default: defaultRelation } =
69
+ useTemplateRelations({
70
+ resourceType: "system",
71
+ listAll: true,
72
+ });
73
+ const defaultTemplateId =
74
+ defaultRelation?.template_id != null
75
+ ? Number(defaultRelation.template_id) || defaultRelation.template_id
76
+ : null;
17
77
  const { formatMessage } = useIntl();
18
- const [isRequesting, setIsRequesting] = useState(false);
78
+ const { scopesWithRelations = [] } = useWebContext();
79
+ const {
80
+ template,
81
+ templateContent,
82
+ modificationGrant,
83
+ validCart,
84
+ user,
85
+ structures = [],
86
+ groupTemplates = {},
87
+ } = grantRequestsCart;
88
+
89
+ const groupedStructures = getGroupedStructuresByTemplate(
90
+ structures,
91
+ templateResourceRelations,
92
+ defaultTemplateId
93
+ );
94
+ const groupKeys = _.keys(groupedStructures);
95
+ const isMultiGroup =
96
+ !modificationGrant &&
97
+ scopesWithRelations.includes("gr") &&
98
+ groupKeys.length > 1;
99
+
100
+ const singleGroupKey =
101
+ !modificationGrant &&
102
+ scopesWithRelations.includes("gr") &&
103
+ groupKeys.length === 1
104
+ ? groupKeys[0]
105
+ : null;
106
+
107
+ const effectiveTemplateIdByKey = useMemo(
108
+ () =>
109
+ _.fromPairs(
110
+ groupKeys.map((k) => [
111
+ k,
112
+ k === "others" ? defaultTemplateId : Number(k) || k,
113
+ ])
114
+ ),
115
+ [groupKeys.join(","), defaultTemplateId]
116
+ );
117
+
118
+ const uniqueTemplateIds = useMemo(
119
+ () => _.uniq(_.compact(_.values(effectiveTemplateIdByKey))),
120
+ [effectiveTemplateIdByKey]
121
+ );
19
122
 
20
- const { template, templateContent, modificationGrant, validCart, user } =
21
- grantRequestsCart;
123
+ const singleTemplateId =
124
+ !isMultiGroup && singleGroupKey != null
125
+ ? singleGroupKey === "others"
126
+ ? defaultTemplateId
127
+ : Number(singleGroupKey) || singleGroupKey
128
+ : null;
129
+
130
+ const templateIdsToFetch = isMultiGroup
131
+ ? uniqueTemplateIds
132
+ : singleTemplateId != null
133
+ ? [singleTemplateId]
134
+ : [];
135
+
136
+ const templatesById = useTemplatesByIds(templateIdsToFetch);
137
+
138
+ const systemTemplates = useMemo(() => {
139
+ if (!isMultiGroup) return {};
140
+ const next = {};
141
+ groupKeys.forEach((k) => {
142
+ const tid = effectiveTemplateIdByKey[k];
143
+ const t =
144
+ tid != null
145
+ ? templatesById[String(tid)] ?? templatesById[tid]
146
+ : null;
147
+ if (t) next[k] = t;
148
+ });
149
+ return next;
150
+ }, [isMultiGroup, groupKeys.join(","), effectiveTemplateIdByKey, templatesById]);
151
+
152
+ const singleSystemTemplate = useMemo(
153
+ () =>
154
+ singleTemplateId != null
155
+ ? templatesById[String(singleTemplateId)] ??
156
+ templatesById[singleTemplateId] ??
157
+ null
158
+ : null,
159
+ [singleTemplateId, templatesById]
160
+ );
161
+
162
+ useEffect(() => {
163
+ if (!isMultiGroup) return;
164
+ if (_.isEmpty(uniqueTemplateIds)) {
165
+ initGroupTemplates(
166
+ _.fromPairs(
167
+ groupKeys.map((k) => [
168
+ k,
169
+ {
170
+ template: k === "others" ? null : undefined,
171
+ templateContent: {},
172
+ valid: false,
173
+ },
174
+ ])
175
+ )
176
+ );
177
+ return;
178
+ }
179
+ const allLoaded = uniqueTemplateIds.every(
180
+ (id) =>
181
+ templatesById[String(id)] !== undefined ||
182
+ templatesById[id] !== undefined
183
+ );
184
+ if (!allLoaded) return;
185
+ const getTemplateById = (tid) =>
186
+ tid != null ? templatesById[String(tid)] ?? templatesById[tid] : null;
187
+ const initial = _.fromPairs(
188
+ groupKeys.map((k) => {
189
+ const t = getTemplateById(effectiveTemplateIdByKey[k])?.name;
190
+ return [
191
+ k,
192
+ {
193
+ template: t ?? (k === "others" ? null : undefined),
194
+ templateContent: {},
195
+ valid: false,
196
+ },
197
+ ];
198
+ })
199
+ );
200
+ initGroupTemplates(initial);
201
+ }, [
202
+ isMultiGroup,
203
+ groupKeys.join(","),
204
+ uniqueTemplateIds.join(","),
205
+ defaultTemplateId,
206
+ templatesById,
207
+ effectiveTemplateIdByKey,
208
+ initGroupTemplates,
209
+ ]);
210
+
211
+ useEffect(() => {
212
+ if (isMultiGroup || singleTemplateId == null || !singleSystemTemplate?.name)
213
+ return;
214
+ selectGrantRequestTemplate(singleSystemTemplate.name);
215
+ }, [
216
+ isMultiGroup,
217
+ singleTemplateId,
218
+ singleSystemTemplate?.name,
219
+ selectGrantRequestTemplate,
220
+ ]);
221
+
222
+ const validCartMulti =
223
+ isMultiGroup &&
224
+ user?.valid &&
225
+ groupKeys.every(
226
+ (k) => groupTemplates[k]?.template && groupTemplates[k]?.valid
227
+ );
228
+
229
+ const canSubmit = isMultiGroup ? validCartMulti : validCart && user?.valid;
22
230
 
23
231
  const checkout = () => {
24
- const requests = _.map((structure) => {
25
- return {
232
+ let payload;
233
+ if (isMultiGroup) {
234
+ const grant_request_groups = groupKeys
235
+ .filter((k) => _.size(groupedStructures[k]) > 0)
236
+ .map((key) => {
237
+ const groupData = groupTemplates[key];
238
+ const templateName = groupData?.template;
239
+ const requests = _.map((structure) => ({
240
+ data_structure_id: structure.id,
241
+ filters: { rows: structure.filters },
242
+ metadata: groupData?.templateContent ?? {},
243
+ }))(groupedStructures[key]);
244
+ return _.omitBy(_.isNil)({
245
+ requests,
246
+ type: templateName,
247
+ modification_grant_id: modificationGrant?.id ?? null,
248
+ user_id: user?.id,
249
+ created_by_id: user?.id,
250
+ });
251
+ });
252
+ payload = { grant_request_groups };
253
+ } else {
254
+ const requests = _.map((structure) => ({
26
255
  data_structure_id: structure.id,
27
- filters: {
28
- rows: structure.filters,
29
- },
256
+ filters: { rows: structure.filters },
30
257
  metadata: templateContent,
258
+ }))(structures);
259
+ payload = {
260
+ grant_request_group: _.omitBy(_.isNil)({
261
+ requests,
262
+ type: template,
263
+ modification_grant_id: modificationGrant?.id ?? null,
264
+ user_id: user?.id,
265
+ created_by_id: user?.id,
266
+ }),
31
267
  };
32
- })(grantRequestsCart.structures);
33
- const payload = {
34
- grant_request_group: _.omitBy(_.isNil)({
35
- requests,
36
- type: template,
37
- modification_grant_id: modificationGrant ? modificationGrant.id : null,
38
- user_id: user?.id,
39
- created_by_id: user?.id,
40
- }),
41
- };
42
- setIsRequesting(true);
43
- checkoutGrantRequest(payload);
268
+ }
269
+ dispatch(checkoutGrantRequest.request());
270
+ trigger(payload)
271
+ .then((data) => dispatch(checkoutGrantRequest.success(data)))
272
+ .catch((err) =>
273
+ dispatch(
274
+ checkoutGrantRequest.failure(
275
+ err?.response
276
+ ? { status: err.response.status, data: err.response.data }
277
+ : err?.message
278
+ )
279
+ )
280
+ )
281
+ .finally(() => dispatch(checkoutGrantRequest.fulfill()));
44
282
  };
45
283
 
46
284
  return (
@@ -51,9 +289,7 @@ export const StructureGrantCartCheckout = ({
51
289
  <Header as="h2">
52
290
  <Header.Content>
53
291
  <FormattedMessage
54
- id={`grantRequest.header.${
55
- modificationGrant ? "update" : "create"
56
- }`}
292
+ id={`grantRequest.header.${modificationGrant ? "update" : "create"}`}
57
293
  />
58
294
  </Header.Content>
59
295
  </Header>
@@ -65,21 +301,91 @@ export const StructureGrantCartCheckout = ({
65
301
  <Button
66
302
  primary
67
303
  onClick={checkout}
68
- disabled={!validCart || !user.valid || isRequesting}
304
+ disabled={!canSubmit || isMutating}
69
305
  content={formatMessage({ id: "actions.save" })}
70
306
  />
71
307
  </GridColumn>
72
308
  </Grid.Row>
73
- <Grid.Row>
74
- <Grid.Column width={5}>
75
- <StructureGrantCartInformation
76
- isModification={!!modificationGrant}
77
- />
78
- </Grid.Column>
79
- <Grid.Column width={11}>
80
- <StructureGrantCart />
81
- </Grid.Column>
82
- </Grid.Row>
309
+ {isMultiGroup ? (
310
+ <>
311
+ <Grid.Row>
312
+ <Grid.Column width={16}>
313
+ <Header as="h4">
314
+ <FormattedMessage id="grants.props.user_name" />
315
+ </Header>
316
+ <StructureGrantCartUserSelector spacedLabel />
317
+ </Grid.Column>
318
+ </Grid.Row>
319
+ {groupKeys.map((key) => {
320
+ const groupStructures = groupedStructures[key] ?? [];
321
+ if (_.isEmpty(groupStructures)) return null;
322
+ const isOthers = key === "others";
323
+ const groupData = groupTemplates[key] ?? {};
324
+ const selectedTemplate = systemTemplates[key];
325
+ return (
326
+ <Grid.Row key={key}>
327
+ <Grid.Column width={5}>
328
+ <Segment className="grant-cart-group-form">
329
+ <Header as="h4">
330
+ {isOthers ? (
331
+ <FormattedMessage id="grantRequest.cart.group.others" />
332
+ ) : (
333
+ selectedTemplate?.name ?? key
334
+ )}
335
+ </Header>
336
+ <Suspense fallback={null}>
337
+ <SelectableDynamicForm
338
+ scope="gr"
339
+ content={groupData.templateContent ?? {}}
340
+ hideLabel
341
+ isModification={!!modificationGrant}
342
+ selectedTemplate={selectedTemplate}
343
+ disableSelector={!!selectedTemplate}
344
+ onChange={({ content, valid }) =>
345
+ updateGrantRequestTemplateContentForGroup({
346
+ key,
347
+ content,
348
+ valid,
349
+ })
350
+ }
351
+ onNameChange={(name) =>
352
+ selectGrantRequestTemplateForGroup({
353
+ key,
354
+ template: name,
355
+ })
356
+ }
357
+ placeholder={formatMessage({
358
+ id: "structure.grant.cart.template.placeholder",
359
+ })}
360
+ required
361
+ />
362
+ </Suspense>
363
+ </Segment>
364
+ </Grid.Column>
365
+ <Grid.Column width={11}>
366
+ <StructureGrantCart
367
+ structureRequestGroups={groupStructures}
368
+ allowEmptyCart
369
+ />
370
+ </Grid.Column>
371
+ </Grid.Row>
372
+ );
373
+ })}
374
+ </>
375
+ ) : (
376
+ <Grid.Row>
377
+ <Grid.Column width={5}>
378
+ <StructureGrantCartInformation
379
+ isModification={!!modificationGrant}
380
+ selectedTemplate={singleSystemTemplate}
381
+ disableSelector={!!singleSystemTemplate}
382
+ />
383
+ </Grid.Column>
384
+ <Grid.Column width={11}>
385
+ <StructureGrantCart />
386
+ </Grid.Column>
387
+ </Grid.Row>
388
+ )}
83
389
  </Grid>
84
390
  </Segment>
85
391
  );
@@ -87,7 +393,10 @@ export const StructureGrantCartCheckout = ({
87
393
 
88
394
  StructureGrantCartCheckout.propTypes = {
89
395
  grantRequestsCart: PropTypes.object,
90
- checkoutGrantRequest: PropTypes.func,
396
+ initGroupTemplates: PropTypes.func,
397
+ selectGrantRequestTemplate: PropTypes.func,
398
+ selectGrantRequestTemplateForGroup: PropTypes.func,
399
+ updateGrantRequestTemplateContentForGroup: PropTypes.func,
91
400
  };
92
401
 
93
402
  const mapStateToProps = ({ grantRequestsCart }) => ({
@@ -95,7 +404,15 @@ const mapStateToProps = ({ grantRequestsCart }) => ({
95
404
  });
96
405
 
97
406
  const mapDispatchToProps = (dispatch) =>
98
- bindActionCreators({ checkoutGrantRequest }, dispatch);
407
+ bindActionCreators(
408
+ {
409
+ initGroupTemplates,
410
+ selectGrantRequestTemplate,
411
+ selectGrantRequestTemplateForGroup,
412
+ updateGrantRequestTemplateContentForGroup,
413
+ },
414
+ dispatch
415
+ );
99
416
 
100
417
  export default connect(
101
418
  mapStateToProps,
@@ -21,6 +21,8 @@ export const StructureGrantCartInformation = ({
21
21
  isModification,
22
22
  selectGrantRequestTemplate,
23
23
  updateGrantRequestTemplateContent,
24
+ selectedTemplate,
25
+ disableSelector,
24
26
  }) => {
25
27
  const { formatMessage } = useIntl();
26
28
  const templateContent = _.pathOr({}, "templateContent")(grantRequestsCart);
@@ -60,6 +62,8 @@ export const StructureGrantCartInformation = ({
60
62
  id: `structure.grant.cart.template.placeholder`,
61
63
  })}
62
64
  required
65
+ selectedTemplate={selectedTemplate}
66
+ disableSelector={disableSelector}
63
67
  />
64
68
  </Form>
65
69
  </Segment>
@@ -71,6 +75,8 @@ StructureGrantCartInformation.propTypes = {
71
75
  selectGrantRequestTemplate: PropTypes.func,
72
76
  updateGrantRequestTemplateContent: PropTypes.func,
73
77
  isModification: PropTypes.bool,
78
+ selectedTemplate: PropTypes.object,
79
+ disableSelector: PropTypes.bool,
74
80
  };
75
81
 
76
82
  const mapStateToProps = ({ grantRequestsCart }) => ({ grantRequestsCart });
@@ -14,6 +14,7 @@ import { updateGrantRequestUser } from "../routines";
14
14
  export const StructureGrantCartUserSelector = ({
15
15
  grantRequestsCart,
16
16
  updateGrantRequestUser,
17
+ spacedLabel,
17
18
  }) => {
18
19
  const { formatMessage } = useIntl();
19
20
  const [usersGroupsQuery, setUsersGroupsQuery] = useState("");
@@ -166,14 +167,27 @@ export const StructureGrantCartUserSelector = ({
166
167
  selection
167
168
  required={thirdParty}
168
169
  label={
169
- <Checkbox
170
- label={formatMessage({ id: "grantRequestCart.user.label" })}
171
- onChange={(e, data) => {
172
- setThirdParty(data.checked);
173
- selectUser(null);
174
- }}
175
- checked={thirdParty}
176
- />
170
+ spacedLabel ? (
171
+ <span className="grant-cart-user-selector-label-spaced">
172
+ <Checkbox
173
+ label={formatMessage({ id: "grantRequestCart.user.label" })}
174
+ onChange={(e, data) => {
175
+ setThirdParty(data.checked);
176
+ selectUser(null);
177
+ }}
178
+ checked={thirdParty}
179
+ />
180
+ </span>
181
+ ) : (
182
+ <Checkbox
183
+ label={formatMessage({ id: "grantRequestCart.user.label" })}
184
+ onChange={(e, data) => {
185
+ setThirdParty(data.checked);
186
+ selectUser(null);
187
+ }}
188
+ checked={thirdParty}
189
+ />
190
+ )
177
191
  }
178
192
  options={options}
179
193
  value={userId}
@@ -186,6 +200,7 @@ export const StructureGrantCartUserSelector = ({
186
200
  StructureGrantCartUserSelector.propTypes = {
187
201
  grantRequestsCart: PropTypes.object,
188
202
  updateGrantRequestUser: PropTypes.func,
203
+ spacedLabel: PropTypes.bool,
189
204
  };
190
205
 
191
206
  const mapStateToProps = ({ grantRequestsCart }) => ({
@@ -0,0 +1,79 @@
1
+ import { render, waitForLoad } from "@truedat/test/render";
2
+ import { GrantRequestCartResult } from "../GrantRequestCartResult";
3
+
4
+ jest.mock("../../hooks/useGrantRequestGroupsByIds", () => ({
5
+ useGrantRequestGroupsByIds: jest.fn(),
6
+ }));
7
+
8
+ jest.mock("react-router", () => {
9
+ const actual = jest.requireActual("react-router");
10
+ return {
11
+ ...actual,
12
+ useParams: jest.fn(),
13
+ };
14
+ });
15
+
16
+ const { useGrantRequestGroupsByIds } = require("../../hooks/useGrantRequestGroupsByIds");
17
+ const { useParams } = require("react-router");
18
+
19
+ describe("GrantRequestCartResult", () => {
20
+ it("matches snapshot when no ids in URL (empty)", async () => {
21
+ useParams.mockReturnValue({});
22
+ useGrantRequestGroupsByIds.mockReturnValue({
23
+ groups: [],
24
+ isLoading: false,
25
+ error: null,
26
+ });
27
+ const rendered = render(<GrantRequestCartResult />);
28
+ await waitForLoad(rendered);
29
+ expect(rendered.container).toMatchSnapshot();
30
+ });
31
+
32
+ it("matches snapshot when ids in URL and hook returns groups", async () => {
33
+ useParams.mockReturnValue({ ids: "1-2" });
34
+ const groups = [
35
+ { id: 1, type: "Template A" },
36
+ { id: 2, type: "Template B" },
37
+ ];
38
+ useGrantRequestGroupsByIds.mockReturnValue({
39
+ groups,
40
+ isLoading: false,
41
+ error: null,
42
+ });
43
+ const rendered = render(<GrantRequestCartResult />);
44
+ await waitForLoad(rendered);
45
+ expect(rendered.container).toMatchSnapshot();
46
+ });
47
+
48
+ it("matches snapshot when groups have embedded requests with data structures", async () => {
49
+ useParams.mockReturnValue({ ids: "1" });
50
+ const groups = [
51
+ {
52
+ id: 1,
53
+ type: "Template A",
54
+ _embedded: {
55
+ requests: [
56
+ {
57
+ _embedded: {
58
+ data_structure: { id: 10, name: "Structure One", external_id: "ext-1" },
59
+ },
60
+ },
61
+ {
62
+ _embedded: {
63
+ data_structure: { id: 20, name: "Structure Two", external_id: "ext-2" },
64
+ },
65
+ },
66
+ ],
67
+ },
68
+ },
69
+ ];
70
+ useGrantRequestGroupsByIds.mockReturnValue({
71
+ groups,
72
+ isLoading: false,
73
+ error: null,
74
+ });
75
+ const rendered = render(<GrantRequestCartResult />);
76
+ await waitForLoad(rendered);
77
+ expect(rendered.container).toMatchSnapshot();
78
+ });
79
+ });