@dxos/react-ui-form 0.8.3-main.672df60 → 0.8.3-main.7f5a14c
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 +10 -9
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +10 -9
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +10 -9
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +6 -0
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +2 -1
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +8 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/package.json +27 -26
- package/src/components/FieldEditor/FieldEditor.stories.tsx +27 -4
- package/src/components/FieldEditor/FieldEditor.test.tsx +20 -13
- package/src/components/Form/Form.tsx +4 -1
- package/src/components/Form/FormActions.tsx +1 -1
- package/src/components/Form/FormContent.tsx +2 -2
- package/src/components/ViewEditor/ViewEditor.stories.tsx +27 -8
- package/src/components/ViewEditor/ViewEditor.test.tsx +79 -42
- package/src/components/ViewEditor/ViewEditor.tsx +4 -4
|
@@ -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';
|
|
@@ -19,6 +19,16 @@ import { ViewEditor, type ViewEditorProps } from './ViewEditor';
|
|
|
19
19
|
import translations from '../../translations';
|
|
20
20
|
import { TestLayout, TestPanel } from '../testing';
|
|
21
21
|
|
|
22
|
+
// Symbol for accessing debug objects in tests.
|
|
23
|
+
export const VIEW_EDITOR_DEBUG_SYMBOL = Symbol('viewEditorDebug');
|
|
24
|
+
|
|
25
|
+
// Type definition for debug objects exposed to tests.
|
|
26
|
+
export type ViewEditorDebugObjects = {
|
|
27
|
+
schema: EchoSchema;
|
|
28
|
+
view: ViewType;
|
|
29
|
+
projection: ViewProjection;
|
|
30
|
+
};
|
|
31
|
+
|
|
22
32
|
type StoryProps = Pick<ViewEditorProps, 'readonly'>;
|
|
23
33
|
|
|
24
34
|
const DefaultStory = (props: StoryProps) => {
|
|
@@ -62,12 +72,25 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
62
72
|
|
|
63
73
|
const handleDelete = useCallback((property: string) => projection?.deleteFieldProjection(property), [projection]);
|
|
64
74
|
|
|
75
|
+
// Expose objects on window for test access.
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (typeof window !== 'undefined' && schema && view && projection) {
|
|
78
|
+
(window as any)[VIEW_EDITOR_DEBUG_SYMBOL] = { schema, view, projection } satisfies ViewEditorDebugObjects;
|
|
79
|
+
}
|
|
80
|
+
}, [schema, view, projection]);
|
|
81
|
+
|
|
82
|
+
// NOTE(ZaymonFC): This looks awkward but it resolves an infinite parsing issue with sb.
|
|
83
|
+
const json = useMemo(
|
|
84
|
+
() => JSON.parse(JSON.stringify({ schema, view, projection })),
|
|
85
|
+
[JSON.stringify(schema), JSON.stringify(view), JSON.stringify(projection)],
|
|
86
|
+
);
|
|
87
|
+
|
|
65
88
|
if (!schema || !view || !projection) {
|
|
66
89
|
return <div />;
|
|
67
90
|
}
|
|
68
91
|
|
|
69
92
|
return (
|
|
70
|
-
<TestLayout json={
|
|
93
|
+
<TestLayout json={json}>
|
|
71
94
|
<TestPanel>
|
|
72
95
|
<ViewEditor
|
|
73
96
|
schema={schema}
|
|
@@ -86,9 +109,7 @@ const meta: Meta<StoryProps> = {
|
|
|
86
109
|
title: 'ui/react-ui-form/ViewEditor',
|
|
87
110
|
render: DefaultStory,
|
|
88
111
|
decorators: [withClientProvider({ createSpace: true }), withLayout({ fullscreen: true }), withTheme],
|
|
89
|
-
parameters: {
|
|
90
|
-
translations,
|
|
91
|
-
},
|
|
112
|
+
parameters: { translations },
|
|
92
113
|
};
|
|
93
114
|
|
|
94
115
|
export default meta;
|
|
@@ -98,7 +119,5 @@ type Story = StoryObj<StoryProps>;
|
|
|
98
119
|
export const Default: Story = {};
|
|
99
120
|
|
|
100
121
|
export const Readonly: Story = {
|
|
101
|
-
args: {
|
|
102
|
-
readonly: true,
|
|
103
|
-
},
|
|
122
|
+
args: { readonly: true },
|
|
104
123
|
};
|
|
@@ -6,14 +6,30 @@ 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 { VIEW_EDITOR_DEBUG_SYMBOL, type ViewEditorDebugObjects } from './ViewEditor.stories';
|
|
10
14
|
|
|
11
15
|
const { Default } = composeStories(stories);
|
|
12
16
|
|
|
17
|
+
const getViewEditorDebugObjects = (): ViewEditorDebugObjects => {
|
|
18
|
+
const debugObjects = (window as any)[VIEW_EDITOR_DEBUG_SYMBOL] as ViewEditorDebugObjects;
|
|
19
|
+
expect(debugObjects).toBeDefined();
|
|
20
|
+
expect(debugObjects.schema).toBeInstanceOf(EchoSchema);
|
|
21
|
+
expect(debugObjects.view).toBeInstanceOf(ViewType);
|
|
22
|
+
expect(debugObjects.projection).toBeInstanceOf(ViewProjection);
|
|
23
|
+
return debugObjects;
|
|
24
|
+
};
|
|
25
|
+
|
|
13
26
|
const waitForViewEditor = async () => {
|
|
14
|
-
await waitFor(
|
|
15
|
-
|
|
16
|
-
|
|
27
|
+
await waitFor(
|
|
28
|
+
() => {
|
|
29
|
+
expect(screen.getByTestId('debug')).toBeInTheDocument();
|
|
30
|
+
},
|
|
31
|
+
{ timeout: 15000 },
|
|
32
|
+
);
|
|
17
33
|
};
|
|
18
34
|
|
|
19
35
|
describe('ViewEditor', () => {
|
|
@@ -41,26 +57,30 @@ describe('ViewEditor', () => {
|
|
|
41
57
|
|
|
42
58
|
fireEvent.click(screen.getByTestId('save-button'));
|
|
43
59
|
|
|
44
|
-
//
|
|
45
|
-
|
|
60
|
+
// Wait for the changes to propagate to both UI and schema
|
|
61
|
+
await waitFor(() => {
|
|
62
|
+
expect(screen.getByText('new_property')).toBeInTheDocument();
|
|
63
|
+
});
|
|
46
64
|
|
|
47
|
-
|
|
65
|
+
// Access the live objects directly from window using symbol.
|
|
66
|
+
const debugObjects = getViewEditorDebugObjects();
|
|
48
67
|
|
|
49
|
-
// Check schema contains new_property
|
|
50
|
-
const schemaProperties =
|
|
51
|
-
expect(schemaProperties
|
|
68
|
+
// Check schema contains new_property
|
|
69
|
+
const schemaProperties = debugObjects.schema.jsonSchema.properties;
|
|
70
|
+
expect(schemaProperties).toBeDefined();
|
|
71
|
+
expect(schemaProperties!.new_property).toBeDefined();
|
|
72
|
+
expect(schemaProperties!.name).toBeUndefined();
|
|
52
73
|
|
|
53
|
-
// Check view contains new_property field
|
|
54
|
-
const newPropertyField =
|
|
74
|
+
// Check view contains new_property field
|
|
75
|
+
const newPropertyField = debugObjects.view.fields.find((field: any) => field.path === 'new_property');
|
|
55
76
|
expect(newPropertyField).toBeDefined();
|
|
56
|
-
expect(newPropertyField
|
|
77
|
+
expect(newPropertyField!.path).toBe('new_property');
|
|
57
78
|
|
|
58
|
-
// Check projection contains new_property
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
);
|
|
79
|
+
// Check projection contains new_property
|
|
80
|
+
const fieldProjections = debugObjects.projection.getFieldProjections();
|
|
81
|
+
const newPropertyProjection = fieldProjections.find((proj: any) => proj.field.path === 'new_property');
|
|
62
82
|
expect(newPropertyProjection).toBeDefined();
|
|
63
|
-
expect(newPropertyProjection
|
|
83
|
+
expect(newPropertyProjection!.props.property).toBe('new_property');
|
|
64
84
|
});
|
|
65
85
|
|
|
66
86
|
test('add new property', async () => {
|
|
@@ -85,26 +105,29 @@ describe('ViewEditor', () => {
|
|
|
85
105
|
// Save the changes
|
|
86
106
|
fireEvent.click(screen.getByTestId('save-button'));
|
|
87
107
|
|
|
88
|
-
//
|
|
89
|
-
|
|
108
|
+
// Wait for the changes to propagate
|
|
109
|
+
await waitFor(() => {
|
|
110
|
+
expect(screen.getByText('added_property')).toBeInTheDocument();
|
|
111
|
+
});
|
|
90
112
|
|
|
91
|
-
|
|
113
|
+
// Access the live objects directly from window using symbol.
|
|
114
|
+
const debugObjects = getViewEditorDebugObjects();
|
|
92
115
|
|
|
93
116
|
// Check schema contains added_property
|
|
94
|
-
const schemaProperties =
|
|
95
|
-
expect(schemaProperties
|
|
117
|
+
const schemaProperties = debugObjects.schema.jsonSchema.properties;
|
|
118
|
+
expect(schemaProperties).toBeDefined();
|
|
119
|
+
expect(schemaProperties!.added_property).toBeDefined();
|
|
96
120
|
|
|
97
121
|
// Check view contains added_property field
|
|
98
|
-
const addedPropertyField =
|
|
122
|
+
const addedPropertyField = debugObjects.view.fields.find((field: any) => field.path === 'added_property');
|
|
99
123
|
expect(addedPropertyField).toBeDefined();
|
|
100
|
-
expect(addedPropertyField
|
|
124
|
+
expect(addedPropertyField!.path).toBe('added_property');
|
|
101
125
|
|
|
102
126
|
// Check projection contains added_property
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
);
|
|
127
|
+
const fieldProjections = debugObjects.projection.getFieldProjections();
|
|
128
|
+
const addedPropertyProjection = fieldProjections.find((proj: any) => proj.field.path === 'added_property');
|
|
106
129
|
expect(addedPropertyProjection).toBeDefined();
|
|
107
|
-
expect(addedPropertyProjection
|
|
130
|
+
expect(addedPropertyProjection!.props.property).toBe('added_property');
|
|
108
131
|
});
|
|
109
132
|
|
|
110
133
|
test('delete property', async () => {
|
|
@@ -123,21 +146,26 @@ describe('ViewEditor', () => {
|
|
|
123
146
|
// Click the delete button
|
|
124
147
|
fireEvent.click(deleteButton);
|
|
125
148
|
|
|
126
|
-
//
|
|
127
|
-
|
|
149
|
+
// Wait for the changes to propagate
|
|
150
|
+
await waitFor(() => {
|
|
151
|
+
expect(screen.queryByText('name')).not.toBeInTheDocument();
|
|
152
|
+
});
|
|
128
153
|
|
|
129
|
-
|
|
154
|
+
// Access the live objects directly from window using symbol.
|
|
155
|
+
const debugObjects = getViewEditorDebugObjects();
|
|
130
156
|
|
|
131
157
|
// Check schema no longer contains the name property
|
|
132
|
-
const schemaProperties =
|
|
133
|
-
expect(schemaProperties
|
|
158
|
+
const schemaProperties = debugObjects.schema.jsonSchema.properties;
|
|
159
|
+
expect(schemaProperties).toBeDefined();
|
|
160
|
+
expect(schemaProperties!.name).toBeUndefined();
|
|
134
161
|
|
|
135
162
|
// Check view no longer contains the name field
|
|
136
|
-
const nameField =
|
|
163
|
+
const nameField = debugObjects.view.fields.find((field: any) => field.path === 'name');
|
|
137
164
|
expect(nameField).toBeUndefined();
|
|
138
165
|
|
|
139
166
|
// Check projection no longer contains the name property
|
|
140
|
-
const
|
|
167
|
+
const fieldProjections = debugObjects.projection.getFieldProjections();
|
|
168
|
+
const nameProjection = fieldProjections.find((proj: any) => proj.field.path === 'name');
|
|
141
169
|
expect(nameProjection).toBeUndefined();
|
|
142
170
|
});
|
|
143
171
|
|
|
@@ -150,17 +178,26 @@ describe('ViewEditor', () => {
|
|
|
150
178
|
fireEvent.click(hideButtons[0]);
|
|
151
179
|
|
|
152
180
|
// 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
181
|
await waitFor(() => {
|
|
158
182
|
expect(screen.getByTestId('show-field-button')).toBeInTheDocument();
|
|
159
183
|
});
|
|
184
|
+
|
|
185
|
+
// Access the live objects directly from window using symbol.
|
|
186
|
+
const debugObjects = getViewEditorDebugObjects();
|
|
187
|
+
|
|
188
|
+
// Check that hiddenFields is non-empty
|
|
189
|
+
expect(debugObjects.view.hiddenFields).toBeDefined();
|
|
190
|
+
expect(debugObjects.view.hiddenFields!.length).toBeGreaterThan(0);
|
|
191
|
+
|
|
192
|
+
// Click the show button
|
|
160
193
|
fireEvent.click(screen.getByTestId('show-field-button'));
|
|
161
194
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
195
|
+
// Wait for the data to update and check that hiddenFields is empty
|
|
196
|
+
await waitFor(() => {
|
|
197
|
+
expect(debugObjects.view.hiddenFields!.length).toBe(0);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Also verify that the field is back in the visible fields list
|
|
201
|
+
expect(debugObjects.view.fields.length).toBeGreaterThan(0);
|
|
165
202
|
});
|
|
166
203
|
});
|
|
@@ -153,7 +153,7 @@ export const ViewEditor = ({
|
|
|
153
153
|
return (
|
|
154
154
|
<div role='none' className={mx('overflow-y-auto', classNames)}>
|
|
155
155
|
{immutable && (
|
|
156
|
-
<div role='none' className='mbe-
|
|
156
|
+
<div role='none' className='mbe-card-spacing-block'>
|
|
157
157
|
<Message.Root valence='neutral' className='rounded'>
|
|
158
158
|
<Message.Title>
|
|
159
159
|
<Icon icon='ph--info--regular' size={5} classNames='inline' /> {t('system schema title')}
|
|
@@ -172,7 +172,7 @@ export const ViewEditor = ({
|
|
|
172
172
|
|
|
173
173
|
<div role='none' className='min-bs-0 overflow-y-auto'>
|
|
174
174
|
{/* TODO(burdon): Clean up common form ux. */}
|
|
175
|
-
<div role='none' className='
|
|
175
|
+
<div role='none' className='pli-card-spacing-inline mlb-card-spacing-block'>
|
|
176
176
|
<label className={mx(inputTextLabel)}>{t('fields label')}</label>
|
|
177
177
|
</div>
|
|
178
178
|
|
|
@@ -224,7 +224,7 @@ export const ViewEditor = ({
|
|
|
224
224
|
|
|
225
225
|
{hiddenProperties.length > 0 && (
|
|
226
226
|
<div>
|
|
227
|
-
<div role='none' className='
|
|
227
|
+
<div role='none' className='pli-card-spacing-inline mlb-card-spacing-block'>
|
|
228
228
|
<label className={mx(inputTextLabel)}>{t('hidden fields label')}</label>
|
|
229
229
|
</div>
|
|
230
230
|
|
|
@@ -269,7 +269,7 @@ export const ViewEditor = ({
|
|
|
269
269
|
)}
|
|
270
270
|
|
|
271
271
|
{!readonly && !field && (
|
|
272
|
-
<div role='none' className='
|
|
272
|
+
<div role='none' className='pli-card-spacing-chrome mlb-card-spacing-chrome'>
|
|
273
273
|
<IconButton
|
|
274
274
|
icon='ph--plus--regular'
|
|
275
275
|
label={t('button add property')}
|