@truedat/dd 5.18.3 → 5.20.0

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 (50) hide show
  1. package/package.json +5 -5
  2. package/src/api/queries.js +19 -1
  3. package/src/components/Grant.js +9 -27
  4. package/src/components/GrantRemoval.js +26 -34
  5. package/src/components/GrantRemovalDirectButton.js +89 -0
  6. package/src/components/GrantRemovalWorkflow.js +107 -0
  7. package/src/components/GrantRemovalWorkflowDropdown.js +144 -0
  8. package/src/components/GrantRequest.js +2 -1
  9. package/src/components/GrantRequestCancel.js +5 -2
  10. package/src/components/GrantRequestHeader.js +13 -7
  11. package/src/components/GrantRequestRow.js +8 -1
  12. package/src/components/GrantRequests.js +8 -3
  13. package/src/components/GrantRequestsTable.js +1 -1
  14. package/src/components/StructureGrantDropdown.js +7 -40
  15. package/src/components/StructureGrantSummaryButton.js +2 -2
  16. package/src/components/StructureGrants.js +21 -17
  17. package/src/components/StructureNoteActions.js +1 -0
  18. package/src/components/StructureNoteSuggestions.js +179 -0
  19. package/src/components/StructureNotesEdit.js +19 -2
  20. package/src/components/__tests__/Grant.spec.js +11 -7
  21. package/src/components/__tests__/GrantRemoval.spec.js +19 -54
  22. package/src/components/__tests__/GrantRemovalDirectButton.spec.js +66 -0
  23. package/src/components/__tests__/GrantRemovalWorkflow.spec.js +157 -0
  24. package/src/components/__tests__/GrantRequest.spec.js +2 -0
  25. package/src/components/__tests__/GrantRequestHeader.spec.js +2 -5
  26. package/src/components/__tests__/StructureGrantDropdown.spec.js +1 -16
  27. package/src/components/__tests__/StructureGrantSummaryButton.spec.js +3 -2
  28. package/src/components/__tests__/StructureGrants.spec.js +5 -1
  29. package/src/components/__tests__/StructureNoteSuggestions.spec.js +151 -0
  30. package/src/components/__tests__/StructureNotesEdit.spec.js +34 -0
  31. package/src/components/__tests__/__snapshots__/Grant.spec.js.snap +5 -4
  32. package/src/components/__tests__/__snapshots__/GrantRequest.spec.js.snap +3 -3
  33. package/src/components/__tests__/__snapshots__/GrantRequestHeader.spec.js.snap +4 -4
  34. package/src/components/__tests__/__snapshots__/GrantRequestsSearchResults.spec.js.snap +1 -1
  35. package/src/components/__tests__/__snapshots__/StructureGrantDropdown.spec.js.snap +0 -52
  36. package/src/components/__tests__/__snapshots__/StructureGrantListButton.spec.js.snap +0 -14
  37. package/src/components/__tests__/__snapshots__/StructureGrantSummaryButton.spec.js.snap +0 -14
  38. package/src/components/__tests__/__snapshots__/StructureGrants.spec.js.snap +4 -5
  39. package/src/components/__tests__/__snapshots__/StructureNoteSuggestions.spec.js.snap +316 -0
  40. package/src/components/__tests__/__snapshots__/StructureNotesEdit.spec.js.snap +49 -0
  41. package/src/hooks/useGrantRequest.js +10 -1
  42. package/src/messages/en.js +23 -8
  43. package/src/messages/es.js +26 -9
  44. package/src/reducers/structure.js +1 -0
  45. package/src/sagas/__tests__/createGrantRequestStatus.spec.js +1 -1
  46. package/src/sagas/createGrantRequestStatus.js +24 -8
  47. package/src/selectors/getGrantRequestsColumns.js +32 -5
  48. package/src/selectors/getGrantRequestsSearchColumns.js +20 -14
  49. package/src/selectors/utils/decorators.js +68 -0
  50. package/src/components/__tests__/__snapshots__/GrantRemoval.spec.js.snap +0 -9
@@ -8,11 +8,11 @@ import { linkTo } from "@truedat/core/routes";
8
8
  import { Date } from "@truedat/core/components";
9
9
 
10
10
  import GrantChangeRequest from "./GrantChangeRequest";
11
- import GrantRemoval from "./GrantRemoval";
12
11
  import GrantRequestCancel from "./GrantRequestCancel";
13
12
  import StructureGrantRequestAddCart from "./StructureGrantRequestAddCart";
14
13
 
15
14
  const isNilOrEmpty = (value) => _.isEmpty(value) || _.isNil(value);
15
+
16
16
  export const StructureGrantDropdown = ({
17
17
  grantRequest,
18
18
  grantRequestStatus,
@@ -54,38 +54,10 @@ export const StructureGrantDropdown = ({
54
54
  }
55
55
  icon="info"
56
56
  text={formatMessage({
57
- id: `grant.actions.view_request.confirmation.header`,
57
+ id: `grant.actions.viewRequest.confirmation.header`,
58
58
  })}
59
59
  />
60
60
  ),
61
- requestRemoval: (
62
- <GrantRemoval
63
- grant={grant}
64
- previousStructureQuery={previousStructureQuery}
65
- key="requestRemoval"
66
- >
67
- <Dropdown.Item
68
- icon="delete"
69
- text={formatMessage({
70
- id: `grant.actions.request_removal.confirmation.header`,
71
- })}
72
- />
73
- </GrantRemoval>
74
- ),
75
- cancelRemoval: (
76
- <GrantRemoval
77
- grant={grant}
78
- previousStructureQuery={previousStructureQuery}
79
- key="cancelRemoval"
80
- >
81
- <Dropdown.Item
82
- icon="delete"
83
- text={formatMessage({
84
- id: `grant.actions.cancel_removal.confirmation.header`,
85
- })}
86
- />
87
- </GrantRemoval>
88
- ),
89
61
  requestChange: (
90
62
  <GrantChangeRequest
91
63
  structure={structure}
@@ -133,14 +105,9 @@ export const StructureGrantDropdown = ({
133
105
  ? "structure.grant.modification.attended"
134
106
  : "structure.grant.access.attended",
135
107
  },
136
- pendingRemovalGrant: {
137
- color: "red",
138
- options: [actions["cancelRemoval"]],
139
- header: "structure.grant.has_grant_pending_removal",
140
- },
141
108
  activeGrant: {
142
109
  color: "green",
143
- options: [actions["requestChange"], actions["requestRemoval"]],
110
+ options: [actions["requestChange"]],
144
111
  header: "structure.grant.has_grant",
145
112
  },
146
113
  };
@@ -149,8 +116,6 @@ export const StructureGrantDropdown = ({
149
116
  ? "pendingRequest"
150
117
  : hasProcessingGrantRequest
151
118
  ? "processingRequest"
152
- : grant?.pending_removal
153
- ? "pendingRemovalGrant"
154
119
  : "activeGrant";
155
120
 
156
121
  const stateProps = dropdownStates[currentState];
@@ -173,7 +138,9 @@ export const StructureGrantDropdown = ({
173
138
 
174
139
  const popupContent = (
175
140
  <>
176
- <Header as="h6">{formatMessage({ id: stateProps.header })}</Header>
141
+ {stateProps?.header ? (
142
+ <Header as="h6">{formatMessage({ id: stateProps.header })}</Header>
143
+ ) : null}
177
144
  {grantRequest ? grantRequestDetails : grantDetails}
178
145
  </>
179
146
  );
@@ -181,7 +148,7 @@ export const StructureGrantDropdown = ({
181
148
  const optionsWithCart =
182
149
  authorized && !requested
183
150
  ? [actions["addToCart"], ...stateProps.options]
184
- : stateProps.options;
151
+ : stateProps?.options;
185
152
 
186
153
  const trigger = (
187
154
  <Dropdown
@@ -4,7 +4,7 @@ import PropTypes from "prop-types";
4
4
  import { useQuery } from "@apollo/client";
5
5
  import { connect } from "react-redux";
6
6
  import { Loading } from "@truedat/core/components";
7
- import { LATEST_GRANT_REQUEST_QUERY } from "../api/queries";
7
+ import { LATEST_GRANT_REQUEST_BY_DS_QUERY } from "../api/queries";
8
8
  import StructureGrantRequestButton from "./StructureGrantRequestButton";
9
9
  import StructureGrantDropdown from "./StructureGrantDropdown";
10
10
 
@@ -17,7 +17,7 @@ export const StructureGrantSummaryButton = ({
17
17
  }) => {
18
18
  const { grant, id } = structure;
19
19
 
20
- const { loading, error, data } = useQuery(LATEST_GRANT_REQUEST_QUERY, {
20
+ const { loading, error, data } = useQuery(LATEST_GRANT_REQUEST_BY_DS_QUERY, {
21
21
  fetchPolicy: "cache-and-network",
22
22
  variables: { id },
23
23
  });
@@ -2,35 +2,25 @@ import _ from "lodash/fp";
2
2
  import React from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { useIntl } from "react-intl";
5
- import { Table, Button } from "semantic-ui-react";
5
+ import { Table } from "semantic-ui-react";
6
6
  import { connect } from "react-redux";
7
+ import { useHistory } from "react-router-dom";
7
8
  import { columnDecorator } from "@truedat/core/services";
9
+ import { linkTo } from "@truedat/core/routes";
8
10
  import { getGrantsColumns } from "../selectors";
9
11
  import GrantRemoval from "./GrantRemoval";
10
12
 
11
- export const RemovalButton = ({ grant }) => (
12
- <GrantRemoval grant={grant}>
13
- {grant.pending_removal ? (
14
- <Button size="mini" color="olive" icon="undo" />
15
- ) : (
16
- <Button size="mini" color="red" icon="delete" />
17
- )}
18
- </GrantRemoval>
19
- );
20
-
21
- RemovalButton.propTypes = {
22
- grant: PropTypes.object,
23
- };
24
-
25
13
  const matchesStructure = ({ id }) => _.pathEq("data_structure.id", id);
26
14
 
27
15
  export const StructureGrants = ({
28
16
  columns,
29
17
  grants,
18
+ grantsActions,
30
19
  structure,
31
20
  userPermissions,
32
21
  }) => {
33
22
  const { formatMessage } = useIntl();
23
+ const history = useHistory();
34
24
  const canUpdateRemoval =
35
25
  userPermissions?.update_grant_removal &&
36
26
  _.any(matchesStructure(structure))(grants);
@@ -54,19 +44,31 @@ export const StructureGrants = ({
54
44
  </Table.Header>
55
45
  <Table.Body>
56
46
  {grants.map((grant, i) => (
57
- <Table.Row key={i}>
47
+ // Move style to file after task to create "CSS entrypoint" is done
48
+ <Table.Row key={i} style={{ cursor: "pointer" }}>
58
49
  {columns.map((column, key) => (
59
50
  <Table.Cell
60
51
  key={key}
61
52
  textAlign={column.textAlign}
62
53
  content={columnDecorator(column)(grant)}
54
+ onClick={(ev) => {
55
+ /* Only do history.push if the table cell but not
56
+ * some possibly contained anchor (or an element
57
+ * inside an anchor, such as an icon) is clicked
58
+ */
59
+ !ev.target.closest("a") && history.push(linkTo.GRANT(grant));
60
+ }}
63
61
  />
64
62
  ))}
65
63
  {canUpdateRemoval ? (
66
64
  <Table.Cell
67
65
  content={
68
66
  matchesStructure(structure)(grant) ? (
69
- <RemovalButton grant={grant} />
67
+ <GrantRemoval
68
+ grant={grant}
69
+ actions={grantsActions}
70
+ userPermissions={userPermissions}
71
+ />
70
72
  ) : null
71
73
  }
72
74
  />
@@ -80,6 +82,7 @@ export const StructureGrants = ({
80
82
 
81
83
  StructureGrants.propTypes = {
82
84
  grants: PropTypes.array,
85
+ grantsActions: PropTypes.object,
83
86
  columns: PropTypes.array,
84
87
  structure: PropTypes.object,
85
88
  userPermissions: PropTypes.object,
@@ -90,6 +93,7 @@ const mapStateToProps = (state) => ({
90
93
  structure: state.structure,
91
94
  columns: getGrantsColumns(state),
92
95
  userPermissions: state.userPermissions,
96
+ grantsActions: state?.structure?._actions?._grants,
93
97
  });
94
98
 
95
99
  export default connect(mapStateToProps)(StructureGrants);
@@ -67,6 +67,7 @@ export const StructureNoteActions = ({
67
67
 
68
68
  const parsedActions = _.flow(
69
69
  _.toPairs,
70
+ _.reject(([action, _data]) => !_.get(action)(actionToIcon)),
70
71
  _.map(([action, data]) => {
71
72
  const baseAction = {
72
73
  key: action,
@@ -0,0 +1,179 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState } from "react";
3
+ import PropTypes from "prop-types";
4
+ import {
5
+ Button,
6
+ Checkbox,
7
+ Container,
8
+ Divider,
9
+ Icon,
10
+ List,
11
+ Message,
12
+ Segment,
13
+ } from "semantic-ui-react";
14
+ import { FormattedMessage, useIntl } from "react-intl";
15
+ import { apiJson } from "@truedat/core/services/api";
16
+
17
+ export default function StructureNoteSuggestions({
18
+ aiSuggestionsAction,
19
+ applySuggestions,
20
+ template,
21
+ isModification,
22
+ }) {
23
+ const [loadingSuggestion, setLoadingSuggestion] = useState();
24
+ const [suggestions, setSuggestions] = useState();
25
+ const [suggestionsError, setSuggestionsError] = useState();
26
+ const [selectedSuggestions, setSelectedSuggestions] = useState([]);
27
+
28
+ const { locale } = useIntl();
29
+
30
+ const suggestionKeys = _.keys(suggestions);
31
+ const fieldInSuggestions = ({ name }) => _.includes(name)(suggestionKeys);
32
+ const templateFields = _.flow(
33
+ _.prop("content"),
34
+ _.filter(({ fields }) => _.any(fieldInSuggestions)(fields)),
35
+ _.map(({ name, fields }) => ({
36
+ name,
37
+ fields: _.filter(fieldInSuggestions)(fields),
38
+ }))
39
+ )(template);
40
+ const validFields = _.flow(
41
+ _.prop("content"),
42
+ _.flatMap(({ fields }) =>
43
+ _.flow(
44
+ _.filter(({ editable }) => editable || !isModification),
45
+ _.map(({ name }) => name)
46
+ )(fields)
47
+ )
48
+ )(template);
49
+
50
+ const handleRequestSuggestions = () => {
51
+ setLoadingSuggestion(true);
52
+ setSuggestionsError(null);
53
+ setSelectedSuggestions([]);
54
+ setSuggestions(null);
55
+ const url = `${aiSuggestionsAction.href}?language=${locale}`;
56
+ apiJson(url).then(({ data: { data: suggestions } }) => {
57
+ if (_.prop("[0]")(suggestions) === "error") {
58
+ setSuggestionsError(
59
+ _.prop("[1].error.message")(suggestions) || _.prop("[1]")(suggestions)
60
+ );
61
+ } else {
62
+ setSuggestions(suggestions);
63
+ const selectedFields = _.flow(
64
+ _.keys,
65
+ _.filter(
66
+ (key) =>
67
+ _.includes(key)(validFields) && !_.isEmpty(suggestions[key])
68
+ )
69
+ )(suggestions);
70
+ setSelectedSuggestions(selectedFields);
71
+ }
72
+ setLoadingSuggestion(false);
73
+ });
74
+ };
75
+ const handleApplySuggestions = () => {
76
+ applySuggestions(_.pick(selectedSuggestions)(suggestions));
77
+ setSelectedSuggestions([]);
78
+ setSuggestions(null);
79
+ };
80
+
81
+ const toggleSelectedSuggestion = (name) => {
82
+ setSelectedSuggestions(
83
+ _.includes(name)(selectedSuggestions)
84
+ ? _.reject((v) => v == name)(selectedSuggestions)
85
+ : [...selectedSuggestions, name]
86
+ );
87
+ };
88
+
89
+ return (
90
+ <Segment loading={loadingSuggestion}>
91
+ <Container style={{ display: "flex", justifyContent: "space-between" }}>
92
+ <h4>
93
+ {suggestions ? (
94
+ <Icon name="lightbulb outline" />
95
+ ) : (
96
+ <Icon name="lightbulb" />
97
+ )}
98
+ <FormattedMessage id="structure_note.ai_suggestion.header" />
99
+ </h4>
100
+ <Divider hidden />
101
+ {suggestions ? null : (
102
+ <Button onClick={handleRequestSuggestions}>
103
+ <FormattedMessage id="actions.ai_suggestion" />
104
+ </Button>
105
+ )}
106
+ </Container>
107
+ {suggestionsError ? (
108
+ <Message negative>
109
+ <Message.Header>
110
+ <FormattedMessage id="structure_note.ai_suggestion.error" />
111
+ </Message.Header>
112
+ <p>{suggestionsError}</p>
113
+ </Message>
114
+ ) : null}
115
+ {suggestions ? (
116
+ <>
117
+ {_.map(({ name: groupName, fields }) => (
118
+ <Container key={`group-${groupName}`}>
119
+ <>
120
+ <h5>{groupName ? groupName : ""}</h5>
121
+ <List>
122
+ {_.map(({ name, label, editable }) => (
123
+ <List.Item
124
+ key={name}
125
+ style={{
126
+ display: "flex",
127
+ alignItems: "center",
128
+ gap: "12px",
129
+ }}
130
+ >
131
+ <Checkbox
132
+ id={name}
133
+ disabled={
134
+ (isModification && !editable) ||
135
+ _.isEmpty(suggestions[name])
136
+ }
137
+ checked={_.includes(name)(selectedSuggestions)}
138
+ onChange={() => toggleSelectedSuggestion(name)}
139
+ />
140
+ <label
141
+ htmlFor={name}
142
+ style={{ cursor: "pointer" }}
143
+ disabled
144
+ >
145
+ <List.Header>{label}</List.Header>
146
+ <List.Description>
147
+ {_.isEmpty(suggestions[name])
148
+ ? "-"
149
+ : suggestions[name]}
150
+ </List.Description>
151
+ </label>
152
+ </List.Item>
153
+ ))(fields)}
154
+ </List>
155
+ <Divider hidden />
156
+ </>
157
+ </Container>
158
+ ))(templateFields)}
159
+ <Container textAlign="right">
160
+ <Button
161
+ primary
162
+ onClick={handleApplySuggestions}
163
+ disabled={_.isEmpty(selectedSuggestions)}
164
+ >
165
+ <FormattedMessage id="actions.apply_ai_suggestion" />
166
+ </Button>
167
+ </Container>
168
+ </>
169
+ ) : null}
170
+ </Segment>
171
+ );
172
+ }
173
+
174
+ StructureNoteSuggestions.propTypes = {
175
+ aiSuggestionsAction: PropTypes.object,
176
+ applySuggestions: PropTypes.func,
177
+ template: PropTypes.object,
178
+ isModification: PropTypes.bool,
179
+ };
@@ -9,6 +9,7 @@ import { selectTemplate, selectDomains } from "@truedat/df/routines";
9
9
  import { applyTemplate, validateContent } from "@truedat/df/utils";
10
10
  import { doStructureNoteAction } from "../routines";
11
11
  import { getLatestDfContent } from "../selectors/getSortedStructureNotes";
12
+ import StructureNoteSuggestions from "./StructureNoteSuggestions";
12
13
 
13
14
  const DynamicForm = React.lazy(() =>
14
15
  import("@truedat/df/components/DynamicForm")
@@ -29,6 +30,9 @@ export const StructureNotesEdit = ({
29
30
  latestDfContent,
30
31
  }) => {
31
32
  const [content, setContent] = useState(latestDfContent);
33
+ const aiSuggestionsAction = _.prop("_actions.ai_suggestions")(structureNotes);
34
+
35
+ const isModification = !_.isEmpty(latestDfContent);
32
36
 
33
37
  useEffect(() => {
34
38
  selectDomains(structure.domain_ids);
@@ -41,7 +45,7 @@ export const StructureNotesEdit = ({
41
45
  }
42
46
  }, [applyTemplate, template, latestDfContent, structure.domain_id]);
43
47
 
44
- const { formatMessage } = useIntl();
48
+ const { formatMessage, locale } = useIntl();
45
49
 
46
50
  const submitDisabled =
47
51
  !template || _.size(validateContent(template)(content)) > 0;
@@ -67,14 +71,27 @@ export const StructureNotesEdit = ({
67
71
  doStructureNoteAction(payload);
68
72
  };
69
73
 
74
+ const applySuggestions = (suggestions) => {
75
+ setContent({ ...content, ...suggestions });
76
+ };
77
+
70
78
  return (
71
79
  <Segment attached="bottom">
72
80
  <TemplateLoader />
81
+
82
+ {aiSuggestionsAction ? (
83
+ <StructureNoteSuggestions
84
+ aiSuggestionsAction={aiSuggestionsAction}
85
+ template={template}
86
+ applySuggestions={applySuggestions}
87
+ isModification={isModification}
88
+ />
89
+ ) : null}
73
90
  <Form>
74
91
  <DynamicForm
75
92
  onChange={setContent}
76
93
  content={content}
77
- isModification={true}
94
+ isModification={isModification}
78
95
  />
79
96
  <Button
80
97
  primary
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import userEvent from "@testing-library/user-event";
3
3
  import { render } from "@truedat/test/render";
4
+ import { waitFor } from "@testing-library/react";
4
5
  import { Grant } from "../Grant";
5
6
  import en from "../../messages/en";
6
7
 
@@ -45,7 +46,7 @@ describe("<Grant />", () => {
45
46
  },
46
47
  };
47
48
  const actions = {
48
- request_removal: {},
49
+ manage_grant_removal: {},
49
50
  };
50
51
 
51
52
  const defaultProps = {
@@ -71,25 +72,28 @@ describe("<Grant />", () => {
71
72
  expect(container).toMatchSnapshot();
72
73
  });
73
74
 
74
- it("matches the latest snapshot with cancel_removal action", () => {
75
+ it("matches the latest snapshot with manage_grant_removal action", async () => {
75
76
  const props = {
76
77
  ...defaultProps,
78
+ grant: { ...grant, pending_removal: true },
77
79
  actions: {
78
- cancel_removal: {},
80
+ manage_grant_removal: {},
79
81
  },
80
82
  };
81
- const { container } = render(<Grant {...props} />, renderOpts);
83
+ const { container, queryByText } = render(<Grant {...props} />, renderOpts);
84
+ await waitFor(() => expect(queryByText(/lazy/i)).not.toBeInTheDocument());
82
85
  expect(container).toMatchSnapshot();
83
86
  });
84
87
 
85
- it("matches the latest snapshot with update action", () => {
88
+ it("matches the latest snapshot with update action", async () => {
86
89
  const props = {
87
90
  ...defaultProps,
88
91
  actions: {
89
92
  update: {},
90
93
  },
91
94
  };
92
- const { container } = render(<Grant {...props} />, renderOpts);
95
+ const { container, queryByText } = render(<Grant {...props} />, renderOpts);
96
+ await waitFor(() => expect(queryByText(/lazy/i)).not.toBeInTheDocument());
93
97
  expect(container).toMatchSnapshot();
94
98
  });
95
99
 
@@ -102,7 +106,7 @@ describe("<Grant />", () => {
102
106
  userEvent.click(await findByText("yes"));
103
107
 
104
108
  expect(defaultProps.onStatusChange).toHaveBeenLastCalledWith({
105
- action: "request_removal",
109
+ action: "mark_pending_removal",
106
110
  data_structure_id: 2,
107
111
  id: 1,
108
112
  });
@@ -1,68 +1,33 @@
1
1
  import React from "react";
2
- import userEvent from "@testing-library/user-event";
3
2
  import { render } from "@truedat/test/render";
4
- import { requestGrantRemoval } from "../../routines";
5
3
  import GrantRemoval from "../GrantRemoval";
6
4
 
7
5
  describe("<GrantRemoval />", () => {
8
6
  const grant = {
9
- id: 1,
10
- detail: { foo: "bar" },
11
- data_structure: { id: 2 },
12
- data_structure_version: { id: 3 },
7
+ id: 17,
13
8
  };
14
- it("matches the latest snapshot", () => {
15
- const { container } = render(
16
- <GrantRemoval grant={grant}>
17
- <p>child</p>
18
- </GrantRemoval>
19
- );
20
- expect(container).toMatchSnapshot();
21
- });
22
-
23
- it("on click calls requestGrantRemoval routine if onConfirm si undefined", async () => {
24
- const dispatch = jest.fn();
25
- const { findByText } = render(
26
- <GrantRemoval grant={grant}>
27
- <p>child</p>
28
- </GrantRemoval>,
29
- { dispatch }
30
- );
31
-
32
- userEvent.click(await findByText("child"));
33
- expect(await findByText("Request grant removal")).toBeInTheDocument();
34
9
 
35
- userEvent.click(await findByText("Yes"));
10
+ const actions_with_request_cancel = {
11
+ manage_grant_removal: {},
12
+ };
36
13
 
37
- expect(dispatch).toHaveBeenLastCalledWith({
38
- ...requestGrantRemoval(),
39
- payload: {
40
- action: "request_removal",
41
- data_structure_id: 2,
42
- id: 1,
43
- version: 3,
44
- },
45
- });
14
+ it("Mounts grant removal/reactivation button if user has permissions, remove", () => {
15
+ const { queryByText } = render(
16
+ <GrantRemoval
17
+ grant={{ ...grant, pending_removal: false }}
18
+ actions={actions_with_request_cancel}
19
+ />
20
+ );
21
+ expect(queryByText(/Remove/)).toBeInTheDocument();
46
22
  });
47
23
 
48
- it("on click calls onConfirm prop", async () => {
49
- const onConfirm = jest.fn();
50
- const { findByText } = render(
51
- <GrantRemoval grant={grant} onConfirm={onConfirm}>
52
- <p>child</p>
53
- </GrantRemoval>
24
+ it("Mounts grant removal/reactivation button if user has permissions, reactivate", () => {
25
+ const { queryByText } = render(
26
+ <GrantRemoval
27
+ grant={{ ...grant, pending_removal: true }}
28
+ actions={actions_with_request_cancel}
29
+ />
54
30
  );
55
-
56
- userEvent.click(await findByText("child"));
57
- expect(await findByText("Request grant removal")).toBeInTheDocument();
58
-
59
- userEvent.click(await findByText("Yes"));
60
-
61
- expect(onConfirm).toHaveBeenLastCalledWith({
62
- action: "request_removal",
63
- data_structure_id: 2,
64
- id: 1,
65
- version: 3,
66
- });
31
+ expect(queryByText(/Reactivate/)).toBeInTheDocument();
67
32
  });
68
33
  });
@@ -0,0 +1,66 @@
1
+ import React from "react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { render } from "@truedat/test/render";
4
+
5
+ import GrantRemovalDirectButton from "../GrantRemovalDirectButton";
6
+
7
+ describe("<GrantRemovalDirectButton />", () => {
8
+ const grant = {
9
+ id: 1,
10
+ detail: { foo: "bar" },
11
+ data_structure: { id: 2 },
12
+ data_structure_version: { id: 3 },
13
+ };
14
+
15
+ it("on click calls requestGrantRemoval routine if onConfirm is undefined", async () => {
16
+ const requestGrantRemoval = jest.fn();
17
+ const { findByText } = render(
18
+ <GrantRemovalDirectButton
19
+ grant={grant}
20
+ operation="markPendingRemoval"
21
+ actions={{ manage_grant_removal: {} }}
22
+ requestGrantRemoval={requestGrantRemoval}
23
+ previousStructureQuery={{}}
24
+ />
25
+ );
26
+
27
+ userEvent.click(await findByText("Remove"));
28
+ expect(await findByText("Request grant removal")).toBeInTheDocument();
29
+
30
+ userEvent.click(await findByText("Yes"));
31
+
32
+ expect(requestGrantRemoval).toHaveBeenCalledWith({
33
+ action: "mark_pending_removal",
34
+ data_structure_id: 2,
35
+ id: 1,
36
+ version: 3,
37
+ previousStructureQuery: {},
38
+ });
39
+ });
40
+
41
+ it("on click calls onConfirm prop", async () => {
42
+ const onConfirm = jest.fn();
43
+ const { findByText } = render(
44
+ <GrantRemovalDirectButton
45
+ grant={grant}
46
+ operation="markPendingRemoval"
47
+ actions={{ manage_grant_removal: {} }}
48
+ onConfirm={onConfirm}
49
+ previousStructureQuery={{}}
50
+ />
51
+ );
52
+
53
+ userEvent.click(await findByText("Remove"));
54
+ expect(await findByText("Request grant removal")).toBeInTheDocument();
55
+
56
+ userEvent.click(await findByText("Yes"));
57
+
58
+ expect(onConfirm).toHaveBeenLastCalledWith({
59
+ action: "mark_pending_removal",
60
+ data_structure_id: 2,
61
+ id: 1,
62
+ version: 3,
63
+ previousStructureQuery: {},
64
+ });
65
+ });
66
+ });