@truedat/qx 8.10.2 → 8.10.4
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.
- package/package.json +3 -3
- package/src/components/QxRoutes.js +8 -7
- package/src/components/__tests__/QxRoutes.spec.js +2 -1
- package/src/components/__tests__/__fixtures__/helper.js +13 -0
- package/src/components/__tests__/__snapshots__/QxRoutes.spec.js.snap +1 -1
- package/src/components/common/ClauseViewer.js +52 -8
- package/src/components/common/DescriptionInput.js +9 -34
- package/src/components/common/TypeSelector.js +20 -10
- package/src/components/common/__tests__/ClauseViewer.spec.js +150 -0
- package/src/components/common/__tests__/DescriptionInput.spec.js +2 -5
- package/src/components/common/__tests__/__snapshots__/DescriptionInput.spec.js.snap +7 -10
- package/src/components/common/expressions/Clauses.js +22 -17
- package/src/components/common/expressions/Condition.js +201 -102
- package/src/components/common/expressions/Expression.js +17 -23
- package/src/components/common/expressions/FunctionSelector.js +3 -3
- package/src/components/common/expressions/ShapeSelector.js +1 -1
- package/src/components/common/expressions/__tests__/Clauses.spec.js +1 -1
- package/src/components/common/expressions/__tests__/Condition.spec.js +83 -26
- package/src/components/common/expressions/__tests__/ParamSelector.spec.js +1 -1
- package/src/components/common/expressions/__tests__/__snapshots__/Clauses.spec.js.snap +18 -22
- package/src/components/common/expressions/__tests__/__snapshots__/Condition.spec.js.snap +215 -216
- package/src/components/common/expressions/__tests__/__snapshots__/ConstantSelector.spec.js.snap +6 -10
- package/src/components/common/expressions/__tests__/__snapshots__/Expression.spec.js.snap +630 -654
- package/src/components/common/expressions/__tests__/__snapshots__/FunctionArgs.spec.js.snap +193 -201
- package/src/components/common/expressions/__tests__/__snapshots__/FunctionSelector.spec.js.snap +450 -466
- package/src/components/common/expressions/__tests__/__snapshots__/ShapeSelector.spec.js.snap +34 -34
- package/src/components/common/expressions/constantInputs/AnySelector.js +1 -0
- package/src/components/common/expressions/constantInputs/__tests__/__snapshots__/AnySelector.spec.js.snap +15 -15
- package/src/components/dataViews/__tests__/__snapshots__/AdvancedDataViewEditor.spec.js.snap +378 -406
- package/src/components/dataViews/__tests__/__snapshots__/DataViewSummary.spec.js.snap +6 -6
- package/src/components/dataViews/__tests__/__snapshots__/Queryable.spec.js.snap +489 -577
- package/src/components/dataViews/__tests__/__snapshots__/Queryables.spec.js.snap +400 -476
- package/src/components/dataViews/advancedForm/Queryable.js +56 -64
- package/src/components/dataViews/advancedForm/Queryables.js +11 -2
- package/src/components/dataViews/queryableProperties/Join.js +16 -6
- package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Join.spec.js.snap +0 -28
- package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Select.spec.js.snap +270 -282
- package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/SelectField.spec.js.snap +172 -180
- package/src/components/dataViews/simpleForm/DatasetForm.js +4 -0
- package/src/components/dataViews/simpleForm/FormQueryable.js +49 -53
- package/src/components/dataViews/summary/Where.js +3 -2
- package/src/components/functions/FunctionEdit.js +92 -0
- package/src/components/functions/FunctionEditor.js +32 -10
- package/src/components/functions/FunctionParams.js +54 -61
- package/src/components/functions/FunctionsList.js +213 -0
- package/src/components/functions/__tests__/FunctionEdit.spec.js +168 -0
- package/src/components/functions/__tests__/FunctionParams.spec.js +1 -8
- package/src/components/functions/__tests__/FunctionsList.spec.js +62 -0
- package/src/components/functions/__tests__/__snapshots__/FunctionEditor.spec.js.snap +553 -469
- package/src/components/functions/__tests__/__snapshots__/FunctionParams.spec.js.snap +139 -161
- package/src/components/functions/__tests__/__snapshots__/FunctionsList.spec.js.snap +588 -0
- package/src/components/qualityControls/__tests__/NewQualityControl.spec.js +4 -0
- package/src/components/qualityControls/__tests__/QualityControlEditor.spec.js +8 -1
- package/src/components/qualityControls/__tests__/__snapshots__/EditQualityControl.spec.js.snap +109 -118
- package/src/components/qualityControls/__tests__/__snapshots__/NewDraftQualityControl.spec.js.snap +109 -118
- package/src/components/qualityControls/__tests__/__snapshots__/QualityControlEditor.spec.js.snap +471 -468
- package/src/hooks/useFunctions.js +6 -0
- package/src/styles/Expression.less +39 -6
- package/src/components/functions/Functions.js +0 -143
- package/src/components/functions/__tests__/Functions.spec.js +0 -56
- package/src/components/functions/__tests__/__snapshots__/Functions.spec.js.snap +0 -85
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
2
|
import { useIntl } from "react-intl";
|
|
3
|
-
import { use } from "react";
|
|
3
|
+
import { use, useState } from "react";
|
|
4
4
|
import PropTypes from "prop-types";
|
|
5
|
-
import { Dropdown, Button, Form,
|
|
5
|
+
import { Dropdown, Button, Form, Input, Label } from "semantic-ui-react";
|
|
6
6
|
import { Controller, useFormContext } from "react-hook-form";
|
|
7
7
|
import QxContext from "@truedat/qx/components/QxContext";
|
|
8
8
|
import Expression from "./Expression";
|
|
@@ -10,11 +10,8 @@ import Expression from "./Expression";
|
|
|
10
10
|
const hasTypeKey = _.flow(_.keys, _.contains("type"));
|
|
11
11
|
export const isConditionFunction = _.conforms({
|
|
12
12
|
type: _.isEqual("boolean"),
|
|
13
|
-
params: _.conforms([
|
|
14
|
-
_.conforms({ name: _.isEqual("arg1"), type: _.isEqual("any") }),
|
|
15
|
-
_.conforms({ name: _.isEqual("arg2"), type: _.isEqual("any") }),
|
|
16
|
-
]),
|
|
17
13
|
});
|
|
14
|
+
export const argNames = (func) => _.map(_.prop("name"))(func?.params ?? []);
|
|
18
15
|
const isConditionExpression = (expression, functions) => {
|
|
19
16
|
const value = _.propOr({}, "value")(expression);
|
|
20
17
|
const func = _.find({ name: value?.name, type: value?.type })(functions);
|
|
@@ -23,29 +20,77 @@ const isConditionExpression = (expression, functions) => {
|
|
|
23
20
|
: isConditionFunction(func) || _.isNil(value) || !hasTypeKey(value);
|
|
24
21
|
};
|
|
25
22
|
|
|
23
|
+
const CLASS_ORDER = [
|
|
24
|
+
"comparison",
|
|
25
|
+
"length_comparison",
|
|
26
|
+
"eq_date",
|
|
27
|
+
"neq_date",
|
|
28
|
+
"gt_date",
|
|
29
|
+
"gte_date",
|
|
30
|
+
"lt_date",
|
|
31
|
+
"lte_date",
|
|
32
|
+
"string",
|
|
33
|
+
"format",
|
|
34
|
+
"logic",
|
|
35
|
+
"validation",
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const CLASS_ICONS = {
|
|
39
|
+
comparison: "balance scale",
|
|
40
|
+
length_comparison: "arrows alternate horizontal",
|
|
41
|
+
eq_date: "calendar alternate outline",
|
|
42
|
+
neq_date: "calendar alternate outline",
|
|
43
|
+
gt_date: "calendar alternate outline",
|
|
44
|
+
gte_date: "calendar alternate outline",
|
|
45
|
+
lt_date: "calendar alternate outline",
|
|
46
|
+
lte_date: "calendar alternate outline",
|
|
47
|
+
string: "font",
|
|
48
|
+
format: "magic",
|
|
49
|
+
logic: "code branch",
|
|
50
|
+
validation: "check circle outline",
|
|
51
|
+
};
|
|
52
|
+
|
|
26
53
|
export default function Condition({ onDelete }) {
|
|
27
54
|
const { formatMessage } = useIntl();
|
|
55
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
28
56
|
const context = use(QxContext);
|
|
29
57
|
const { control, watch, setValue } = useFormContext();
|
|
30
|
-
const { field, functions } = context;
|
|
58
|
+
const { field, functions, conditionClasses } = context;
|
|
31
59
|
|
|
32
60
|
const expression = watch(field);
|
|
33
61
|
const isCondition = isConditionExpression(expression, functions);
|
|
34
62
|
|
|
35
|
-
const conditionFunctions = _.filter(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
63
|
+
const conditionFunctions = _.filter(
|
|
64
|
+
(fn) =>
|
|
65
|
+
isConditionFunction(fn) && fn.params?.length > 0 && fn.class !== "logic",
|
|
66
|
+
)(functions);
|
|
67
|
+
|
|
68
|
+
const grouped = _.groupBy((fn) => fn.class)(conditionFunctions);
|
|
69
|
+
const allowedClasses = conditionClasses ?? CLASS_ORDER;
|
|
70
|
+
const orderedGroups = allowedClasses.filter(
|
|
71
|
+
(group) => grouped[group]?.length,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const selectedFunc = _.find({ name: expression?.value?.name })(
|
|
75
|
+
conditionFunctions,
|
|
76
|
+
);
|
|
77
|
+
const params = selectedFunc?.params ?? [];
|
|
78
|
+
const arity = params.length || 2;
|
|
79
|
+
const argName = (i) => params[i]?.name ?? `arg${i + 1}`;
|
|
80
|
+
|
|
81
|
+
const argContext = (argField) => ({
|
|
82
|
+
...context,
|
|
83
|
+
type: "any",
|
|
84
|
+
field: `${field}.value.args.${argField}`,
|
|
85
|
+
omitLogicOperators: true,
|
|
86
|
+
defaultShape: "field",
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const matchesSearch = ({ name, label, operator }) => {
|
|
90
|
+
if (!searchQuery) return true;
|
|
91
|
+
const q = searchQuery.toLowerCase();
|
|
92
|
+
return (operator || label || name).toLowerCase().includes(q);
|
|
93
|
+
};
|
|
49
94
|
|
|
50
95
|
const operatorTrigger = (name) => {
|
|
51
96
|
const currentCondFunction = _.find({ name })(conditionFunctions);
|
|
@@ -54,7 +99,9 @@ export default function Condition({ onDelete }) {
|
|
|
54
99
|
{currentCondFunction ? (
|
|
55
100
|
<b>
|
|
56
101
|
<code>
|
|
57
|
-
{currentCondFunction.operator ||
|
|
102
|
+
{currentCondFunction.operator ||
|
|
103
|
+
currentCondFunction.label ||
|
|
104
|
+
currentCondFunction.name}
|
|
58
105
|
</code>
|
|
59
106
|
</b>
|
|
60
107
|
) : (
|
|
@@ -68,7 +115,7 @@ export default function Condition({ onDelete }) {
|
|
|
68
115
|
);
|
|
69
116
|
};
|
|
70
117
|
return (
|
|
71
|
-
|
|
118
|
+
<div>
|
|
72
119
|
{!isCondition ? (
|
|
73
120
|
<QxContext
|
|
74
121
|
value={{
|
|
@@ -80,22 +127,15 @@ export default function Condition({ onDelete }) {
|
|
|
80
127
|
<Expression onDelete={onDelete} deletable />
|
|
81
128
|
</QxContext>
|
|
82
129
|
) : (
|
|
83
|
-
<
|
|
84
|
-
<
|
|
85
|
-
<
|
|
86
|
-
value={
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}}
|
|
93
|
-
>
|
|
94
|
-
<Expression />
|
|
95
|
-
</QxContext>
|
|
96
|
-
</Grid.Column>
|
|
97
|
-
<Grid.Column width={3}>
|
|
98
|
-
<div className="flex-justify-center">
|
|
130
|
+
<div className="condition-row">
|
|
131
|
+
<div className="condition-fields">
|
|
132
|
+
<div className="condition-arg-field">
|
|
133
|
+
<QxContext value={argContext(argName(0))}>
|
|
134
|
+
<Expression />
|
|
135
|
+
</QxContext>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div className="condition-operator-field">
|
|
99
139
|
<Controller
|
|
100
140
|
control={control}
|
|
101
141
|
name={`${field}.value.name`}
|
|
@@ -111,73 +151,132 @@ export default function Condition({ onDelete }) {
|
|
|
111
151
|
},
|
|
112
152
|
}}
|
|
113
153
|
render={({
|
|
114
|
-
field: { onBlur, onChange },
|
|
154
|
+
field: { onBlur, onChange, value },
|
|
115
155
|
fieldState: { error },
|
|
116
|
-
}) =>
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
156
|
+
}) => (
|
|
157
|
+
<Form.Field error={!!error?.message}>
|
|
158
|
+
<Dropdown
|
|
159
|
+
fluid
|
|
160
|
+
onClose={() => setSearchQuery("")}
|
|
161
|
+
onBlur={onBlur}
|
|
162
|
+
trigger={operatorTrigger(value)}
|
|
163
|
+
pointing="top left"
|
|
164
|
+
icon={null}
|
|
165
|
+
scrolling
|
|
166
|
+
>
|
|
167
|
+
<Dropdown.Menu>
|
|
168
|
+
<Input
|
|
169
|
+
icon="search"
|
|
170
|
+
iconPosition="left"
|
|
171
|
+
className="search"
|
|
172
|
+
value={searchQuery}
|
|
173
|
+
onChange={(_, { value: q }) => setSearchQuery(q)}
|
|
174
|
+
onClick={(e) => {
|
|
175
|
+
e.preventDefault();
|
|
176
|
+
e.stopPropagation();
|
|
177
|
+
}}
|
|
178
|
+
onKeyDown={(e) => {
|
|
179
|
+
if (e.key === " ") e.stopPropagation();
|
|
180
|
+
}}
|
|
181
|
+
fluid
|
|
182
|
+
/>
|
|
183
|
+
<Dropdown.Divider />
|
|
184
|
+
{orderedGroups
|
|
185
|
+
.map((group) => ({
|
|
186
|
+
group,
|
|
187
|
+
fns: grouped[group].filter(matchesSearch),
|
|
188
|
+
}))
|
|
189
|
+
.filter(({ fns }) => fns.length > 0)
|
|
190
|
+
.flatMap(({ group, fns }, renderedIndex) => [
|
|
191
|
+
...(renderedIndex > 0
|
|
192
|
+
? [<Dropdown.Divider key={`divider-${group}`} />]
|
|
193
|
+
: []),
|
|
194
|
+
<Dropdown.Header
|
|
195
|
+
key={`header-${group}`}
|
|
196
|
+
icon={CLASS_ICONS[group]}
|
|
197
|
+
content={formatMessage({
|
|
198
|
+
id: `expression.condition.class.${group}`,
|
|
199
|
+
})}
|
|
200
|
+
/>,
|
|
201
|
+
...fns.map(
|
|
202
|
+
({ name, label, operator, description }) => (
|
|
203
|
+
<Dropdown.Item
|
|
204
|
+
key={name}
|
|
205
|
+
text={operator || label || name}
|
|
206
|
+
value={name}
|
|
207
|
+
active={value === name}
|
|
208
|
+
selected={value === name}
|
|
209
|
+
title={description}
|
|
210
|
+
onClick={() => {
|
|
211
|
+
const fn = _.find({ name })(
|
|
212
|
+
conditionFunctions,
|
|
213
|
+
);
|
|
214
|
+
const newParams = fn?.params ?? [];
|
|
215
|
+
const oldArgs =
|
|
216
|
+
expression?.value?.args ?? {};
|
|
217
|
+
const nextArgs = newParams.reduce(
|
|
218
|
+
(acc, param, i) => ({
|
|
219
|
+
...acc,
|
|
220
|
+
[param.name]: oldArgs[argName(i)],
|
|
221
|
+
}),
|
|
222
|
+
{},
|
|
223
|
+
);
|
|
224
|
+
onChange(name);
|
|
225
|
+
setValue(`${field}.value.type`, "boolean");
|
|
226
|
+
setValue(
|
|
227
|
+
`${field}.value.isCondition`,
|
|
228
|
+
true,
|
|
229
|
+
);
|
|
230
|
+
setValue(`${field}.value.args`, nextArgs);
|
|
231
|
+
}}
|
|
232
|
+
/>
|
|
233
|
+
),
|
|
234
|
+
),
|
|
235
|
+
])}
|
|
236
|
+
</Dropdown.Menu>
|
|
237
|
+
</Dropdown>
|
|
238
|
+
{error?.message && (
|
|
239
|
+
<Label prompt pointing>
|
|
240
|
+
{error?.message}
|
|
241
|
+
</Label>
|
|
242
|
+
)}
|
|
243
|
+
</Form.Field>
|
|
244
|
+
)}
|
|
146
245
|
/>
|
|
147
246
|
</div>
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
)
|
|
177
|
-
</
|
|
178
|
-
</
|
|
247
|
+
|
|
248
|
+
<div className="condition-arg-field">
|
|
249
|
+
{arity >= 2 && (
|
|
250
|
+
<QxContext value={argContext(argName(1))}>
|
|
251
|
+
<Expression />
|
|
252
|
+
</QxContext>
|
|
253
|
+
)}
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<div className="condition-arg-field">
|
|
257
|
+
{arity >= 3 && (
|
|
258
|
+
<QxContext value={argContext(argName(2))}>
|
|
259
|
+
<Expression />
|
|
260
|
+
</QxContext>
|
|
261
|
+
)}
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
<div className="delete-expression-column">
|
|
266
|
+
{onDelete && (
|
|
267
|
+
<Button
|
|
268
|
+
size="mini"
|
|
269
|
+
onClick={onDelete}
|
|
270
|
+
icon="trash alternate outline"
|
|
271
|
+
basic
|
|
272
|
+
color="red"
|
|
273
|
+
aria-label="delete"
|
|
274
|
+
/>
|
|
275
|
+
)}
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
179
278
|
)}
|
|
180
|
-
|
|
279
|
+
</div>
|
|
181
280
|
);
|
|
182
281
|
}
|
|
183
282
|
|
|
@@ -2,7 +2,7 @@ import _ from "lodash/fp";
|
|
|
2
2
|
import { use } from "react";
|
|
3
3
|
import PropTypes from "prop-types";
|
|
4
4
|
import { useFormContext } from "react-hook-form";
|
|
5
|
-
import { Button
|
|
5
|
+
import { Button } from "semantic-ui-react";
|
|
6
6
|
import QxContext from "@truedat/qx/components/QxContext";
|
|
7
7
|
import ShapeSelector from "./ShapeSelector";
|
|
8
8
|
import FunctionSelector from "./FunctionSelector";
|
|
@@ -23,28 +23,22 @@ export default function Expression({ onDelete, deletable }) {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
return (
|
|
26
|
-
<
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
aria-label="delete"
|
|
43
|
-
/>
|
|
44
|
-
) : null}
|
|
45
|
-
</Grid.Column>
|
|
46
|
-
) : null}
|
|
47
|
-
</Grid>
|
|
26
|
+
<div className="expression-container">
|
|
27
|
+
<div className="expression-content">
|
|
28
|
+
{!aggregate ? <ShapeSelector /> : null}
|
|
29
|
+
{shape ? valueComponentForShape[shape] : null}
|
|
30
|
+
</div>
|
|
31
|
+
{deletable && onDelete && (
|
|
32
|
+
<Button
|
|
33
|
+
size="mini"
|
|
34
|
+
onClick={onDelete}
|
|
35
|
+
icon="trash alternate outline"
|
|
36
|
+
basic
|
|
37
|
+
color="red"
|
|
38
|
+
aria-label="delete"
|
|
39
|
+
/>
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
48
42
|
);
|
|
49
43
|
}
|
|
50
44
|
|
|
@@ -46,15 +46,15 @@ export default function FunctionSelector() {
|
|
|
46
46
|
|
|
47
47
|
const funcOpts = _.flow(
|
|
48
48
|
_.reject({ name: functionName, type: functionType }),
|
|
49
|
-
_.map(({ name, type, description, params }) => ({
|
|
49
|
+
_.map(({ name, type, label, operator, description, params }) => ({
|
|
50
50
|
key: fnToKey({ name, type }),
|
|
51
51
|
value: fnToKey({ name, type }),
|
|
52
|
-
text:
|
|
52
|
+
text: operator || label || name,
|
|
53
53
|
content: (
|
|
54
54
|
<>
|
|
55
55
|
<Header>
|
|
56
56
|
<div className="flex-center">
|
|
57
|
-
<div>{name}</div>
|
|
57
|
+
<div>{operator || label || name}</div>
|
|
58
58
|
<div className="function-params-label">
|
|
59
59
|
{params?.map(({ type: paramType, name: paramName }, index) => (
|
|
60
60
|
<Label key={index} color={typeToColor(paramType)}>
|
|
@@ -38,7 +38,7 @@ export default function ShapeSelector() {
|
|
|
38
38
|
(shape) =>
|
|
39
39
|
(shape === "param" && emptyTypeParams) ||
|
|
40
40
|
(shape === "field" && emptyTypeFields),
|
|
41
|
-
)(["
|
|
41
|
+
)(["field", "function", "constant", "param"]);
|
|
42
42
|
|
|
43
43
|
const shape = watch(`${field}.shape`);
|
|
44
44
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
1
2
|
import userEvent from "@testing-library/user-event";
|
|
2
3
|
import { waitFor } from "@testing-library/react";
|
|
3
4
|
import PropTypes from "prop-types";
|
|
@@ -6,6 +7,27 @@ import { useFormContext } from "react-hook-form";
|
|
|
6
7
|
import TestFormWrapper from "@truedat/qx/components/common/TestFormWrapper";
|
|
7
8
|
import Condition, { isConditionFunction } from "../Condition";
|
|
8
9
|
|
|
10
|
+
const eq = {
|
|
11
|
+
name: "eq",
|
|
12
|
+
type: "boolean",
|
|
13
|
+
class: "comparison",
|
|
14
|
+
params: [
|
|
15
|
+
{ name: "arg1", type: "any" },
|
|
16
|
+
{ name: "arg2", type: "any" },
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const lengthGt = {
|
|
21
|
+
name: "length_gt",
|
|
22
|
+
type: "boolean",
|
|
23
|
+
class: "length_comparison",
|
|
24
|
+
label: "LengthGreater",
|
|
25
|
+
params: [
|
|
26
|
+
{ name: "value", type: "string" },
|
|
27
|
+
{ name: "length", type: "number" },
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
|
|
9
31
|
function ValidationTrigger({ name }) {
|
|
10
32
|
const { trigger } = useFormContext();
|
|
11
33
|
return (
|
|
@@ -63,6 +85,7 @@ describe("<Condition />", () => {
|
|
|
63
85
|
{
|
|
64
86
|
name: "eq",
|
|
65
87
|
type: "boolean",
|
|
88
|
+
class: "comparison",
|
|
66
89
|
params: [
|
|
67
90
|
{ name: "arg1", type: "any" },
|
|
68
91
|
{ name: "arg2", type: "any" },
|
|
@@ -100,6 +123,7 @@ describe("<Condition />", () => {
|
|
|
100
123
|
{
|
|
101
124
|
name: "eq",
|
|
102
125
|
type: "boolean",
|
|
126
|
+
class: "comparison",
|
|
103
127
|
params: [
|
|
104
128
|
{ name: "arg1", type: "any" },
|
|
105
129
|
{ name: "arg2", type: "any" },
|
|
@@ -133,7 +157,7 @@ describe("<Condition />", () => {
|
|
|
133
157
|
);
|
|
134
158
|
});
|
|
135
159
|
|
|
136
|
-
it("
|
|
160
|
+
it("marks selected operator as condition function", async () => {
|
|
137
161
|
const user = userEvent.setup({ delay: null });
|
|
138
162
|
const formValuesRef = { current: {} };
|
|
139
163
|
const rendered = render(
|
|
@@ -144,6 +168,7 @@ describe("<Condition />", () => {
|
|
|
144
168
|
{
|
|
145
169
|
name: "eq",
|
|
146
170
|
type: "boolean",
|
|
171
|
+
class: "comparison",
|
|
147
172
|
params: [
|
|
148
173
|
{ name: "arg1", type: "any" },
|
|
149
174
|
{ name: "arg2", type: "any" },
|
|
@@ -155,7 +180,7 @@ describe("<Condition />", () => {
|
|
|
155
180
|
test: {
|
|
156
181
|
shape: "function",
|
|
157
182
|
value: {
|
|
158
|
-
name: "
|
|
183
|
+
name: "",
|
|
159
184
|
type: "boolean",
|
|
160
185
|
isCondition: true,
|
|
161
186
|
args: { arg1: { shape: "field" }, arg2: { shape: "field" } },
|
|
@@ -171,42 +196,77 @@ describe("<Condition />", () => {
|
|
|
171
196
|
);
|
|
172
197
|
await waitForLoad(rendered);
|
|
173
198
|
|
|
174
|
-
await user.click(rendered.getAllByRole("listbox")[1]);
|
|
175
199
|
await user.click(
|
|
176
|
-
rendered.getByText(/expression.condition.
|
|
200
|
+
rendered.getByText(/expression.condition.selectfunction/i),
|
|
177
201
|
);
|
|
202
|
+
await user.click(rendered.getByText(/eq/i));
|
|
178
203
|
|
|
179
204
|
await waitFor(() =>
|
|
180
|
-
expect(formValuesRef.current.test.value).toEqual(
|
|
205
|
+
expect(formValuesRef.current.test.value).toEqual(
|
|
206
|
+
expect.objectContaining({
|
|
207
|
+
name: "eq",
|
|
208
|
+
type: "boolean",
|
|
209
|
+
isCondition: true,
|
|
210
|
+
}),
|
|
211
|
+
),
|
|
181
212
|
);
|
|
182
213
|
});
|
|
183
214
|
|
|
184
|
-
it("
|
|
185
|
-
const user = userEvent.setup({ delay: null });
|
|
215
|
+
it("names args after the function params, not their position", async () => {
|
|
186
216
|
const formValuesRef = { current: {} };
|
|
187
217
|
const rendered = render(
|
|
188
218
|
<TestFormWrapper
|
|
189
219
|
context={{
|
|
190
220
|
field: "test",
|
|
191
|
-
functions: [
|
|
192
|
-
|
|
193
|
-
|
|
221
|
+
functions: [lengthGt],
|
|
222
|
+
}}
|
|
223
|
+
defaultValues={{
|
|
224
|
+
test: {
|
|
225
|
+
shape: "function",
|
|
226
|
+
value: {
|
|
227
|
+
name: "length_gt",
|
|
194
228
|
type: "boolean",
|
|
195
|
-
|
|
196
|
-
{ name: "arg1", type: "any" },
|
|
197
|
-
{ name: "arg2", type: "any" },
|
|
198
|
-
],
|
|
229
|
+
isCondition: true,
|
|
199
230
|
},
|
|
200
|
-
|
|
231
|
+
},
|
|
232
|
+
}}
|
|
233
|
+
watcher={(values) => {
|
|
234
|
+
formValuesRef.current = values;
|
|
235
|
+
}}
|
|
236
|
+
>
|
|
237
|
+
<Condition />
|
|
238
|
+
</TestFormWrapper>,
|
|
239
|
+
);
|
|
240
|
+
await waitForLoad(rendered);
|
|
241
|
+
|
|
242
|
+
await waitFor(() =>
|
|
243
|
+
expect(_.keys(formValuesRef.current.test.value.args)).toEqual([
|
|
244
|
+
"value",
|
|
245
|
+
"length",
|
|
246
|
+
]),
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("remaps operands onto the new param names when the operator changes", async () => {
|
|
251
|
+
const user = userEvent.setup({ delay: null });
|
|
252
|
+
const formValuesRef = { current: {} };
|
|
253
|
+
const rendered = render(
|
|
254
|
+
<TestFormWrapper
|
|
255
|
+
context={{
|
|
256
|
+
field: "test",
|
|
257
|
+
functions: [eq, lengthGt],
|
|
201
258
|
}}
|
|
202
259
|
defaultValues={{
|
|
203
260
|
test: {
|
|
204
261
|
shape: "function",
|
|
205
262
|
value: {
|
|
206
|
-
name: "",
|
|
263
|
+
name: "eq",
|
|
207
264
|
type: "boolean",
|
|
208
265
|
isCondition: true,
|
|
209
|
-
args: {
|
|
266
|
+
args: {
|
|
267
|
+
arg1: { shape: "constant", value: { value: "left" } },
|
|
268
|
+
arg2: { shape: "constant", value: { value: "right" } },
|
|
269
|
+
},
|
|
210
270
|
},
|
|
211
271
|
},
|
|
212
272
|
}}
|
|
@@ -220,18 +280,15 @@ describe("<Condition />", () => {
|
|
|
220
280
|
await waitForLoad(rendered);
|
|
221
281
|
|
|
222
282
|
await user.click(
|
|
223
|
-
rendered.
|
|
283
|
+
rendered.container.querySelector(".condition-operator-trigger"),
|
|
224
284
|
);
|
|
225
|
-
await user.click(rendered.getByText(
|
|
285
|
+
await user.click(rendered.getByText("LengthGreater"));
|
|
226
286
|
|
|
227
287
|
await waitFor(() =>
|
|
228
|
-
expect(formValuesRef.current.test.value).toEqual(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
isCondition: true,
|
|
233
|
-
}),
|
|
234
|
-
),
|
|
288
|
+
expect(formValuesRef.current.test.value.args).toEqual({
|
|
289
|
+
value: { shape: "constant", value: { value: "left" } },
|
|
290
|
+
length: { shape: "constant", value: { value: "right" } },
|
|
291
|
+
}),
|
|
235
292
|
);
|
|
236
293
|
});
|
|
237
294
|
});
|
|
@@ -84,7 +84,7 @@ describe("<ParamSelector />", () => {
|
|
|
84
84
|
test: { value: { id: 1 } },
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
-
await user.hover(rendered.
|
|
87
|
+
await user.hover(rendered.getByPlaceholderText("functions.form.name"));
|
|
88
88
|
await user.click(rendered.getAllByRole("button")[0]);
|
|
89
89
|
|
|
90
90
|
expect(watcher).toHaveBeenLastCalledWith({
|