@truedat/dd 4.54.12 → 4.56.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 (44) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +6 -6
  3. package/src/api/mutations.js +28 -0
  4. package/src/api/queries.js +36 -0
  5. package/src/components/GrantApprovalRule.js +149 -0
  6. package/src/components/GrantApprovalRuleConditions.js +134 -0
  7. package/src/components/GrantApprovalRuleDeleteButton.js +44 -0
  8. package/src/components/GrantApprovalRuleEdit.js +38 -0
  9. package/src/components/GrantApprovalRuleForm.js +264 -0
  10. package/src/components/GrantApprovalRuleNew.js +15 -0
  11. package/src/components/GrantApprovalRules.js +113 -0
  12. package/src/components/GrantRequest.js +4 -3
  13. package/src/components/GrantRoutes.js +30 -0
  14. package/src/components/__tests__/GrantApprovalRule.spec.js +69 -0
  15. package/src/components/__tests__/GrantApprovalRuleConditions.spec.js +120 -0
  16. package/src/components/__tests__/GrantApprovalRuleDeleteButton.spec.js +56 -0
  17. package/src/components/__tests__/GrantApprovalRuleEdit.spec.js +76 -0
  18. package/src/components/__tests__/GrantApprovalRuleForm.spec.js +135 -0
  19. package/src/components/__tests__/GrantApprovalRuleNew.spec.js +40 -0
  20. package/src/components/__tests__/GrantApprovalRules.spec.js +107 -0
  21. package/src/components/__tests__/StructureGrantDropdown.spec.js +1 -1
  22. package/src/components/__tests__/__snapshots__/GrantApprovalRule.spec.js.snap +239 -0
  23. package/src/components/__tests__/__snapshots__/GrantApprovalRuleConditions.spec.js.snap +819 -0
  24. package/src/components/__tests__/__snapshots__/GrantApprovalRuleDeleteButton.spec.js.snap +11 -0
  25. package/src/components/__tests__/__snapshots__/GrantApprovalRuleEdit.spec.js.snap +487 -0
  26. package/src/components/__tests__/__snapshots__/GrantApprovalRuleForm.spec.js.snap +1036 -0
  27. package/src/components/__tests__/__snapshots__/GrantApprovalRuleNew.spec.js.snap +464 -0
  28. package/src/components/__tests__/__snapshots__/GrantApprovalRules.spec.js.snap +266 -0
  29. package/src/components/__tests__/__snapshots__/GrantRoutes.spec.js.snap +27 -5
  30. package/src/messages/en.js +40 -0
  31. package/src/messages/es.js +41 -0
  32. package/src/reducers/structureRedirect.js +23 -3
  33. package/src/routines.js +3 -0
  34. package/src/sagas/__tests__/createGrantApprovalRule.spec.js +96 -0
  35. package/src/sagas/__tests__/deleteGrantApprovalRule.spec.js +85 -0
  36. package/src/sagas/__tests__/updateGrantApprovalRule.spec.js +96 -0
  37. package/src/sagas/createGrantApprovalRule.js +26 -0
  38. package/src/sagas/deleteGrantApprovalRule.js +32 -0
  39. package/src/sagas/index.js +9 -0
  40. package/src/sagas/updateGrantApprovalRule.js +26 -0
  41. package/src/selectors/__tests__/getGrantsColumns.spec.js +4 -0
  42. package/src/components/GrantRequestCrumbs.js +0 -25
  43. package/src/components/__tests__/GrantRequestCrumbs.spec.js +0 -18
  44. package/src/components/__tests__/__snapshots__/GrantRequestCrumbs.spec.js.snap +0 -100
@@ -0,0 +1,264 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { useForm, useWatch, Controller } from "react-hook-form";
5
+ import { useIntl } from "react-intl";
6
+ import { useQuery } from "@apollo/client";
7
+ import { Button, Form, Segment, Label } from "semantic-ui-react";
8
+ import { DomainSelector, HistoryBackButton } from "@truedat/core/components";
9
+ import { CURRENT_ROLES } from "@truedat/core/api/queries";
10
+ import GrantApprovalRuleConditions from "./GrantApprovalRuleConditions";
11
+
12
+ const initialValues = {
13
+ name: "",
14
+ action: "approve",
15
+ role: null,
16
+ domainId: null,
17
+ comment: "",
18
+ conditions: [{}],
19
+ };
20
+
21
+ export default function GrantApprovalRuleForm({ onSubmit, approvalRule }) {
22
+ const { formatMessage } = useIntl();
23
+
24
+ const defaultValues = approvalRule
25
+ ? {
26
+ ..._.pick(["id", "name", "action", "role", "comment"])(approvalRule),
27
+ conditions: _.flow(
28
+ _.get("conditions"),
29
+ _.map(({ field, operator, value }) => {
30
+ const fields = field.match(/(\w+)\.(.*)/);
31
+ const fieldPrefix = fields ? fields[1] : null;
32
+ const fieldName = fields ? fields[2] : field;
33
+ return { fieldPrefix, fieldName, operator, value };
34
+ })
35
+ )(approvalRule),
36
+ domainId: _.path("domainIds[0]")(approvalRule),
37
+ }
38
+ : initialValues;
39
+
40
+ const { handleSubmit, control, formState } = useForm({
41
+ mode: "all",
42
+ defaultValues,
43
+ });
44
+
45
+ const domainId = useWatch({ name: "domainId", control });
46
+
47
+ const {
48
+ error,
49
+ loading,
50
+ data: rolesData,
51
+ } = useQuery(CURRENT_ROLES, {
52
+ variables: { domainId, permission: "approve_grant_request" },
53
+ });
54
+ const roleOptions =
55
+ loading || error
56
+ ? null
57
+ : _.flow(
58
+ _.get("currentRoles"),
59
+ _.map((role) => ({ key: role, value: role, text: role }))
60
+ )(rolesData);
61
+
62
+ const doHandleSubmit = handleSubmit((data) => {
63
+ const { domainId } = data;
64
+ const conditions = _.flow(
65
+ _.get("conditions"),
66
+ _.map(({ fieldPrefix, fieldName, operator, value }) => ({
67
+ field: [fieldPrefix, fieldName].join("."),
68
+ operator,
69
+ value,
70
+ }))
71
+ )(data);
72
+ const approvalRule = _.flow(
73
+ _.omit(["domainId"]),
74
+ _.set("domainIds", [domainId]),
75
+ _.set("conditions", conditions)
76
+ )(data);
77
+ onSubmit({ approvalRule });
78
+ });
79
+
80
+ const disabledForm = !domainId || !roleOptions;
81
+
82
+ const { isValid } = formState;
83
+
84
+ const validateConditions = (value) =>
85
+ _.size(value) >= 1 &&
86
+ _.every((condition) =>
87
+ _.every(
88
+ (field) =>
89
+ !_.isNil(_.get(field)(condition)) &&
90
+ !_.isEmpty(_.get(field)(condition))
91
+ )(["fieldPrefix", "fieldName", "operator", "value"])
92
+ )(value);
93
+
94
+ return (
95
+ <>
96
+ <Form onSubmit={doHandleSubmit}>
97
+ <Controller
98
+ control={control}
99
+ name="name"
100
+ rules={{ required: true }}
101
+ render={({
102
+ field: { onBlur, onChange, value },
103
+ fieldState: { error },
104
+ }) => (
105
+ <Form.Input
106
+ id="name"
107
+ error={!!error}
108
+ label={formatMessage({ id: "grantApprovalRule.name" })}
109
+ onBlur={onBlur}
110
+ onChange={(_e, { value }) => onChange(value)}
111
+ placeholder={formatMessage({
112
+ id: "grantApprovalRule.name.placeholder",
113
+ })}
114
+ value={value}
115
+ required
116
+ />
117
+ )}
118
+ />
119
+ <Controller
120
+ control={control}
121
+ name="domainId"
122
+ rules={{ required: true }}
123
+ render={({ field: { onChange, value } }) => (
124
+ <DomainSelector
125
+ action="approveGrantRequests"
126
+ label={formatMessage({ id: "domain.selector.label" })}
127
+ required
128
+ labels
129
+ value={value}
130
+ onChange={(_e, { value }) => onChange(value)}
131
+ />
132
+ )}
133
+ />
134
+
135
+ <Controller
136
+ control={control}
137
+ name="role"
138
+ render={({ field: { onBlur, onChange, value } }) => (
139
+ <Form.Dropdown
140
+ required
141
+ disabled={disabledForm}
142
+ name="role"
143
+ label={formatMessage({ id: "grantApprovalRule.role" })}
144
+ selection
145
+ options={roleOptions}
146
+ onBlur={onBlur}
147
+ onChange={(_e, { value }) => onChange(value)}
148
+ placeholder={formatMessage({
149
+ id: "grantApprovalRule.role.placeholder",
150
+ })}
151
+ value={value}
152
+ />
153
+ )}
154
+ />
155
+
156
+ <Controller
157
+ control={control}
158
+ rules={{ required: true }}
159
+ name="action"
160
+ render={({ field: { onBlur, onChange, value } }) => (
161
+ <Form.Dropdown
162
+ required
163
+ disabled={disabledForm}
164
+ name="action"
165
+ label={formatMessage({ id: "grantApprovalRule.action" })}
166
+ selection
167
+ options={["approve", "reject"].map((action, key) => ({
168
+ key,
169
+ value: action,
170
+ text: formatMessage({
171
+ id: `grantApprovalRule.action.${action}`,
172
+ }),
173
+ }))}
174
+ onBlur={onBlur}
175
+ onChange={(_e, { value }) => onChange(value)}
176
+ placeholder={formatMessage({
177
+ id: "grantApprovalRule.action.placeholder",
178
+ })}
179
+ value={value}
180
+ />
181
+ )}
182
+ />
183
+
184
+ <Controller
185
+ control={control}
186
+ name="comment"
187
+ render={({
188
+ field: { onBlur, onChange, value },
189
+ fieldState: { error },
190
+ }) => (
191
+ <Form.Input
192
+ id="comment"
193
+ label={formatMessage({ id: "grantApprovalRule.comment" })}
194
+ disabled={disabledForm}
195
+ error={!!error}
196
+ onBlur={onBlur}
197
+ onChange={(_e, { value }) => onChange(value)}
198
+ value={value}
199
+ placeholder={formatMessage({
200
+ id: "grantApprovalRule.comment.placeholder",
201
+ })}
202
+ />
203
+ )}
204
+ />
205
+
206
+ <Controller
207
+ control={control}
208
+ name="conditions"
209
+ rules={{
210
+ validate: {
211
+ conditions: (value) =>
212
+ validateConditions(value) || "invalid_conditions",
213
+ },
214
+ }}
215
+ render={({
216
+ field: { onBlur, onChange, value },
217
+ fieldState: { error },
218
+ }) => (
219
+ <Segment disabled={disabledForm}>
220
+ <div className="field">
221
+ <label>
222
+ {formatMessage({
223
+ id: "grantApprovalRule.conditions",
224
+ })}
225
+ {error ? (
226
+ <Label pointing="left">
227
+ {formatMessage({
228
+ id: `grantApprovalRule.conditions.error.${error.message}`,
229
+ })}
230
+ </Label>
231
+ ) : null}
232
+ </label>
233
+ </div>
234
+ <GrantApprovalRuleConditions
235
+ disabled={disabledForm}
236
+ onChange={onChange}
237
+ value={value}
238
+ onBlur={onBlur}
239
+ />
240
+ </Segment>
241
+ )}
242
+ />
243
+
244
+ <div className="actions">
245
+ <HistoryBackButton
246
+ content={formatMessage({ id: "actions.cancel" })}
247
+ />
248
+ <Button
249
+ primary
250
+ type="submit"
251
+ // loading={isLoading}
252
+ disabled={!isValid}
253
+ content={formatMessage({ id: "actions.save" })}
254
+ />
255
+ </div>
256
+ </Form>
257
+ </>
258
+ );
259
+ }
260
+
261
+ GrantApprovalRuleForm.propTypes = {
262
+ onSubmit: PropTypes.func,
263
+ approvalRule: PropTypes.object,
264
+ };
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import { connect } from "react-redux";
4
+ import { createGrantApprovalRule } from "../routines";
5
+ import GrantApprovalRuleForm from "./GrantApprovalRuleForm";
6
+
7
+ export const GrantApprovalRuleNew = ({ createGrantApprovalRule }) => {
8
+ return <GrantApprovalRuleForm onSubmit={createGrantApprovalRule} />;
9
+ };
10
+
11
+ GrantApprovalRuleNew.propTypes = {
12
+ createGrantApprovalRule: PropTypes.func,
13
+ };
14
+
15
+ export default connect(null, { createGrantApprovalRule })(GrantApprovalRuleNew);
@@ -0,0 +1,113 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import { useIntl } from "react-intl";
4
+ import { useQuery } from "@apollo/client";
5
+ import {
6
+ Button,
7
+ Header,
8
+ Icon,
9
+ Loader,
10
+ Segment,
11
+ Table,
12
+ } from "semantic-ui-react";
13
+ import { Link, useHistory } from "react-router-dom";
14
+ import { GRANT_APPROVAL_RULE_NEW, linkTo } from "@truedat/core/routes";
15
+ import { columnDecoratorComponent } from "@truedat/core/services";
16
+ import { GRANT_APPROVAL_RULES_QUERY } from "../api/queries";
17
+
18
+ export default function GrantApprovalRules() {
19
+ const { formatMessage } = useIntl();
20
+ const history = useHistory();
21
+
22
+ const columns = [
23
+ { name: "name" },
24
+ { name: "action" },
25
+ { name: "role" },
26
+ {
27
+ name: "domain",
28
+ fieldSelector: _.prop("domains[0].name"),
29
+ },
30
+ ];
31
+
32
+ const { loading, data } = useQuery(GRANT_APPROVAL_RULES_QUERY, {
33
+ fetchPolicy: "cache-and-network",
34
+ });
35
+
36
+ const grantApprovalRules = _.get("grantApprovalRules")(data);
37
+ return (
38
+ <Segment>
39
+ <Header as="h2">
40
+ <Icon circular name="key" />
41
+ <Header.Content>
42
+ {formatMessage({ id: "grantApprovalRules.header" })}
43
+ <Header.Subheader>
44
+ {formatMessage({ id: "grantApprovalRules.subheader" })}
45
+ </Header.Subheader>
46
+ </Header.Content>
47
+ </Header>
48
+ <Segment textAlign="right" basic>
49
+ <Button
50
+ primary
51
+ as={Link}
52
+ to={GRANT_APPROVAL_RULE_NEW}
53
+ content={formatMessage({
54
+ id: "grantApprovalRules.new",
55
+ })}
56
+ />
57
+ </Segment>
58
+
59
+ {!_.isEmpty(grantApprovalRules) && !loading ? (
60
+ <Table selectable>
61
+ <Table.Header>
62
+ <Table.Row>
63
+ {columns.map((column, i) => {
64
+ return (
65
+ <Table.HeaderCell
66
+ key={i}
67
+ width={column.width}
68
+ content={formatMessage({
69
+ id: `grantApprovalRule.${column.name}`,
70
+ })}
71
+ />
72
+ );
73
+ })}
74
+ </Table.Row>
75
+ </Table.Header>
76
+ <Table.Body>
77
+ {grantApprovalRules.map((grantApprovalRule, i) => {
78
+ return (
79
+ <Table.Row
80
+ key={i}
81
+ onClick={() =>
82
+ history.push(
83
+ linkTo.GRANT_APPROVAL_RULE({ id: grantApprovalRule.id })
84
+ )
85
+ }
86
+ >
87
+ {columns.map((column, key) => (
88
+ <Table.Cell
89
+ key={key}
90
+ textAlign={column.textAlign}
91
+ content={columnDecoratorComponent(column)(
92
+ grantApprovalRule
93
+ )}
94
+ />
95
+ ))}
96
+ </Table.Row>
97
+ );
98
+ })}
99
+ </Table.Body>
100
+ </Table>
101
+ ) : loading ? (
102
+ <Loader />
103
+ ) : (
104
+ <Header as="h4">
105
+ <Icon name="search" />
106
+ <Header.Content>
107
+ {formatMessage({ id: "grantApprovalRules.empty" })}
108
+ </Header.Content>
109
+ </Header>
110
+ )}
111
+ </Segment>
112
+ );
113
+ }
@@ -6,8 +6,8 @@ import { Link } from "react-router-dom";
6
6
  import { linkTo } from "@truedat/core/routes";
7
7
  import { useIntl } from "react-intl";
8
8
  import { Grid, Segment, List, Label } from "semantic-ui-react";
9
+ import GenericCrumbs from "@truedat/core/components/GenericCrumbs";
9
10
  import { getGrantRequestTemplate } from "../selectors";
10
- import GrantRequestCrumbs from "./GrantRequestCrumbs";
11
11
  import GrantRequestHeader from "./GrantRequestHeader";
12
12
  import GrantRequestSummary from "./GrantRequestSummary";
13
13
 
@@ -34,8 +34,9 @@ export const GrantRequest = ({ grantRequest, template }) => {
34
34
  grantRequest?._embedded?.group?._embedded?.created_by?.user_name;
35
35
  return (
36
36
  <>
37
- <GrantRequestCrumbs
38
- title={formatMessage({ id: "grantRequest.crumb.title" })}
37
+ <GenericCrumbs
38
+ header="grantRequests.header"
39
+ title="grantRequest.crumb.title"
39
40
  />
40
41
  <Grid columns="equal" style={{ marginTop: 0 }}>
41
42
  <Grid.Column width={12}>
@@ -4,14 +4,20 @@ import PropTypes from "prop-types";
4
4
  import { Route, Switch } from "react-router-dom";
5
5
  import { connect } from "react-redux";
6
6
  import {
7
+ GRANT_APPROVAL_RULE,
8
+ GRANT_APPROVAL_RULE_EDIT,
9
+ GRANT_APPROVAL_RULE_NEW,
7
10
  GRANT_REQUEST_APPROVALS,
8
11
  GRANT_REQUEST,
9
12
  GRANT_REQUESTS,
10
13
  GRANTS_REQUESTS_CHECKOUT,
11
14
  GRANTS,
15
+ GRANT_APPROVAL_RULES,
12
16
  MY_GRANT_REQUESTS,
13
17
  MY_GRANTS,
14
18
  } from "@truedat/core/routes";
19
+ import GrantApprovalRuleNew from "./GrantApprovalRuleNew";
20
+ import GrantApprovalRuleEdit from "./GrantApprovalRuleEdit";
15
21
  import GrantRequestApprovals from "./GrantRequestApprovals";
16
22
  import StructureGrantCartCheckout from "./StructureGrantCartCheckout";
17
23
  import GrantFiltersLoader from "./GrantFiltersLoader";
@@ -19,6 +25,8 @@ import GrantRequestsLoader from "./GrantRequestsLoader";
19
25
  import GrantRequestLoader from "./GrantRequestLoader";
20
26
  import GrantsLoader from "./GrantsLoader";
21
27
  import Grants from "./Grants";
28
+ import GrantApprovalRule from "./GrantApprovalRule";
29
+ import GrantApprovalRules from "./GrantApprovalRules";
22
30
  import MyGrantRequests from "./MyGrantRequests";
23
31
  import GrantRequest from "./GrantRequest";
24
32
 
@@ -80,6 +88,28 @@ export const GrantRoutes = ({ grantRequestLoaded }) => (
80
88
  </>
81
89
  )}
82
90
  />
91
+ <Switch>
92
+ <Route
93
+ exact
94
+ path={GRANT_APPROVAL_RULE_NEW}
95
+ render={() => <GrantApprovalRuleNew />}
96
+ />
97
+ <Route
98
+ exact
99
+ path={GRANT_APPROVAL_RULE_EDIT}
100
+ render={() => <GrantApprovalRuleEdit />}
101
+ />
102
+ <Route
103
+ exact
104
+ path={GRANT_APPROVAL_RULE}
105
+ render={() => <GrantApprovalRule />}
106
+ />
107
+ <Route
108
+ exact
109
+ path={GRANT_APPROVAL_RULES}
110
+ render={() => <GrantApprovalRules />}
111
+ />
112
+ </Switch>
83
113
  <Route
84
114
  path={GRANT_REQUESTS}
85
115
  render={() => (
@@ -0,0 +1,69 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { waitFor } from "@testing-library/react";
4
+ import { GrantApprovalRule } from "../GrantApprovalRule";
5
+ import { GRANT_APPROVAL_RULE_QUERY } from "../../api/queries";
6
+ import en from "../../messages/en";
7
+
8
+ jest.mock("react-router-dom", () => ({
9
+ ...jest.requireActual("react-router-dom"),
10
+ useParams: () => ({ id: 123 }),
11
+ }));
12
+
13
+ const grantApprovalRule = {
14
+ grantApprovalRule: {
15
+ __typename: "ApprovalRule",
16
+ id: "123",
17
+ name: "rule_name",
18
+ action: "approve",
19
+ role: "grants",
20
+ domainIds: ["1"],
21
+ userId: "1",
22
+ domains: [
23
+ {
24
+ __typename: "Domain",
25
+ name: "domain_test",
26
+ },
27
+ ],
28
+ conditions: [
29
+ {
30
+ __typename: "Condition",
31
+ field: "request.foo",
32
+ operator: "neq",
33
+ value: "bar",
34
+ },
35
+ {
36
+ __typename: "Condition",
37
+ field: "invalid_field",
38
+ operator: "eq",
39
+ value: "baz",
40
+ },
41
+ ],
42
+ comment: "comment invalid",
43
+ },
44
+ };
45
+
46
+ const approvalRuleMock = {
47
+ request: { query: GRANT_APPROVAL_RULE_QUERY, variables: { id: 123 } },
48
+ result: { data: grantApprovalRule },
49
+ };
50
+
51
+ const renderOpts = {
52
+ mocks: [approvalRuleMock],
53
+ messages: { en },
54
+ fallback: "lazy",
55
+ };
56
+
57
+ describe("<GrantApprovalRule />", () => {
58
+ it("matches the last snapshot", async () => {
59
+ const { container, queryByText } = render(
60
+ <GrantApprovalRule />,
61
+ renderOpts
62
+ );
63
+ await waitFor(() => expect(queryByText(/lazy/i)).not.toBeInTheDocument());
64
+ await waitFor(() =>
65
+ expect(queryByText(/loading/i)).not.toBeInTheDocument()
66
+ );
67
+ expect(container).toMatchSnapshot();
68
+ });
69
+ });
@@ -0,0 +1,120 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { waitFor } from "@testing-library/react";
4
+ import userEvent from "@testing-library/user-event";
5
+ import GrantApprovalRuleConditions from "../GrantApprovalRuleConditions";
6
+ import en from "../../messages/en";
7
+
8
+ const renderOpts = {
9
+ messages: { en },
10
+ };
11
+
12
+ describe("<GrantApprovalRuleConditions />", () => {
13
+ it("matches the last snapshot with values", () => {
14
+ const props = {
15
+ onChange: jest.fn(),
16
+ value: [
17
+ {
18
+ fieldPrefix: "request",
19
+ fieldName: "foo",
20
+ operator: "eq",
21
+ value: "bar",
22
+ },
23
+ {
24
+ fieldPrefix: null,
25
+ fieldname: "invalid_field",
26
+ operator: "eq",
27
+ value: "bar",
28
+ },
29
+ ],
30
+ };
31
+ const { container } = render(
32
+ <GrantApprovalRuleConditions {...props} />,
33
+ renderOpts
34
+ );
35
+ expect(container).toMatchSnapshot();
36
+ });
37
+
38
+ it("matches the last snapshot with empty values", () => {
39
+ const props = {
40
+ onChange: jest.fn(),
41
+ value: [{}],
42
+ };
43
+ const { container } = render(
44
+ <GrantApprovalRuleConditions {...props} />,
45
+ renderOpts
46
+ );
47
+ expect(container).toMatchSnapshot();
48
+ });
49
+
50
+ it("matches the last snapshot with no values", () => {
51
+ const props = {
52
+ onChange: jest.fn(),
53
+ };
54
+ const { container } = render(
55
+ <GrantApprovalRuleConditions {...props} />,
56
+ renderOpts
57
+ );
58
+ expect(container).toMatchSnapshot();
59
+ });
60
+
61
+ it("matches the last snapshot disable", () => {
62
+ const props = {
63
+ onChange: jest.fn(),
64
+ value: [{}],
65
+ disable: true,
66
+ };
67
+ const { container } = render(
68
+ <GrantApprovalRuleConditions {...props} />,
69
+ renderOpts
70
+ );
71
+ expect(container).toMatchSnapshot();
72
+ });
73
+
74
+ it("changing the form will trigger onChange function", async () => {
75
+ const onChange = jest.fn();
76
+ const props = {
77
+ onChange,
78
+ value: [{}],
79
+ };
80
+ const { findByText, getByRole } = render(
81
+ <GrantApprovalRuleConditions {...props} />,
82
+ renderOpts
83
+ );
84
+
85
+ userEvent.click(await findByText(/request metadata/i));
86
+ userEvent.type(getByRole("textbox", { name: /field/i }), "F");
87
+ userEvent.click(await findByText(/is equal/i));
88
+ userEvent.type(getByRole("textbox", { name: /value/i }), "V");
89
+
90
+ await waitFor(() => {
91
+ expect(onChange.mock.calls.length).toBe(4);
92
+ });
93
+ expect(onChange.mock.calls).toMatchObject([
94
+ [[{ fieldPrefix: "request" }]],
95
+ [[{ fieldName: "F" }]],
96
+ [[{ operator: "eq" }]],
97
+ [[{ value: "V" }]],
98
+ ]);
99
+ });
100
+
101
+ it("add and remove condition", async () => {
102
+ const onChange = jest.fn();
103
+ const props = {
104
+ onChange,
105
+ value: [{}, {}],
106
+ };
107
+ const { container, findByText } = render(
108
+ <GrantApprovalRuleConditions {...props} />,
109
+ renderOpts
110
+ );
111
+
112
+ userEvent.click(await findByText(/add condition/i));
113
+ userEvent.click(container.querySelector("i.trash"));
114
+
115
+ await waitFor(() => {
116
+ expect(onChange.mock.calls.length).toBe(2);
117
+ });
118
+ expect(onChange.mock.calls).toMatchObject([[[{}, {}, {}]], [[{}]]]);
119
+ });
120
+ });