@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.
@@ -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={{ schema, view, projection }}>
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
- expect(screen.getByTestId('debug')).toBeInTheDocument();
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
- // Assert.
45
- expect(screen.getByText('new_property')).toBeInTheDocument();
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
- const debugInfo = JSON.parse(screen.getByTestId('debug').textContent!);
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 = debugInfo.schema._storedSchema.jsonSchema.properties;
51
- expect(schemaProperties.new_property).toBeDefined();
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 = debugInfo.view.fields.find((field: any) => field.path === 'new_property');
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.path).toBe('new_property');
77
+ expect(newPropertyField!.path).toBe('new_property');
57
78
 
58
- // Check projection contains new_property.
59
- const newPropertyProjection = debugInfo.projection._fieldProjections.find(
60
- (proj: any) => proj.field.path === 'new_property',
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.props.property).toBe('new_property');
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
- // Assert the new property exists
89
- expect(screen.getByText('added_property')).toBeInTheDocument();
108
+ // Wait for the changes to propagate
109
+ await waitFor(() => {
110
+ expect(screen.getByText('added_property')).toBeInTheDocument();
111
+ });
90
112
 
91
- const debugInfo = JSON.parse(screen.getByTestId('debug').textContent!);
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 = debugInfo.schema._storedSchema.jsonSchema.properties;
95
- expect(schemaProperties.added_property).toBeDefined();
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 = debugInfo.view.fields.find((field: any) => field.path === 'added_property');
122
+ const addedPropertyField = debugObjects.view.fields.find((field: any) => field.path === 'added_property');
99
123
  expect(addedPropertyField).toBeDefined();
100
- expect(addedPropertyField.path).toBe('added_property');
124
+ expect(addedPropertyField!.path).toBe('added_property');
101
125
 
102
126
  // Check projection contains added_property
103
- const addedPropertyProjection = debugInfo.projection._fieldProjections.find(
104
- (proj: any) => proj.field.path === 'added_property',
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.props.property).toBe('added_property');
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
- // Assert the property is no longer visible
127
- expect(screen.queryByText('name')).not.toBeInTheDocument();
149
+ // Wait for the changes to propagate
150
+ await waitFor(() => {
151
+ expect(screen.queryByText('name')).not.toBeInTheDocument();
152
+ });
128
153
 
129
- const debugInfo = JSON.parse(screen.getByTestId('debug').textContent!);
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 = debugInfo.schema._storedSchema.jsonSchema.properties;
133
- expect(schemaProperties.name).toBeUndefined();
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 = debugInfo.view.fields.find((field: any) => field.path === 'name');
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 nameProjection = debugInfo.projection._fieldProjections.find((proj: any) => proj.field.path === 'name');
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
- // Check that hiddenFields is empty again
163
- debugInfo = JSON.parse(screen.getByTestId('debug').textContent!);
164
- expect(debugInfo.view.hiddenFields.length).toBe(0);
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-2'>
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='p-2'>
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='p-2'>
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='p-2'>
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')}