@evoke-platform/ui-components 1.13.0-dev.4 → 1.13.0-dev.6
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/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.d.ts +4 -4
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +145 -72
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +189 -67
- package/dist/published/components/custom/CriteriaBuilder/PropertyTree.d.ts +6 -6
- package/dist/published/components/custom/CriteriaBuilder/PropertyTree.js +12 -25
- package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.d.ts +4 -5
- package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.js +34 -22
- package/dist/published/components/custom/CriteriaBuilder/types.d.ts +2 -11
- package/dist/published/components/custom/CriteriaBuilder/utils.d.ts +6 -34
- package/dist/published/components/custom/CriteriaBuilder/utils.js +18 -89
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +1 -1
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.js +6 -3
- package/dist/published/components/custom/Form/utils.d.ts +1 -0
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +1 -0
- package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +1 -0
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +25 -22
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.d.ts +3 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +46 -10
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +4 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +29 -23
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +4 -2
- package/dist/published/components/custom/FormV2/components/types.d.ts +6 -1
- package/dist/published/components/custom/FormV2/components/utils.d.ts +5 -7
- package/dist/published/components/custom/FormV2/components/utils.js +146 -69
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +20 -8
- package/dist/published/stories/CriteriaBuilder.stories.js +70 -22
- package/dist/published/stories/FormRenderer.stories.d.ts +3 -0
- package/dist/published/stories/FormRendererContainer.stories.d.ts +5 -0
- package/dist/published/theme/hooks.d.ts +2 -1
- package/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { Property } from '@evoke-platform/context';
|
|
1
2
|
import React from 'react';
|
|
2
3
|
import 'react-querybuilder/dist/query-builder.css';
|
|
3
|
-
import {
|
|
4
|
-
import { ObjectProperty, Operator, PresetValue, TreeViewObject } from './types';
|
|
4
|
+
import { Operator, PresetValue, TreeViewObject } from './types';
|
|
5
5
|
import { ValueEditorProps } from './ValueEditor';
|
|
6
6
|
export type CriteriaInputProps = {
|
|
7
|
-
properties:
|
|
7
|
+
properties: Property[];
|
|
8
8
|
setCriteria: (criteria?: Record<string, unknown> | undefined) => void;
|
|
9
9
|
criteria?: Record<string, unknown>;
|
|
10
10
|
originalCriteria?: Record<string, unknown>;
|
|
@@ -23,7 +23,7 @@ export type CriteriaInputProps = {
|
|
|
23
23
|
hideBorder?: boolean;
|
|
24
24
|
presetGroupLabel?: string;
|
|
25
25
|
treeViewOpts?: {
|
|
26
|
-
fetchObject?: (objectId: string) => Promise<
|
|
26
|
+
fetchObject?: (objectId: string) => Promise<TreeViewObject | undefined>;
|
|
27
27
|
object: TreeViewObject;
|
|
28
28
|
};
|
|
29
29
|
/**
|
|
@@ -11,7 +11,7 @@ import { Box } from '../../layout';
|
|
|
11
11
|
import { OverflowTextField } from '../OverflowTextField';
|
|
12
12
|
import { difference } from '../util';
|
|
13
13
|
import PropertyTree from './PropertyTree';
|
|
14
|
-
import { ALL_OPERATORS, parseMongoDB
|
|
14
|
+
import { ALL_OPERATORS, parseMongoDB } from './utils';
|
|
15
15
|
import ValueEditor from './ValueEditor';
|
|
16
16
|
const styles = {
|
|
17
17
|
buttons: {
|
|
@@ -103,6 +103,7 @@ const customSelector = (props) => {
|
|
|
103
103
|
const isTreeViewEnabled = context.treeViewOpts && title === 'Fields';
|
|
104
104
|
const fetchObject = context.treeViewOpts?.fetchObject;
|
|
105
105
|
const object = context.treeViewOpts?.object;
|
|
106
|
+
const setObject = context.treeViewOpts?.setObject;
|
|
106
107
|
let readOnly = context.disabled;
|
|
107
108
|
if (!readOnly && context.disabledCriteria) {
|
|
108
109
|
readOnly =
|
|
@@ -173,14 +174,7 @@ const customSelector = (props) => {
|
|
|
173
174
|
val = options.find((option) => option.name === val)?.name;
|
|
174
175
|
break;
|
|
175
176
|
}
|
|
176
|
-
|
|
177
|
-
const propertyInfo = await traversePropertyPath(propertyId, object, fetchObject);
|
|
178
|
-
context.setPropertyTreeMap((prev) => {
|
|
179
|
-
return { ...prev, [propertyId]: propertyInfo };
|
|
180
|
-
});
|
|
181
|
-
handleOnChange(propertyId);
|
|
182
|
-
};
|
|
183
|
-
return (React.createElement(React.Fragment, null, isTreeViewEnabled ? (React.createElement(PropertyTree, { value: val ?? value, rootObject: object, fetchObject: fetchObject, propertyTreeMap: context.propertyTreeMap ?? {}, handleTreePropertySelect: handleTreePropertySelect })) : (React.createElement(Autocomplete, { options: opts, value: val ?? null, getOptionLabel: (option) => {
|
|
177
|
+
return (React.createElement(React.Fragment, null, isTreeViewEnabled ? (React.createElement(PropertyTree, { value: val ?? value, rootObject: object, setRootObject: setObject, fetchObject: fetchObject, propertyTreeMap: context.propertyTreeMap ?? {}, handleTreePropertySelect: handleOnChange })) : (React.createElement(Autocomplete, { options: opts, value: val ?? null, getOptionLabel: (option) => {
|
|
184
178
|
if (typeof option === 'string') {
|
|
185
179
|
return opts.find((o) => option === o.name)?.label || option;
|
|
186
180
|
}
|
|
@@ -275,77 +269,159 @@ const getAllRuleIds = (rules) => {
|
|
|
275
269
|
});
|
|
276
270
|
return ids;
|
|
277
271
|
};
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
let adjustedValue = rule.value;
|
|
292
|
-
if ((rule.operator === 'null' || rule.operator === 'notNull') && rule.value) {
|
|
293
|
-
adjustedValue = null;
|
|
294
|
-
}
|
|
295
|
-
return {
|
|
296
|
-
...rule,
|
|
297
|
-
operator: propertyType === 'array' && rule.operator === '=' ? 'in' : rule.operator,
|
|
298
|
-
value: adjustedValue,
|
|
299
|
-
};
|
|
272
|
+
const processRules = (rules, properties) => {
|
|
273
|
+
return rules.map((rule) => {
|
|
274
|
+
if ('rules' in rule) {
|
|
275
|
+
return {
|
|
276
|
+
...rule,
|
|
277
|
+
rules: processRules(rule.rules, properties),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
const propertyType = properties.find((property) => property.id === rule.field)?.type;
|
|
282
|
+
let adjustedValue = rule.value;
|
|
283
|
+
if ((rule.operator === 'null' || rule.operator === 'notNull') && rule.value) {
|
|
284
|
+
adjustedValue = null;
|
|
300
285
|
}
|
|
301
|
-
|
|
286
|
+
return {
|
|
287
|
+
...rule,
|
|
288
|
+
operator: propertyType === 'array' && rule.operator === '=' ? 'in' : rule.operator,
|
|
289
|
+
value: adjustedValue,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
const insertChangesOnly = (prev, newObject) => {
|
|
295
|
+
const mergedProperties = newObject.properties.map((newProp) => {
|
|
296
|
+
const prevProp = prev.properties.find((prop) => prop.id === newProp.id);
|
|
297
|
+
if (prevProp) {
|
|
298
|
+
return {
|
|
299
|
+
...newProp,
|
|
300
|
+
children: newProp.children &&
|
|
301
|
+
newProp.children[0].type === 'loading' &&
|
|
302
|
+
prevProp.children
|
|
303
|
+
? prevProp.children
|
|
304
|
+
: newProp.children && prevProp.children
|
|
305
|
+
? insertChangesOnly({ id: prevProp.id, name: prevProp.name, properties: prevProp.children }, { id: newProp.id, name: newProp.name, properties: newProp.children }).properties
|
|
306
|
+
: newProp.children,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
return newProp;
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
return {
|
|
314
|
+
...newObject,
|
|
315
|
+
properties: mergedProperties,
|
|
302
316
|
};
|
|
317
|
+
};
|
|
318
|
+
const CriteriaBuilder = (props) => {
|
|
319
|
+
const { properties, criteria, setCriteria, originalCriteria, enablePresetValues, presetValues, operators, disabled, disabledCriteria, hideBorder, presetGroupLabel, customValueEditor, treeViewOpts, disableRegexEscapeChars, } = props;
|
|
320
|
+
const [propertyTreeMap, setPropertyTreeMap] = useState({});
|
|
321
|
+
const [treeViewObject, setTreeViewObject] = useState();
|
|
303
322
|
useEffect(() => {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
323
|
+
let obj;
|
|
324
|
+
if (treeViewOpts?.object) {
|
|
325
|
+
obj = {
|
|
326
|
+
...treeViewOpts.object,
|
|
327
|
+
properties: treeViewOpts.object.properties
|
|
328
|
+
.filter((prop) => prop.type !== 'collection')
|
|
329
|
+
.map((prop) => ({
|
|
330
|
+
...prop,
|
|
331
|
+
children: prop.children
|
|
332
|
+
?.filter((child) => child.type !== 'collection')
|
|
333
|
+
.map((child) => ({
|
|
334
|
+
...child,
|
|
335
|
+
id: `${prop.id}.${child.id}`,
|
|
336
|
+
})),
|
|
337
|
+
})),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
if ((criteria || originalCriteria) && obj && treeViewOpts?.fetchObject) {
|
|
341
|
+
const updateTreeViewObject = async (criteria, treeViewObject, fetchObject) => {
|
|
342
|
+
const newQuery = parseMongoDB(criteria);
|
|
313
343
|
const ids = getAllRuleIds(newQuery.rules);
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
if (
|
|
318
|
-
|
|
319
|
-
.
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
344
|
+
const newTreeViewObject = { ...treeViewObject };
|
|
345
|
+
const traversePath = async (path, properties, fetchObject) => {
|
|
346
|
+
const prop = properties?.find((prop) => path.startsWith(`${prop.id}.`) || path === prop.id);
|
|
347
|
+
if (prop?.type === 'object' && prop.objectId && prop.children) {
|
|
348
|
+
if (prop.children.length === 1 &&
|
|
349
|
+
prop.children[0]?.type === 'loading') {
|
|
350
|
+
try {
|
|
351
|
+
const fetchedObject = await fetchObject(prop.objectId);
|
|
352
|
+
prop.children = fetchedObject?.properties
|
|
353
|
+
?.filter((item) => item.type !== 'collection')
|
|
354
|
+
.map((item) => {
|
|
355
|
+
return {
|
|
356
|
+
...item,
|
|
357
|
+
id: `${prop.id}.${item.id}`,
|
|
358
|
+
children: item.children?.map((child) => ({
|
|
359
|
+
...child,
|
|
360
|
+
id: `${prop.id}.${item.id}.${child.id}`,
|
|
361
|
+
})),
|
|
362
|
+
};
|
|
363
|
+
});
|
|
364
|
+
fetchedObject && (await traversePath(path, fetchedObject.properties, fetchObject));
|
|
324
365
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
366
|
+
catch (error) {
|
|
367
|
+
prop.children = [
|
|
368
|
+
{
|
|
369
|
+
id: `${prop.id}-failed`,
|
|
370
|
+
name: 'Loading Failed',
|
|
371
|
+
type: 'loadingFailed',
|
|
372
|
+
},
|
|
373
|
+
];
|
|
374
|
+
console.error('Error fetching object for criteria builder:', error);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
await traversePath(path, prop.children, fetchObject);
|
|
379
|
+
}
|
|
331
380
|
}
|
|
381
|
+
};
|
|
382
|
+
for (const id of ids) {
|
|
383
|
+
await traversePath(id, newTreeViewObject.properties, fetchObject);
|
|
332
384
|
}
|
|
333
|
-
|
|
334
|
-
setPropertyTreeMap((prevPropertyTreeMap) => ({
|
|
335
|
-
...prevPropertyTreeMap,
|
|
336
|
-
...newPropertyTreeMap,
|
|
337
|
-
}));
|
|
385
|
+
setTreeViewObject((prevTreeViewObject) => prevTreeViewObject ? insertChangesOnly(prevTreeViewObject, newTreeViewObject) : newTreeViewObject);
|
|
338
386
|
};
|
|
339
|
-
|
|
387
|
+
updateTreeViewObject(criteria || originalCriteria || {}, obj, treeViewOpts.fetchObject);
|
|
388
|
+
}
|
|
389
|
+
}, [treeViewOpts, criteria, originalCriteria]);
|
|
390
|
+
useEffect(() => {
|
|
391
|
+
if (!treeViewObject)
|
|
392
|
+
return;
|
|
393
|
+
const getNamePath = (path, object) => {
|
|
394
|
+
const helper = (path, object) => {
|
|
395
|
+
let namePath = '';
|
|
396
|
+
const prop = (object.properties ?? []).find((prop) => path.startsWith(`${prop.id}.`) || path === prop.id);
|
|
397
|
+
if (prop) {
|
|
398
|
+
if (prop.children) {
|
|
399
|
+
const result = helper(path, { id: prop.id, name: prop.name, properties: prop.children });
|
|
400
|
+
namePath = result ? prop.name + ' / ' + result : '';
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
namePath = prop.name;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return namePath;
|
|
407
|
+
};
|
|
408
|
+
return helper(path, object) || path;
|
|
409
|
+
};
|
|
410
|
+
const newQuery = parseMongoDB(criteria || originalCriteria || {});
|
|
411
|
+
const ids = getAllRuleIds(newQuery.rules);
|
|
412
|
+
const result = {};
|
|
413
|
+
for (const id of ids) {
|
|
414
|
+
result[id] = getNamePath(id, treeViewObject);
|
|
340
415
|
}
|
|
341
|
-
|
|
416
|
+
setPropertyTreeMap(result);
|
|
417
|
+
}, [criteria, originalCriteria, treeViewObject]);
|
|
342
418
|
const initializeQuery = () => {
|
|
343
419
|
const criteriaToParse = criteria || originalCriteria;
|
|
344
420
|
const updatedQuery = criteriaToParse ? parseMongoDB(criteriaToParse || {}) : undefined;
|
|
345
421
|
return updatedQuery
|
|
346
422
|
? {
|
|
347
423
|
...updatedQuery,
|
|
348
|
-
rules: processRules(updatedQuery.rules),
|
|
424
|
+
rules: processRules(updatedQuery.rules, properties),
|
|
349
425
|
}
|
|
350
426
|
: { combinator: 'and', rules: [] };
|
|
351
427
|
};
|
|
@@ -371,7 +447,7 @@ const CriteriaBuilder = (props) => {
|
|
|
371
447
|
const handleQueryChange = (q) => {
|
|
372
448
|
const processedQuery = {
|
|
373
449
|
...q,
|
|
374
|
-
rules: processRules(q.rules),
|
|
450
|
+
rules: processRules(q.rules, properties),
|
|
375
451
|
};
|
|
376
452
|
setQuery(processedQuery);
|
|
377
453
|
const newCriteria = JSON.parse(formatQuery(processedQuery, {
|
|
@@ -525,17 +601,14 @@ const CriteriaBuilder = (props) => {
|
|
|
525
601
|
presetGroupLabel,
|
|
526
602
|
disabled,
|
|
527
603
|
disabledCriteria,
|
|
528
|
-
treeViewOpts: treeViewOpts
|
|
604
|
+
treeViewOpts: treeViewObject && treeViewOpts
|
|
529
605
|
? {
|
|
530
606
|
...treeViewOpts,
|
|
531
|
-
object:
|
|
532
|
-
|
|
533
|
-
properties: treeViewOpts?.object.properties.filter(({ type }) => type !== 'collection'),
|
|
534
|
-
},
|
|
607
|
+
object: treeViewObject,
|
|
608
|
+
setObject: setTreeViewObject,
|
|
535
609
|
}
|
|
536
610
|
: undefined,
|
|
537
611
|
propertyTreeMap,
|
|
538
|
-
setPropertyTreeMap,
|
|
539
612
|
}, controlClassnames: {
|
|
540
613
|
ruleGroup: 'container',
|
|
541
614
|
}, operators: operators
|
|
@@ -1,74 +1,196 @@
|
|
|
1
|
-
import { render, screen } from '@testing-library/react';
|
|
1
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import React from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { beforeEach, describe, it, vi } from 'vitest';
|
|
5
5
|
import CriteriaBuilder from './CriteriaBuilder';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
name:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
6
|
+
describe('CriteriaBuilderWithTreeView', () => {
|
|
7
|
+
// Mock function for setCriteria
|
|
8
|
+
const setCriteriaMock = vi.fn();
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
// Reset the mock before each test
|
|
11
|
+
setCriteriaMock.mockReset();
|
|
12
|
+
});
|
|
13
|
+
it('expands related object property', async () => {
|
|
14
|
+
const user = userEvent.setup();
|
|
15
|
+
render(React.createElement(CriteriaBuilder, { treeViewOpts: {
|
|
16
|
+
object: {
|
|
17
|
+
id: 'objectA',
|
|
18
|
+
name: 'Object A',
|
|
19
|
+
properties: [
|
|
20
|
+
{
|
|
21
|
+
id: 'relatedObjectProp',
|
|
22
|
+
name: 'Related Object Prop',
|
|
23
|
+
type: 'object',
|
|
24
|
+
objectId: 'objectB',
|
|
25
|
+
children: [
|
|
26
|
+
{
|
|
27
|
+
id: 'relatedObjectProp-loading',
|
|
28
|
+
name: 'Loading...',
|
|
29
|
+
type: 'loading',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
fetchObject: async (id) => {
|
|
36
|
+
if (id === 'objectB') {
|
|
37
|
+
// add delay to simulate network request
|
|
38
|
+
return new Promise((resolve) => setTimeout(() => {
|
|
39
|
+
resolve({
|
|
40
|
+
id: 'objectB',
|
|
41
|
+
name: 'Object B',
|
|
42
|
+
properties: [
|
|
43
|
+
{
|
|
44
|
+
id: 'id',
|
|
45
|
+
name: 'ID',
|
|
46
|
+
type: 'string',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'name',
|
|
50
|
+
name: 'Name',
|
|
51
|
+
type: 'string',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
}, 5000));
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
},
|
|
59
|
+
}, properties: [], criteria: {}, setCriteria: setCriteriaMock }));
|
|
60
|
+
// Step 1: Click "Add Condition" button to create a new rule
|
|
61
|
+
const addConditionButton = screen.getByRole('button', { name: /^add condition$/i });
|
|
62
|
+
await user.click(addConditionButton);
|
|
63
|
+
// Step 2: Click on the property selector to open the dropdown
|
|
64
|
+
const propertySelector = screen.getByPlaceholderText(/select a property/i);
|
|
65
|
+
await user.click(propertySelector);
|
|
66
|
+
// Step 3: Click on "Related Object Prop" to expand it and fetch its children
|
|
67
|
+
const relatedObjectPropItem = screen.getByText('Related Object Prop');
|
|
68
|
+
await user.click(relatedObjectPropItem);
|
|
69
|
+
// Step 4: Verify loading indicator is shown
|
|
70
|
+
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
|
71
|
+
// Step 5: Wait for the related object properties to load and verify ID and Name are visible
|
|
72
|
+
await waitFor(() => {
|
|
73
|
+
expect(screen.getByText('ID')).toBeInTheDocument();
|
|
74
|
+
// eslint-disable-next-line testing-library/no-wait-for-multiple-assertions
|
|
75
|
+
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
76
|
+
}, { timeout: 6000 });
|
|
77
|
+
});
|
|
78
|
+
it('fails to expand related object property', async () => {
|
|
79
|
+
const user = userEvent.setup();
|
|
80
|
+
render(React.createElement(CriteriaBuilder, { treeViewOpts: {
|
|
81
|
+
object: {
|
|
82
|
+
id: 'objectA',
|
|
83
|
+
name: 'Object A',
|
|
84
|
+
properties: [
|
|
85
|
+
{
|
|
86
|
+
id: 'relatedObjectProp',
|
|
87
|
+
name: 'Related Object Prop',
|
|
88
|
+
type: 'object',
|
|
89
|
+
objectId: 'objectB',
|
|
90
|
+
children: [
|
|
91
|
+
{
|
|
92
|
+
id: 'relatedObjectProp-loading',
|
|
93
|
+
name: 'Loading...',
|
|
94
|
+
type: 'loading',
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
fetchObject: async (id) => {
|
|
101
|
+
if (id === 'objectB') {
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
reject('Failed to fetch objectB');
|
|
105
|
+
}, 5000);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
},
|
|
110
|
+
}, properties: [], criteria: {}, setCriteria: setCriteriaMock }));
|
|
111
|
+
// Step 1: Click "Add Condition" button to create a new rule
|
|
112
|
+
const addConditionButton = screen.getByRole('button', { name: /^add condition$/i });
|
|
113
|
+
await user.click(addConditionButton);
|
|
114
|
+
// Step 2: Click on the property selector to open the dropdown
|
|
115
|
+
const propertySelector = screen.getByPlaceholderText(/select a property/i);
|
|
116
|
+
await user.click(propertySelector);
|
|
117
|
+
// Step 3: Click on "Related Object Prop" to expand it and fetch its children
|
|
118
|
+
const relatedObjectPropItem = screen.getByText('Related Object Prop');
|
|
119
|
+
await user.click(relatedObjectPropItem);
|
|
120
|
+
// Step 4: Verify loading indicator is shown
|
|
121
|
+
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
|
122
|
+
// Step 5: Wait for the related object properties to load and verify ID and Name are visible
|
|
123
|
+
await waitFor(() => {
|
|
124
|
+
expect(screen.getByText('Loading Failed')).toBeInTheDocument();
|
|
125
|
+
}, { timeout: 6000 });
|
|
126
|
+
});
|
|
127
|
+
});
|
|
71
128
|
describe('CriteriaBuilder', () => {
|
|
129
|
+
const mockProperties = [
|
|
130
|
+
{
|
|
131
|
+
id: 'name',
|
|
132
|
+
name: 'Name',
|
|
133
|
+
type: 'string',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: 'age',
|
|
137
|
+
name: 'Age',
|
|
138
|
+
type: 'integer',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: 'birthDate',
|
|
142
|
+
name: 'Birth Date',
|
|
143
|
+
type: 'date',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'createdTime',
|
|
147
|
+
name: 'Created Time',
|
|
148
|
+
type: 'time',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: 'tags',
|
|
152
|
+
name: 'Tags',
|
|
153
|
+
type: 'array',
|
|
154
|
+
enum: ['tag1', 'tag2', 'tag3'],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: 'status',
|
|
158
|
+
name: 'Status',
|
|
159
|
+
type: 'string',
|
|
160
|
+
enum: ['active', 'inactive', 'pending'],
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'profilePic',
|
|
164
|
+
name: 'Profile Picture',
|
|
165
|
+
type: 'image',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: 'metadata',
|
|
169
|
+
name: 'Metadata',
|
|
170
|
+
type: 'document',
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'percentCompleted',
|
|
174
|
+
name: 'Percent Completed',
|
|
175
|
+
type: 'number',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: 'boolean',
|
|
179
|
+
name: 'Boolean',
|
|
180
|
+
type: 'boolean',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: 'regularRelatedObject',
|
|
184
|
+
name: 'Regular Related Object',
|
|
185
|
+
type: 'object',
|
|
186
|
+
objectId: 'relatedObjectId',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: 'dynamicRelatedObject',
|
|
190
|
+
name: 'Dynamic Related Object',
|
|
191
|
+
type: 'object',
|
|
192
|
+
},
|
|
193
|
+
];
|
|
72
194
|
// Mock function for setCriteria
|
|
73
195
|
const setCriteriaMock = vi.fn();
|
|
74
196
|
beforeEach(() => {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { Property } from '@evoke-platform/context';
|
|
2
1
|
import React from 'react';
|
|
3
|
-
import {
|
|
2
|
+
import { TreeViewObject } from './types';
|
|
4
3
|
type PropertyTreeProps = {
|
|
5
|
-
fetchObject: (id: string) => Promise<
|
|
6
|
-
rootObject:
|
|
7
|
-
|
|
4
|
+
fetchObject: (id: string) => Promise<TreeViewObject | undefined>;
|
|
5
|
+
rootObject: TreeViewObject;
|
|
6
|
+
setRootObject: React.Dispatch<React.SetStateAction<TreeViewObject>>;
|
|
7
|
+
handleTreePropertySelect: (propertyId: string) => void;
|
|
8
8
|
value: string | undefined;
|
|
9
|
-
propertyTreeMap: Record<string,
|
|
9
|
+
propertyTreeMap: Record<string, string>;
|
|
10
10
|
};
|
|
11
11
|
declare const PropertyTree: (props: PropertyTreeProps) => React.JSX.Element;
|
|
12
12
|
export default PropertyTree;
|