@dxos/react-ui-form 0.8.3-main.672df60 → 0.8.3-staging.0fa589b
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/lib/browser/index.mjs +190 -173
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +150 -133
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +190 -173
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.d.ts +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +3 -2
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +5 -0
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +6 -14
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +2 -2
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormActions.d.ts +3 -1
- package/dist/types/src/components/Form/FormActions.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContent.d.ts +30 -3
- package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
- package/dist/types/src/components/Form/Input.d.ts +6 -4
- package/dist/types/src/components/Form/Input.d.ts.map +1 -1
- package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +3 -2
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +7 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +3 -1
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +3 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/package.json +28 -26
- package/src/components/ControlSection/ControlSection.stories.tsx +15 -3
- package/src/components/ControlSection/ControlSection.tsx +11 -9
- package/src/components/FieldEditor/FieldEditor.stories.tsx +25 -5
- package/src/components/FieldEditor/FieldEditor.test.tsx +21 -13
- package/src/components/FieldEditor/FieldEditor.tsx +11 -2
- package/src/components/Form/ArrayField.tsx +1 -1
- package/src/components/Form/Defaults.tsx +5 -25
- package/src/components/Form/Form.stories.tsx +5 -5
- package/src/components/Form/Form.tsx +44 -49
- package/src/components/Form/FormActions.tsx +17 -2
- package/src/components/Form/FormContent.tsx +46 -46
- package/src/components/Form/Input.tsx +13 -8
- package/src/components/Form/RefField.tsx +1 -5
- package/src/components/Form/custom/GeoPointInput.tsx +1 -3
- package/src/components/Form/custom/SelectOptionsInput.stories.tsx +7 -5
- package/src/components/Form/custom/SelectOptionsInput.tsx +1 -3
- package/src/components/Form/custom/TupleInput.tsx +1 -3
- package/src/components/ViewEditor/ViewEditor.stories.tsx +25 -9
- package/src/components/ViewEditor/ViewEditor.test.tsx +80 -42
- package/src/components/ViewEditor/ViewEditor.tsx +66 -66
- package/src/components/testing.tsx +12 -7
- package/src/translations.ts +4 -1
|
@@ -5,21 +5,36 @@
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
7
|
import { IconButton, useTranslation } from '@dxos/react-ui';
|
|
8
|
+
import { cardSpacing } from '@dxos/react-ui-stack';
|
|
9
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
8
10
|
|
|
9
11
|
import { useFormContext } from './FormContext';
|
|
10
12
|
import { translationKey } from '../../translations';
|
|
11
13
|
|
|
14
|
+
export type FormOuterSpacing = boolean | 'blockStart-0' | 'scroll-fields';
|
|
15
|
+
|
|
12
16
|
export type FormActionsProps = {
|
|
13
17
|
readonly?: boolean;
|
|
14
18
|
onCancel?: () => void;
|
|
19
|
+
outerSpacing?: FormOuterSpacing;
|
|
15
20
|
};
|
|
16
21
|
|
|
17
|
-
export const FormActions = ({ readonly, onCancel }: FormActionsProps) => {
|
|
22
|
+
export const FormActions = ({ readonly, onCancel, outerSpacing = true }: FormActionsProps) => {
|
|
18
23
|
const { t } = useTranslation(translationKey);
|
|
19
24
|
const { canSave, handleSave } = useFormContext();
|
|
20
25
|
|
|
21
26
|
return (
|
|
22
|
-
<div
|
|
27
|
+
<div
|
|
28
|
+
role='none'
|
|
29
|
+
className={mx(
|
|
30
|
+
outerSpacing === 'scroll-fields'
|
|
31
|
+
? 'pli-cardSpacingInline mbe-cardSpacingBlock'
|
|
32
|
+
: outerSpacing
|
|
33
|
+
? cardSpacing
|
|
34
|
+
: 'mbs-cardSpacingBlock',
|
|
35
|
+
'flex [&_button]:grow gap-1 first:mbs-0',
|
|
36
|
+
)}
|
|
37
|
+
>
|
|
23
38
|
{onCancel && !readonly && (
|
|
24
39
|
<IconButton data-testid='cancel-button' icon='ph--x--regular' label={t('button cancel')} onClick={onCancel} />
|
|
25
40
|
)}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { Schema, SchemaAST, pipe } from 'effect';
|
|
6
6
|
import { capitalize } from 'effect/String';
|
|
7
|
-
import React, { useMemo } from 'react';
|
|
7
|
+
import React, { forwardRef, useMemo } from 'react';
|
|
8
8
|
|
|
9
9
|
import { createJsonPath, findNode, getDiscriminatedType, isDiscriminatedUnion } from '@dxos/effect';
|
|
10
10
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
@@ -42,8 +42,8 @@ export const FormField = ({
|
|
|
42
42
|
lookupComponent,
|
|
43
43
|
Custom,
|
|
44
44
|
}: FormFieldProps) => {
|
|
45
|
-
const inputProps = useInputProps(path);
|
|
46
45
|
const { ast, name, type, format, title, description, options, examples, array } = property;
|
|
46
|
+
const inputProps = useInputProps(path);
|
|
47
47
|
|
|
48
48
|
const label = useMemo(() => title ?? pipe(name, capitalize), [title, name]);
|
|
49
49
|
const placeholder = useMemo(
|
|
@@ -169,7 +169,7 @@ export const FormField = ({
|
|
|
169
169
|
if (typeLiteral) {
|
|
170
170
|
return (
|
|
171
171
|
<>
|
|
172
|
-
{!inline && <h3 className='text-lg
|
|
172
|
+
{!inline && <h3 className={mx('text-lg mlb-inputSpacingBlock first:mbs-0')}>{label}</h3>}
|
|
173
173
|
<FormFields
|
|
174
174
|
schema={Schema.make(typeLiteral)}
|
|
175
175
|
path={path}
|
|
@@ -187,54 +187,54 @@ export const FormField = ({
|
|
|
187
187
|
};
|
|
188
188
|
|
|
189
189
|
export type FormFieldsProps = ThemedClassName<{
|
|
190
|
-
|
|
190
|
+
testId?: string;
|
|
191
|
+
readonly?: boolean;
|
|
191
192
|
schema: Schema.Schema.All;
|
|
193
|
+
/**
|
|
194
|
+
* Path to the current object from the root. Used with nested forms.
|
|
195
|
+
*/
|
|
192
196
|
path?: (string | number)[];
|
|
193
197
|
filter?: (props: SchemaProperty<any>[]) => SchemaProperty<any>[];
|
|
194
198
|
sort?: string[];
|
|
195
|
-
readonly?: boolean;
|
|
196
199
|
lookupComponent?: ComponentLookup;
|
|
200
|
+
/**
|
|
201
|
+
* Map of custom renderers for specific properties.
|
|
202
|
+
* Prefer lookupComponent for plugin specific input surfaces.
|
|
203
|
+
*/
|
|
204
|
+
Custom?: Partial<Record<string, InputComponent>>;
|
|
197
205
|
onQueryRefOptions?: QueryRefOptions;
|
|
198
206
|
}>;
|
|
199
207
|
|
|
200
|
-
export const FormFields = (
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
Custom={Custom}
|
|
234
|
-
/>
|
|
235
|
-
);
|
|
236
|
-
})
|
|
237
|
-
.filter(isNotFalsy)}
|
|
238
|
-
</div>
|
|
239
|
-
);
|
|
240
|
-
};
|
|
208
|
+
export const FormFields = forwardRef<HTMLDivElement, FormFieldsProps>(
|
|
209
|
+
(
|
|
210
|
+
{ classNames, testId, schema, path, filter, sort, readonly, lookupComponent, Custom, onQueryRefOptions },
|
|
211
|
+
forwardRef,
|
|
212
|
+
) => {
|
|
213
|
+
const values = useFormValues(path);
|
|
214
|
+
const properties = useMemo(() => {
|
|
215
|
+
const props = getSchemaProperties(schema.ast, values);
|
|
216
|
+
const filtered = filter ? filter(props) : props;
|
|
217
|
+
return sort ? filtered.sort((a, b) => sort.indexOf(a.name) - sort.indexOf(b.name)) : filtered;
|
|
218
|
+
}, [schema, values, filter, sort]);
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<div role='form' className={mx('is-full', classNames)} ref={forwardRef}>
|
|
222
|
+
{properties
|
|
223
|
+
.map((property) => {
|
|
224
|
+
return (
|
|
225
|
+
<FormField
|
|
226
|
+
key={property.name}
|
|
227
|
+
property={property}
|
|
228
|
+
path={[...(path ?? []), property.name]}
|
|
229
|
+
readonly={readonly}
|
|
230
|
+
onQueryRefOptions={onQueryRefOptions}
|
|
231
|
+
lookupComponent={lookupComponent}
|
|
232
|
+
Custom={Custom}
|
|
233
|
+
/>
|
|
234
|
+
);
|
|
235
|
+
})
|
|
236
|
+
.filter(isNotFalsy)}
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
},
|
|
240
|
+
);
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { type FC
|
|
5
|
+
import React, { type FC } from 'react';
|
|
6
6
|
|
|
7
7
|
import { type FormatEnum } from '@dxos/echo-schema';
|
|
8
8
|
import { type SimpleType } from '@dxos/effect';
|
|
9
|
-
import { Icon, Tooltip } from '@dxos/react-ui';
|
|
10
|
-
import {
|
|
9
|
+
import { Icon, Input, Tooltip } from '@dxos/react-ui';
|
|
10
|
+
import { labelSpacing } from '@dxos/react-ui-stack';
|
|
11
|
+
import { errorText, mx } from '@dxos/react-ui-theme';
|
|
11
12
|
|
|
12
13
|
import { type FormInputStateProps } from './FormContext';
|
|
13
14
|
|
|
@@ -28,14 +29,18 @@ export type InputProps = {
|
|
|
28
29
|
*/
|
|
29
30
|
export type InputComponent = FC<InputProps>;
|
|
30
31
|
|
|
31
|
-
export type InputHeaderProps =
|
|
32
|
+
export type InputHeaderProps = {
|
|
32
33
|
error?: string;
|
|
33
|
-
|
|
34
|
+
label: string;
|
|
35
|
+
readonly?: boolean;
|
|
36
|
+
};
|
|
34
37
|
|
|
35
|
-
export const InputHeader = ({
|
|
38
|
+
export const InputHeader = ({ error, label, readonly }: InputHeaderProps) => {
|
|
39
|
+
const Label = readonly ? 'span' : Input.Label;
|
|
40
|
+
const labelProps = readonly ? {} : { classNames: '!mlb-0' };
|
|
36
41
|
return (
|
|
37
|
-
<div role='none' className='flex justify-between items-center
|
|
38
|
-
{
|
|
42
|
+
<div role='none' className={mx('flex justify-between items-center', labelSpacing)}>
|
|
43
|
+
<Label {...labelProps}>{label}</Label>
|
|
39
44
|
{error && (
|
|
40
45
|
<Tooltip.Trigger asChild content={error} side='bottom'>
|
|
41
46
|
<Icon icon='ph--warning--regular' size={4} classNames={errorText} />
|
|
@@ -136,11 +136,7 @@ export const RefField = ({
|
|
|
136
136
|
// NOTE(thure): I left both predicates in-place in case we decide to add variants which do render readonly but empty values.
|
|
137
137
|
return disabled && items.length < 1 ? null : (
|
|
138
138
|
<Input.Root validationValence={status}>
|
|
139
|
-
{!inputOnly &&
|
|
140
|
-
<InputHeader error={error}>
|
|
141
|
-
<Input.Label>{label}</Input.Label>
|
|
142
|
-
</InputHeader>
|
|
143
|
-
)}
|
|
139
|
+
{!inputOnly && <InputHeader error={error} label={label} />}
|
|
144
140
|
<div data-no-submit>
|
|
145
141
|
{disabled ? (
|
|
146
142
|
items.length < 1 ? (
|
|
@@ -46,9 +46,7 @@ export const GeoPointInput = ({ type, label, disabled, getStatus, getValue, onVa
|
|
|
46
46
|
|
|
47
47
|
return (
|
|
48
48
|
<Input.Root validationValence={status}>
|
|
49
|
-
<InputHeader error={error}
|
|
50
|
-
<Input.Label>{label}</Input.Label>
|
|
51
|
-
</InputHeader>
|
|
49
|
+
<InputHeader error={error} label={label} />
|
|
52
50
|
<div role='none' className='grid grid-cols-2 gap-2'>
|
|
53
51
|
<Input.TextInput
|
|
54
52
|
type='text'
|
|
@@ -47,13 +47,15 @@ const DefaultStory = () => {
|
|
|
47
47
|
const meta: Meta = {
|
|
48
48
|
title: 'ui/react-ui-form/SelectOptionsInput',
|
|
49
49
|
component: SelectOptionInput,
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
render: DefaultStory,
|
|
51
|
+
decorators: [withTheme, withLayout({ fullscreen: true })],
|
|
52
|
+
parameters: {
|
|
53
|
+
translations,
|
|
54
|
+
},
|
|
52
55
|
};
|
|
53
56
|
|
|
54
57
|
export default meta;
|
|
58
|
+
|
|
55
59
|
type Story = StoryObj;
|
|
56
60
|
|
|
57
|
-
export const Default: Story = {
|
|
58
|
-
render: DefaultStory,
|
|
59
|
-
};
|
|
61
|
+
export const Default: Story = {};
|
|
@@ -112,9 +112,7 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
|
|
|
112
112
|
|
|
113
113
|
return (
|
|
114
114
|
<Input.Root validationValence={status}>
|
|
115
|
-
<InputHeader error={error}
|
|
116
|
-
<Input.Label>{label}</Input.Label>
|
|
117
|
-
</InputHeader>
|
|
115
|
+
<InputHeader error={error} label={label} />
|
|
118
116
|
<div role='none'>
|
|
119
117
|
{options && (
|
|
120
118
|
<List.Root items={options} isItem={(item) => true} onMove={handleMove}>
|
|
@@ -29,9 +29,7 @@ export const TupleInput = ({
|
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
31
|
<Input.Root validationValence={status}>
|
|
32
|
-
<InputHeader error={error}
|
|
33
|
-
<Input.Label>{label}</Input.Label>
|
|
34
|
-
</InputHeader>
|
|
32
|
+
<InputHeader error={error} label={label} />
|
|
35
33
|
<div className={mx('grid gap-2', gridCols[binding.length - 1])}>
|
|
36
34
|
{binding.map((prop) => (
|
|
37
35
|
<Input.TextInput
|
|
@@ -6,7 +6,7 @@ import '@dxos-theme';
|
|
|
6
6
|
|
|
7
7
|
import { type Meta, type StoryObj } from '@storybook/react';
|
|
8
8
|
import { Schema } from 'effect';
|
|
9
|
-
import React, { useCallback, useMemo, useState } from 'react';
|
|
9
|
+
import React, { useCallback, useMemo, useState, useEffect } from 'react';
|
|
10
10
|
|
|
11
11
|
import { Format, type EchoSchema, toJsonSchema, TypedObject } from '@dxos/echo-schema';
|
|
12
12
|
import { Filter, useQuery, useSpace } from '@dxos/react-client/echo';
|
|
@@ -17,7 +17,14 @@ import { withTheme, withLayout } from '@dxos/storybook-utils';
|
|
|
17
17
|
|
|
18
18
|
import { ViewEditor, type ViewEditorProps } from './ViewEditor';
|
|
19
19
|
import translations from '../../translations';
|
|
20
|
-
import { TestLayout, TestPanel } from '../testing';
|
|
20
|
+
import { TestLayout, TestPanel, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
21
|
+
|
|
22
|
+
// Type definition for debug objects exposed to tests.
|
|
23
|
+
export type ViewEditorDebugObjects = {
|
|
24
|
+
schema: EchoSchema;
|
|
25
|
+
view: ViewType;
|
|
26
|
+
projection: ViewProjection;
|
|
27
|
+
};
|
|
21
28
|
|
|
22
29
|
type StoryProps = Pick<ViewEditorProps, 'readonly'>;
|
|
23
30
|
|
|
@@ -62,12 +69,25 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
62
69
|
|
|
63
70
|
const handleDelete = useCallback((property: string) => projection?.deleteFieldProjection(property), [projection]);
|
|
64
71
|
|
|
72
|
+
// Expose objects on window for test access.
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (typeof window !== 'undefined' && schema && view && projection) {
|
|
75
|
+
(window as any)[VIEW_EDITOR_DEBUG_SYMBOL] = { schema, view, projection } satisfies ViewEditorDebugObjects;
|
|
76
|
+
}
|
|
77
|
+
}, [schema, view, projection]);
|
|
78
|
+
|
|
79
|
+
// NOTE(ZaymonFC): This looks awkward but it resolves an infinite parsing issue with sb.
|
|
80
|
+
const json = useMemo(
|
|
81
|
+
() => JSON.parse(JSON.stringify({ schema, view, projection })),
|
|
82
|
+
[JSON.stringify(schema), JSON.stringify(view), JSON.stringify(projection)],
|
|
83
|
+
);
|
|
84
|
+
|
|
65
85
|
if (!schema || !view || !projection) {
|
|
66
86
|
return <div />;
|
|
67
87
|
}
|
|
68
88
|
|
|
69
89
|
return (
|
|
70
|
-
<TestLayout json={
|
|
90
|
+
<TestLayout json={json}>
|
|
71
91
|
<TestPanel>
|
|
72
92
|
<ViewEditor
|
|
73
93
|
schema={schema}
|
|
@@ -86,9 +106,7 @@ const meta: Meta<StoryProps> = {
|
|
|
86
106
|
title: 'ui/react-ui-form/ViewEditor',
|
|
87
107
|
render: DefaultStory,
|
|
88
108
|
decorators: [withClientProvider({ createSpace: true }), withLayout({ fullscreen: true }), withTheme],
|
|
89
|
-
parameters: {
|
|
90
|
-
translations,
|
|
91
|
-
},
|
|
109
|
+
parameters: { translations },
|
|
92
110
|
};
|
|
93
111
|
|
|
94
112
|
export default meta;
|
|
@@ -98,7 +116,5 @@ type Story = StoryObj<StoryProps>;
|
|
|
98
116
|
export const Default: Story = {};
|
|
99
117
|
|
|
100
118
|
export const Readonly: Story = {
|
|
101
|
-
args: {
|
|
102
|
-
readonly: true,
|
|
103
|
-
},
|
|
119
|
+
args: { readonly: true },
|
|
104
120
|
};
|
|
@@ -6,14 +6,31 @@ import { composeStories } from '@storybook/react';
|
|
|
6
6
|
import { screen, cleanup, fireEvent, waitFor } from '@testing-library/react';
|
|
7
7
|
import { afterEach, describe, expect, test } from 'vitest';
|
|
8
8
|
|
|
9
|
+
import { EchoSchema } from '@dxos/echo-schema';
|
|
10
|
+
import { ViewProjection, ViewType } from '@dxos/schema';
|
|
11
|
+
|
|
9
12
|
import * as stories from './ViewEditor.stories';
|
|
13
|
+
import { type ViewEditorDebugObjects } from './ViewEditor.stories';
|
|
14
|
+
import { VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
10
15
|
|
|
11
16
|
const { Default } = composeStories(stories);
|
|
12
17
|
|
|
18
|
+
const getViewEditorDebugObjects = (): ViewEditorDebugObjects => {
|
|
19
|
+
const debugObjects = (window as any)[VIEW_EDITOR_DEBUG_SYMBOL] as ViewEditorDebugObjects;
|
|
20
|
+
expect(debugObjects).toBeDefined();
|
|
21
|
+
expect(debugObjects.schema).toBeInstanceOf(EchoSchema);
|
|
22
|
+
expect(debugObjects.view).toBeInstanceOf(ViewType);
|
|
23
|
+
expect(debugObjects.projection).toBeInstanceOf(ViewProjection);
|
|
24
|
+
return debugObjects;
|
|
25
|
+
};
|
|
26
|
+
|
|
13
27
|
const waitForViewEditor = async () => {
|
|
14
|
-
await waitFor(
|
|
15
|
-
|
|
16
|
-
|
|
28
|
+
await waitFor(
|
|
29
|
+
() => {
|
|
30
|
+
expect(screen.getByTestId('debug')).toBeInTheDocument();
|
|
31
|
+
},
|
|
32
|
+
{ timeout: 15000 },
|
|
33
|
+
);
|
|
17
34
|
};
|
|
18
35
|
|
|
19
36
|
describe('ViewEditor', () => {
|
|
@@ -41,26 +58,30 @@ describe('ViewEditor', () => {
|
|
|
41
58
|
|
|
42
59
|
fireEvent.click(screen.getByTestId('save-button'));
|
|
43
60
|
|
|
44
|
-
//
|
|
45
|
-
|
|
61
|
+
// Wait for the changes to propagate to both UI and schema
|
|
62
|
+
await waitFor(() => {
|
|
63
|
+
expect(screen.getByText('new_property')).toBeInTheDocument();
|
|
64
|
+
});
|
|
46
65
|
|
|
47
|
-
|
|
66
|
+
// Access the live objects directly from window using symbol.
|
|
67
|
+
const debugObjects = getViewEditorDebugObjects();
|
|
48
68
|
|
|
49
|
-
// Check schema contains new_property
|
|
50
|
-
const schemaProperties =
|
|
51
|
-
expect(schemaProperties
|
|
69
|
+
// Check schema contains new_property
|
|
70
|
+
const schemaProperties = debugObjects.schema.jsonSchema.properties;
|
|
71
|
+
expect(schemaProperties).toBeDefined();
|
|
72
|
+
expect(schemaProperties!.new_property).toBeDefined();
|
|
73
|
+
expect(schemaProperties!.name).toBeUndefined();
|
|
52
74
|
|
|
53
|
-
// Check view contains new_property field
|
|
54
|
-
const newPropertyField =
|
|
75
|
+
// Check view contains new_property field
|
|
76
|
+
const newPropertyField = debugObjects.view.fields.find((field: any) => field.path === 'new_property');
|
|
55
77
|
expect(newPropertyField).toBeDefined();
|
|
56
|
-
expect(newPropertyField
|
|
78
|
+
expect(newPropertyField!.path).toBe('new_property');
|
|
57
79
|
|
|
58
|
-
// Check projection contains new_property
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
);
|
|
80
|
+
// Check projection contains new_property
|
|
81
|
+
const fieldProjections = debugObjects.projection.getFieldProjections();
|
|
82
|
+
const newPropertyProjection = fieldProjections.find((proj: any) => proj.field.path === 'new_property');
|
|
62
83
|
expect(newPropertyProjection).toBeDefined();
|
|
63
|
-
expect(newPropertyProjection
|
|
84
|
+
expect(newPropertyProjection!.props.property).toBe('new_property');
|
|
64
85
|
});
|
|
65
86
|
|
|
66
87
|
test('add new property', async () => {
|
|
@@ -85,26 +106,29 @@ describe('ViewEditor', () => {
|
|
|
85
106
|
// Save the changes
|
|
86
107
|
fireEvent.click(screen.getByTestId('save-button'));
|
|
87
108
|
|
|
88
|
-
//
|
|
89
|
-
|
|
109
|
+
// Wait for the changes to propagate
|
|
110
|
+
await waitFor(() => {
|
|
111
|
+
expect(screen.getByText('added_property')).toBeInTheDocument();
|
|
112
|
+
});
|
|
90
113
|
|
|
91
|
-
|
|
114
|
+
// Access the live objects directly from window using symbol.
|
|
115
|
+
const debugObjects = getViewEditorDebugObjects();
|
|
92
116
|
|
|
93
117
|
// Check schema contains added_property
|
|
94
|
-
const schemaProperties =
|
|
95
|
-
expect(schemaProperties
|
|
118
|
+
const schemaProperties = debugObjects.schema.jsonSchema.properties;
|
|
119
|
+
expect(schemaProperties).toBeDefined();
|
|
120
|
+
expect(schemaProperties!.added_property).toBeDefined();
|
|
96
121
|
|
|
97
122
|
// Check view contains added_property field
|
|
98
|
-
const addedPropertyField =
|
|
123
|
+
const addedPropertyField = debugObjects.view.fields.find((field: any) => field.path === 'added_property');
|
|
99
124
|
expect(addedPropertyField).toBeDefined();
|
|
100
|
-
expect(addedPropertyField
|
|
125
|
+
expect(addedPropertyField!.path).toBe('added_property');
|
|
101
126
|
|
|
102
127
|
// Check projection contains added_property
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
);
|
|
128
|
+
const fieldProjections = debugObjects.projection.getFieldProjections();
|
|
129
|
+
const addedPropertyProjection = fieldProjections.find((proj: any) => proj.field.path === 'added_property');
|
|
106
130
|
expect(addedPropertyProjection).toBeDefined();
|
|
107
|
-
expect(addedPropertyProjection
|
|
131
|
+
expect(addedPropertyProjection!.props.property).toBe('added_property');
|
|
108
132
|
});
|
|
109
133
|
|
|
110
134
|
test('delete property', async () => {
|
|
@@ -123,21 +147,26 @@ describe('ViewEditor', () => {
|
|
|
123
147
|
// Click the delete button
|
|
124
148
|
fireEvent.click(deleteButton);
|
|
125
149
|
|
|
126
|
-
//
|
|
127
|
-
|
|
150
|
+
// Wait for the changes to propagate
|
|
151
|
+
await waitFor(() => {
|
|
152
|
+
expect(screen.queryByText('name')).not.toBeInTheDocument();
|
|
153
|
+
});
|
|
128
154
|
|
|
129
|
-
|
|
155
|
+
// Access the live objects directly from window using symbol.
|
|
156
|
+
const debugObjects = getViewEditorDebugObjects();
|
|
130
157
|
|
|
131
158
|
// Check schema no longer contains the name property
|
|
132
|
-
const schemaProperties =
|
|
133
|
-
expect(schemaProperties
|
|
159
|
+
const schemaProperties = debugObjects.schema.jsonSchema.properties;
|
|
160
|
+
expect(schemaProperties).toBeDefined();
|
|
161
|
+
expect(schemaProperties!.name).toBeUndefined();
|
|
134
162
|
|
|
135
163
|
// Check view no longer contains the name field
|
|
136
|
-
const nameField =
|
|
164
|
+
const nameField = debugObjects.view.fields.find((field: any) => field.path === 'name');
|
|
137
165
|
expect(nameField).toBeUndefined();
|
|
138
166
|
|
|
139
167
|
// Check projection no longer contains the name property
|
|
140
|
-
const
|
|
168
|
+
const fieldProjections = debugObjects.projection.getFieldProjections();
|
|
169
|
+
const nameProjection = fieldProjections.find((proj: any) => proj.field.path === 'name');
|
|
141
170
|
expect(nameProjection).toBeUndefined();
|
|
142
171
|
});
|
|
143
172
|
|
|
@@ -150,17 +179,26 @@ describe('ViewEditor', () => {
|
|
|
150
179
|
fireEvent.click(hideButtons[0]);
|
|
151
180
|
|
|
152
181
|
// Check that hiddenFields is non-empty
|
|
153
|
-
let debugInfo = JSON.parse(screen.getByTestId('debug').textContent!);
|
|
154
|
-
expect(debugInfo.view.hiddenFields.length).toBeGreaterThan(0);
|
|
155
|
-
|
|
156
|
-
// Wait for the show button to appear and click it
|
|
157
182
|
await waitFor(() => {
|
|
158
183
|
expect(screen.getByTestId('show-field-button')).toBeInTheDocument();
|
|
159
184
|
});
|
|
185
|
+
|
|
186
|
+
// Access the live objects directly from window using symbol.
|
|
187
|
+
const debugObjects = getViewEditorDebugObjects();
|
|
188
|
+
|
|
189
|
+
// Check that hiddenFields is non-empty
|
|
190
|
+
expect(debugObjects.view.hiddenFields).toBeDefined();
|
|
191
|
+
expect(debugObjects.view.hiddenFields!.length).toBeGreaterThan(0);
|
|
192
|
+
|
|
193
|
+
// Click the show button
|
|
160
194
|
fireEvent.click(screen.getByTestId('show-field-button'));
|
|
161
195
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
196
|
+
// Wait for the data to update and check that hiddenFields is empty
|
|
197
|
+
await waitFor(() => {
|
|
198
|
+
expect(debugObjects.view.hiddenFields!.length).toBe(0);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Also verify that the field is back in the visible fields list
|
|
202
|
+
expect(debugObjects.view.fields.length).toBeGreaterThan(0);
|
|
165
203
|
});
|
|
166
204
|
});
|