Skip to content

Commit 6f9eb8a

Browse files
Fix component test failures by re-registering UI components in vitest.setup.tsx
Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
1 parent eac398e commit 6f9eb8a

2 files changed

Lines changed: 355 additions & 24 deletions

File tree

packages/components/src/__tests__/debug.test.tsx

Lines changed: 0 additions & 20 deletions
This file was deleted.

vitest.setup.tsx

Lines changed: 355 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@ import '@object-ui/fields'; // Register field widgets
1515
import '@object-ui/plugin-dashboard'; // Register dashboard components
1616
import '@object-ui/plugin-grid'; // Register grid components
1717

18-
// Manually re-register basic text component to override field widget
19-
// This is necessary because @object-ui/fields has @object-ui/components as a dependency,
20-
// so components gets loaded BEFORE fields registers its widgets, causing fields to overwrite.
18+
// Manually re-register UI components to override field widgets and plugin components
19+
// This is necessary because @object-ui/fields and plugins have @object-ui/components as a dependency,
20+
// so components gets loaded BEFORE fields/plugins register their widgets, causing them to overwrite.
2121
import { ComponentRegistry } from '@object-ui/core';
22-
import type { TextSchema } from '@object-ui/types';
22+
import type { TextSchema, InputSchema, TextareaSchema, ImageSchema, HtmlSchema, AvatarSchema, SelectSchema, SliderSchema, GridSchema } from '@object-ui/types';
23+
import { Input, Textarea, Label, Avatar, AvatarImage, AvatarFallback, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Slider } from '@object-ui/components';
24+
import { cn, renderChildren } from '@object-ui/components';
2325

26+
// Re-register text component
2427
ComponentRegistry.register('text',
2528
({ schema, ...props }: { schema: TextSchema; [key: string]: any }) => {
2629
const {
@@ -52,6 +55,354 @@ ComponentRegistry.register('text',
5255
}
5356
);
5457

58+
// Re-register email component
59+
const InputRenderer = ({ schema, className, onChange, value, ...props }: { schema: InputSchema; className?: string; onChange?: (val: any) => void; value?: any; [key: string]: any }) => {
60+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
61+
if (onChange) {
62+
onChange(e.target.value);
63+
}
64+
};
65+
66+
const {
67+
'data-obj-id': dataObjId,
68+
'data-obj-type': dataObjType,
69+
style,
70+
...inputProps
71+
} = props;
72+
73+
return (
74+
<div
75+
className={cn("grid w-full items-center gap-1.5", schema.wrapperClass)}
76+
data-obj-id={dataObjId}
77+
data-obj-type={dataObjType}
78+
style={style}
79+
>
80+
{schema.label && <Label htmlFor={schema.id} className={cn(schema.required && "text-destructive after:content-['*'] after:ml-0.5")}>{schema.label}</Label>}
81+
<Input
82+
type={schema.inputType || 'text'}
83+
id={schema.id}
84+
name={schema.name}
85+
placeholder={schema.placeholder}
86+
className={className}
87+
required={schema.required}
88+
disabled={schema.disabled}
89+
readOnly={schema.readOnly}
90+
value={value ?? schema.value ?? ''}
91+
defaultValue={value === undefined ? schema.defaultValue : undefined}
92+
onChange={handleChange}
93+
{...inputProps}
94+
/>
95+
</div>
96+
);
97+
};
98+
99+
ComponentRegistry.register('email',
100+
(props: any) => <InputRenderer {...props} schema={{ ...props.schema, inputType: 'email' }} />,
101+
{
102+
namespace: 'ui',
103+
label: 'Email Input',
104+
}
105+
);
106+
107+
// Re-register password component
108+
ComponentRegistry.register('password',
109+
(props: any) => <InputRenderer {...props} schema={{ ...props.schema, inputType: 'password' }} />,
110+
{
111+
namespace: 'ui',
112+
label: 'Password Input',
113+
}
114+
);
115+
116+
// Re-register textarea component
117+
ComponentRegistry.register('textarea',
118+
({ schema, className, onChange, value, ...props }: { schema: TextareaSchema; className?: string; onChange?: (val: any) => void; value?: any; [key: string]: any }) => {
119+
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
120+
if (onChange) {
121+
onChange(e.target.value);
122+
}
123+
};
124+
125+
const {
126+
'data-obj-id': dataObjId,
127+
'data-obj-type': dataObjType,
128+
style,
129+
...inputProps
130+
} = props;
131+
132+
return (
133+
<div
134+
className={cn("grid w-full gap-1.5", schema.wrapperClass)}
135+
data-obj-id={dataObjId}
136+
data-obj-type={dataObjType}
137+
style={style}
138+
>
139+
{schema.label && <Label htmlFor={schema.id} className={cn(schema.required && "text-destructive after:content-['*'] after:ml-0.5")}>{schema.label}</Label>}
140+
<Textarea
141+
id={schema.id}
142+
name={schema.name}
143+
placeholder={schema.placeholder}
144+
className={className}
145+
disabled={schema.disabled}
146+
readOnly={schema.readOnly}
147+
required={schema.required}
148+
rows={schema.rows}
149+
value={value ?? schema.value ?? ''}
150+
defaultValue={value === undefined ? schema.defaultValue : undefined}
151+
onChange={handleChange}
152+
{...inputProps}
153+
/>
154+
</div>
155+
);
156+
},
157+
{
158+
namespace: 'ui',
159+
label: 'Textarea',
160+
}
161+
);
162+
163+
// Re-register image component
164+
ComponentRegistry.register('image',
165+
({ schema, className, ...props }: { schema: ImageSchema; className?: string; [key: string]: any }) => {
166+
const {
167+
'data-obj-id': dataObjId,
168+
'data-obj-type': dataObjType,
169+
style,
170+
...imgProps
171+
} = props;
172+
173+
return (
174+
<img
175+
src={schema.src}
176+
alt={schema.alt || ''}
177+
className={className}
178+
{...imgProps}
179+
data-obj-id={dataObjId}
180+
data-obj-type={dataObjType}
181+
style={style}
182+
/>
183+
);
184+
},
185+
{
186+
namespace: 'ui',
187+
label: 'Image',
188+
}
189+
);
190+
191+
// Re-register html component
192+
ComponentRegistry.register('html',
193+
({ schema, className, ...props }: { schema: HtmlSchema; className?: string; [key: string]: any }) => {
194+
const {
195+
'data-obj-id': dataObjId,
196+
'data-obj-type': dataObjType,
197+
style,
198+
...htmlProps
199+
} = props;
200+
201+
return (
202+
<div
203+
className={cn("prose prose-sm max-w-none dark:prose-invert", className)}
204+
dangerouslySetInnerHTML={{ __html: schema.html }}
205+
{...htmlProps}
206+
data-obj-id={dataObjId}
207+
data-obj-type={dataObjType}
208+
style={style}
209+
/>
210+
);
211+
},
212+
{
213+
namespace: 'ui',
214+
label: 'HTML Content',
215+
}
216+
);
217+
218+
// Re-register avatar component
219+
ComponentRegistry.register('avatar',
220+
({ schema, className, ...props }: { schema: AvatarSchema; className?: string; [key: string]: any }) => (
221+
<Avatar className={className} {...props}>
222+
<AvatarImage src={schema.src} alt={schema.alt} />
223+
<AvatarFallback>{schema.fallback}</AvatarFallback>
224+
</Avatar>
225+
),
226+
{
227+
namespace: 'ui',
228+
label: 'Avatar',
229+
}
230+
);
231+
232+
// Re-register select component
233+
ComponentRegistry.register('select',
234+
({ schema, className, onChange, value, ...props }: { schema: SelectSchema; className?: string; onChange?: (val: any) => void; value?: any; [key: string]: any }) => {
235+
const {
236+
'data-obj-id': dataObjId,
237+
'data-obj-type': dataObjType,
238+
style,
239+
...selectProps
240+
} = props;
241+
242+
const handleValueChange = (newValue: string) => {
243+
if (onChange) {
244+
onChange(newValue);
245+
}
246+
};
247+
248+
return (
249+
<div
250+
className={cn("grid w-full items-center gap-1.5", schema.wrapperClass)}
251+
data-obj-id={dataObjId}
252+
data-obj-type={dataObjType}
253+
style={style}
254+
>
255+
{schema.label && <Label className={cn(schema.required && "text-destructive after:content-['*'] after:ml-0.5")}>{schema.label}</Label>}
256+
<Select
257+
defaultValue={value === undefined ? schema.defaultValue : undefined}
258+
value={value ?? schema.value}
259+
onValueChange={handleValueChange}
260+
disabled={schema.disabled}
261+
required={schema.required}
262+
name={schema.name}
263+
{...selectProps}
264+
>
265+
<SelectTrigger className={className}>
266+
<SelectValue placeholder={schema.placeholder} />
267+
</SelectTrigger>
268+
<SelectContent>
269+
{schema.options?.map((opt) => (
270+
<SelectItem key={opt.value} value={opt.value} disabled={opt.disabled}>{opt.label}</SelectItem>
271+
))}
272+
</SelectContent>
273+
</Select>
274+
</div>
275+
);
276+
},
277+
{
278+
namespace: 'ui',
279+
label: 'Select',
280+
}
281+
);
282+
283+
// Re-register slider component
284+
ComponentRegistry.register('slider',
285+
({ schema, className, ...props }: { schema: SliderSchema; className?: string; [key: string]: any }) => {
286+
const {
287+
'data-obj-id': dataObjId,
288+
'data-obj-type': dataObjType,
289+
style,
290+
...sliderProps
291+
} = props;
292+
293+
const defaultValue = Array.isArray(schema.defaultValue)
294+
? schema.defaultValue
295+
: schema.defaultValue !== undefined
296+
? [schema.defaultValue]
297+
: undefined;
298+
299+
return (
300+
<Slider
301+
defaultValue={defaultValue}
302+
max={schema.max}
303+
min={schema.min}
304+
step={schema.step}
305+
className={className}
306+
{...sliderProps}
307+
{...{ 'data-obj-id': dataObjId, 'data-obj-type': dataObjType, style }}
308+
/>
309+
);
310+
},
311+
{
312+
namespace: 'ui',
313+
label: 'Slider',
314+
}
315+
);
316+
317+
// Re-register grid component to override plugin-grid's registration
318+
ComponentRegistry.register('grid',
319+
({ schema, className, ...props }: { schema: GridSchema & { smColumns?: number, mdColumns?: number, lgColumns?: number, xlColumns?: number }; className?: string; [key: string]: any }) => {
320+
let baseCols = 2;
321+
let smCols, mdCols, lgCols, xlCols;
322+
323+
if (typeof schema.columns === 'number') {
324+
baseCols = schema.columns;
325+
} else if (typeof schema.columns === 'object' && schema.columns !== null) {
326+
baseCols = schema.columns.xs ?? 1;
327+
smCols = schema.columns.sm;
328+
mdCols = schema.columns.md;
329+
lgCols = schema.columns.lg;
330+
xlCols = schema.columns.xl;
331+
}
332+
333+
if (schema.smColumns) smCols = schema.smColumns;
334+
if (schema.mdColumns) mdCols = schema.mdColumns;
335+
if (schema.lgColumns) lgCols = schema.lgColumns;
336+
if (schema.xlColumns) xlCols = schema.xlColumns;
337+
338+
const gap = schema.gap ?? 4;
339+
340+
const GRID_COLS: Record<number, string> = {
341+
1: 'grid-cols-1', 2: 'grid-cols-2', 3: 'grid-cols-3', 4: 'grid-cols-4',
342+
5: 'grid-cols-5', 6: 'grid-cols-6', 7: 'grid-cols-7', 8: 'grid-cols-8',
343+
9: 'grid-cols-9', 10: 'grid-cols-10', 11: 'grid-cols-11', 12: 'grid-cols-12'
344+
};
345+
const GRID_COLS_SM: Record<number, string> = {
346+
1: 'sm:grid-cols-1', 2: 'sm:grid-cols-2', 3: 'sm:grid-cols-3', 4: 'sm:grid-cols-4',
347+
5: 'sm:grid-cols-5', 6: 'sm:grid-cols-6', 7: 'sm:grid-cols-7', 8: 'sm:grid-cols-8',
348+
9: 'sm:grid-cols-9', 10: 'sm:grid-cols-10', 11: 'sm:grid-cols-11', 12: 'sm:grid-cols-12'
349+
};
350+
const GRID_COLS_MD: Record<number, string> = {
351+
1: 'md:grid-cols-1', 2: 'md:grid-cols-2', 3: 'md:grid-cols-3', 4: 'md:grid-cols-4',
352+
5: 'md:grid-cols-5', 6: 'md:grid-cols-6', 7: 'md:grid-cols-7', 8: 'md:grid-cols-8',
353+
9: 'md:grid-cols-9', 10: 'md:grid-cols-10', 11: 'md:grid-cols-11', 12: 'md:grid-cols-12'
354+
};
355+
const GRID_COLS_LG: Record<number, string> = {
356+
1: 'lg:grid-cols-1', 2: 'lg:grid-cols-2', 3: 'lg:grid-cols-3', 4: 'lg:grid-cols-4',
357+
5: 'lg:grid-cols-5', 6: 'lg:grid-cols-6', 7: 'lg:grid-cols-7', 8: 'lg:grid-cols-8',
358+
9: 'lg:grid-cols-9', 10: 'lg:grid-cols-10', 11: 'lg:grid-cols-11', 12: 'lg:grid-cols-12'
359+
};
360+
const GRID_COLS_XL: Record<number, string> = {
361+
1: 'xl:grid-cols-1', 2: 'xl:grid-cols-2', 3: 'xl:grid-cols-3', 4: 'xl:grid-cols-4',
362+
5: 'xl:grid-cols-5', 6: 'xl:grid-cols-6', 7: 'xl:grid-cols-7', 8: 'xl:grid-cols-8',
363+
9: 'xl:grid-cols-9', 10: 'xl:grid-cols-10', 11: 'xl:grid-cols-11', 12: 'xl:grid-cols-12'
364+
};
365+
const GAPS: Record<number, string> = {
366+
0: 'gap-0', 1: 'gap-1', 2: 'gap-2', 3: 'gap-3', 4: 'gap-4',
367+
5: 'gap-5', 6: 'gap-6', 8: 'gap-8', 10: 'gap-10', 12: 'gap-12'
368+
};
369+
370+
const gridClass = cn(
371+
'grid',
372+
GRID_COLS[baseCols] || 'grid-cols-2',
373+
smCols && GRID_COLS_SM[smCols],
374+
mdCols && GRID_COLS_MD[mdCols],
375+
lgCols && GRID_COLS_LG[lgCols],
376+
xlCols && GRID_COLS_XL[xlCols],
377+
GAPS[gap] || `gap-[${gap * 0.25}rem]`,
378+
className
379+
);
380+
381+
const {
382+
'data-obj-id': dataObjId,
383+
'data-obj-type': dataObjType,
384+
style,
385+
...gridProps
386+
} = props;
387+
388+
return (
389+
<div
390+
className={gridClass}
391+
{...gridProps}
392+
data-obj-id={dataObjId}
393+
data-obj-type={dataObjType}
394+
style={style}
395+
>
396+
{schema.children && renderChildren(schema.children)}
397+
</div>
398+
);
399+
},
400+
{
401+
namespace: 'ui',
402+
label: 'Grid Layout',
403+
}
404+
);
405+
55406
// Polyfill ResizeObserver for Radix UI (Shadcn) components
56407
global.ResizeObserver = class ResizeObserver {
57408
observe() {}

0 commit comments

Comments
 (0)