@object-ui/plugin-detail 3.1.1 → 3.1.2

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.
Files changed (48) hide show
  1. package/.turbo/turbo-build.log +40 -40
  2. package/CHANGELOG.md +10 -0
  3. package/dist/{AddressField-B1iVr404.js → AddressField-QBIlXCFl.js} +1 -1
  4. package/dist/{AvatarField-Duw4xOLZ.js → AvatarField-BEZuQTAH.js} +1 -1
  5. package/dist/{BooleanField-CZ4axVeq.js → BooleanField-doa93aFX.js} +1 -1
  6. package/dist/{CodeField-BSz-mk2v.js → CodeField-jVV-hIXg.js} +1 -1
  7. package/dist/{ColorField-B522ad8m.js → ColorField-B53qKQGW.js} +1 -1
  8. package/dist/{CurrencyField-Cwr3_pow.js → CurrencyField-og0NJ2ax.js} +1 -1
  9. package/dist/{DateField-DCo6dxud.js → DateField-BFx64AtG.js} +1 -1
  10. package/dist/{DateTimeField-BWfBuANO.js → DateTimeField-Cxs2Rx2f.js} +1 -1
  11. package/dist/{EmailField-CpwbdVCU.js → EmailField-BfcpzRe7.js} +1 -1
  12. package/dist/{FileField-DVAUAJ8e.js → FileField-KarqvhYm.js} +1 -1
  13. package/dist/{GeolocationField-DNCKitgo.js → GeolocationField-B5SKZaqn.js} +1 -1
  14. package/dist/{GridField-DSblZNfp.js → GridField-DOotrUTo.js} +1 -1
  15. package/dist/{ImageField-DBAlnMon.js → ImageField-Ddotp4u-.js} +1 -1
  16. package/dist/{LocationField-DsHsXA6R.js → LocationField-tOkQaPIM.js} +1 -1
  17. package/dist/{LookupField-CsT0QQz2.js → LookupField-DF36GvIP.js} +1 -1
  18. package/dist/{MasterDetailField-Db8b7Gqs.js → MasterDetailField-CpHw3nTE.js} +1 -1
  19. package/dist/{NumberField-0IGp7lcA.js → NumberField-CzBb2a28.js} +1 -1
  20. package/dist/{ObjectField-BLApgJtS.js → ObjectField-BoL-JqE4.js} +1 -1
  21. package/dist/{PasswordField-pHKyNlmo.js → PasswordField-DrTzkYgj.js} +1 -1
  22. package/dist/{PercentField-CwgKmlIb.js → PercentField-B9ZUQ3zE.js} +1 -1
  23. package/dist/{PhoneField-lKtbYOdN.js → PhoneField-Bf9lhpdu.js} +1 -1
  24. package/dist/{QRCodeField-BTTasT3w.js → QRCodeField-PzMpdBKd.js} +1 -1
  25. package/dist/{RatingField-De2X-l44.js → RatingField-CeBMFe8o.js} +1 -1
  26. package/dist/{RichTextField-B5QnvUOr.js → RichTextField-Ch7CHSQ0.js} +1 -1
  27. package/dist/{SelectField-C9AZRHWu.js → SelectField-f5Nbi02x.js} +1 -1
  28. package/dist/{SignatureField-BgcEmYzd.js → SignatureField-CpxTX2tR.js} +1 -1
  29. package/dist/{SliderField-BzrttVOY.js → SliderField-BoZtzgcr.js} +1 -1
  30. package/dist/{TextAreaField-DSE_CaU6.js → TextAreaField-rT1DLnV2.js} +1 -1
  31. package/dist/{TextField-DFQ4T9PR.js → TextField-CflRxusu.js} +1 -1
  32. package/dist/{TimeField-F0cfmsps.js → TimeField-DeVeCpRu.js} +1 -1
  33. package/dist/{UrlField-DLXrFIH-.js → UrlField-UWKfhP9T.js} +1 -1
  34. package/dist/{UserField-PXMmxJY9.js → UserField-Cp2zQDjz.js} +1 -1
  35. package/dist/index-V_WBvcaA.js +100249 -0
  36. package/dist/index.js +1 -1
  37. package/dist/index.umd.cjs +117 -46
  38. package/dist/plugin-detail.css +1 -1
  39. package/dist/src/DetailSection.d.ts +9 -0
  40. package/dist/src/DetailSection.d.ts.map +1 -1
  41. package/dist/src/DetailView.d.ts.map +1 -1
  42. package/package.json +6 -6
  43. package/src/DetailSection.tsx +42 -24
  44. package/src/DetailView.tsx +15 -8
  45. package/src/__tests__/DetailSection.test.tsx +110 -1
  46. package/src/__tests__/DetailView.test.tsx +31 -0
  47. package/dist/index-qQ1C-yUR.js +0 -59976
  48. package/src/registration.test.tsx +0 -18
@@ -1 +1 @@
1
- {"version":3,"file":"DetailSection.d.ts","sourceRoot":"","sources":["../../src/DetailSection.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAoB/B,OAAO,KAAK,EAAE,iBAAiB,IAAI,qBAAqB,EAAkC,MAAM,kBAAkB,CAAC;AAKnH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,qBAAqB,CAAC;IAC/B,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gEAAgE;IAChE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;CACrD;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA2OtD,CAAC"}
1
+ {"version":3,"file":"DetailSection.d.ts","sourceRoot":"","sources":["../../src/DetailSection.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAoB/B,OAAO,KAAK,EAAE,iBAAiB,IAAI,qBAAqB,EAAkC,MAAM,kBAAkB,CAAC;AAKnH;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAYxF;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,qBAAqB,CAAC;IAC/B,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gEAAgE;IAChE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;CACrD;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAuOtD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"DetailView.d.ts","sourceRoot":"","sources":["../../src/DetailView.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA4C/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMrE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,qDAAqD;IACrD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kDAAkD;IAClD,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChF;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAwuBhD,CAAC"}
1
+ {"version":3,"file":"DetailView.d.ts","sourceRoot":"","sources":["../../src/DetailView.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA4C/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMrE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,qDAAqD;IACrD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kDAAkD;IAClD,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChF;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA+uBhD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/plugin-detail",
3
- "version": "3.1.1",
3
+ "version": "3.1.2",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "DetailView plugin for Object UI - comprehensive detail page with sections, tabs, and related lists",
@@ -25,11 +25,11 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "lucide-react": "^0.576.0",
28
- "@object-ui/components": "3.1.1",
29
- "@object-ui/fields": "3.1.1",
30
- "@object-ui/core": "3.1.1",
31
- "@object-ui/react": "3.1.1",
32
- "@object-ui/types": "3.1.1"
28
+ "@object-ui/components": "3.1.2",
29
+ "@object-ui/core": "3.1.2",
30
+ "@object-ui/fields": "3.1.2",
31
+ "@object-ui/types": "3.1.2",
32
+ "@object-ui/react": "3.1.2"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "react": "^18.0.0 || ^19.0.0",
@@ -31,6 +31,28 @@ import { applyDetailAutoLayout } from './autoLayout';
31
31
  import { useDetailTranslation } from './useDetailTranslation';
32
32
  import { useSafeFieldLabel } from '@object-ui/react';
33
33
 
34
+ /**
35
+ * Compute responsive col-span classes so that col-span never exceeds the
36
+ * visible column count at each Tailwind breakpoint.
37
+ *
38
+ * For columns=1: no span class (always single column)
39
+ * For columns=2: md:col-span-{min(span,2)}
40
+ * For columns>=3: md:col-span-{min(span,2)} lg:col-span-{min(span,3)}
41
+ */
42
+ export function getResponsiveSpanClass(span: number | undefined, columns: number): string {
43
+ if (!span || span <= 1 || columns <= 1) return '';
44
+
45
+ if (columns === 2) {
46
+ return span >= 2 ? 'md:col-span-2' : '';
47
+ }
48
+
49
+ // columns >= 3: grid-cols-1 md:grid-cols-2 lg:grid-cols-3
50
+ if (span === 2) return 'md:col-span-2';
51
+ if (span >= 3) return 'md:col-span-2 lg:col-span-3';
52
+
53
+ return '';
54
+ }
55
+
34
56
  export interface DetailSectionProps {
35
57
  section: DetailViewSectionType;
36
58
  data?: any;
@@ -67,6 +89,23 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
67
89
  });
68
90
  }, []);
69
91
 
92
+ // Filter out empty fields when hideEmpty is set
93
+ const visibleFields = section.hideEmpty
94
+ ? section.fields.filter((field) => {
95
+ const value = data?.[field.name] ?? field.value;
96
+ return value !== null && value !== undefined && value !== '';
97
+ })
98
+ : section.fields;
99
+
100
+ // Hide entire section when all fields are empty
101
+ if (visibleFields.length === 0) return null;
102
+
103
+ // Apply auto-layout: infer columns and auto-span wide fields
104
+ const { fields: layoutFields, columns: effectiveColumns } = applyDetailAutoLayout(
105
+ visibleFields,
106
+ section.columns
107
+ );
108
+
70
109
  const renderField = (field: DetailViewField) => {
71
110
  const value = data?.[field.name] ?? field.value;
72
111
 
@@ -75,13 +114,9 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
75
114
  return <SchemaRenderer schema={field.render} data={{ ...data, value }} />;
76
115
  }
77
116
 
78
- // Calculate span class based on field.span value
79
- const spanClass = field.span === 1 ? 'col-span-1' :
80
- field.span === 2 ? 'col-span-2' :
81
- field.span === 3 ? 'col-span-3' :
82
- field.span === 4 ? 'col-span-4' :
83
- field.span === 5 ? 'col-span-5' :
84
- field.span === 6 ? 'col-span-6' : '';
117
+ // Calculate responsive span class so col-span never exceeds the visible
118
+ // column count at each breakpoint, preventing implicit columns on mobile.
119
+ const spanClass = getResponsiveSpanClass(field.span, effectiveColumns);
85
120
 
86
121
  const displayValue = (() => {
87
122
  if (value === null || value === undefined) return <span className="text-muted-foreground/50 text-xs italic">—</span>;
@@ -178,23 +213,6 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
178
213
  );
179
214
  };
180
215
 
181
- // Filter out empty fields when hideEmpty is set
182
- const visibleFields = section.hideEmpty
183
- ? section.fields.filter((field) => {
184
- const value = data?.[field.name] ?? field.value;
185
- return value !== null && value !== undefined && value !== '';
186
- })
187
- : section.fields;
188
-
189
- // Hide entire section when all fields are empty
190
- if (visibleFields.length === 0) return null;
191
-
192
- // Apply auto-layout: infer columns and auto-span wide fields
193
- const { fields: layoutFields, columns: effectiveColumns } = applyDetailAutoLayout(
194
- visibleFields,
195
- section.columns
196
- );
197
-
198
216
  const content = (
199
217
  <div
200
218
  className={cn(
@@ -130,14 +130,8 @@ export const DetailView: React.FC<DetailViewProps> = ({
130
130
  ? dataSource.findOne(objectName, resourceId, params)
131
131
  : dataSource.findOne(objectName, resourceId);
132
132
 
133
- return findOnePromise.then((result) => {
134
- if (!isMounted) return;
135
- if (result) {
136
- setData(result);
137
- setLoading(false);
138
- return;
139
- }
140
- // Fallback: try alternate ID format for backward compatibility
133
+ // Helper: try alternate ID format (strip or prepend objectName prefix)
134
+ const tryAltId = () => {
141
135
  const resIdStr = String(resourceId);
142
136
  const altId = resIdStr.startsWith(prefix)
143
137
  ? resIdStr.slice(prefix.length) // strip prefix
@@ -156,6 +150,19 @@ export const DetailView: React.FC<DetailViewProps> = ({
156
150
  setLoading(false);
157
151
  }
158
152
  });
153
+ };
154
+
155
+ return findOnePromise
156
+ .catch(() => null) // Convert any error to null to trigger alternate ID fallback
157
+ .then((result) => {
158
+ if (!isMounted) return;
159
+ if (result) {
160
+ setData(result);
161
+ setLoading(false);
162
+ return;
163
+ }
164
+ // Fallback: try alternate ID format for backward compatibility
165
+ return tryAltId();
159
166
  });
160
167
  }).catch((err) => {
161
168
  if (isMounted) {
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { describe, it, expect } from 'vitest';
10
10
  import { render, screen } from '@testing-library/react';
11
- import { DetailSection } from '../DetailSection';
11
+ import { DetailSection, getResponsiveSpanClass } from '../DetailSection';
12
12
 
13
13
  describe('DetailSection', () => {
14
14
  it('should render text fields as plain text', () => {
@@ -317,4 +317,113 @@ describe('DetailSection', () => {
317
317
  // Should use 'text' renderer, not 'number'
318
318
  expect(screen.getByText('Alice')).toBeInTheDocument();
319
319
  });
320
+
321
+ it('should use responsive span classes for wide fields in 3-column layout', () => {
322
+ const section = {
323
+ title: 'Wide Fields',
324
+ fields: Array.from({ length: 12 }, (_, i) => ({
325
+ name: `field_${i}`,
326
+ label: `Field ${i}`,
327
+ type: i === 5 ? 'textarea' : 'text',
328
+ })),
329
+ };
330
+ const { container } = render(
331
+ <DetailSection section={section} data={{}} />
332
+ );
333
+ const grid = container.querySelector('.grid');
334
+ expect(grid).toBeTruthy();
335
+ expect(grid!.className).toContain('lg:grid-cols-3');
336
+ // Wide field (textarea) should have responsive span, not bare col-span-3
337
+ const fields = container.querySelectorAll('[class*="col-span"]');
338
+ fields.forEach((field) => {
339
+ // No bare col-span-3 at base level — must be lg: prefixed
340
+ const classes = field.className.split(/\s+/);
341
+ const hasBareSpan3 = classes.some((c: string) => c === 'col-span-3');
342
+ expect(hasBareSpan3).toBe(false);
343
+ });
344
+ });
345
+
346
+ it('should use responsive span classes for wide fields in 2-column layout', () => {
347
+ const section = {
348
+ title: 'Wide Fields',
349
+ fields: [
350
+ { name: 'a', label: 'A', type: 'text' },
351
+ { name: 'b', label: 'B', type: 'text' },
352
+ { name: 'c', label: 'C', type: 'text' },
353
+ { name: 'd', label: 'D', type: 'text' },
354
+ { name: 'notes', label: 'Notes', type: 'textarea' },
355
+ ],
356
+ };
357
+ const { container } = render(
358
+ <DetailSection section={section} data={{}} />
359
+ );
360
+ const grid = container.querySelector('.grid');
361
+ expect(grid!.className).toContain('md:grid-cols-2');
362
+ // Wide field should have md:col-span-2, not bare col-span-2
363
+ const fields = container.querySelectorAll('[class*="col-span"]');
364
+ fields.forEach((field) => {
365
+ const classes = field.className.split(/\s+/);
366
+ const hasBareSpan2 = classes.some((c: string) => c === 'col-span-2');
367
+ expect(hasBareSpan2).toBe(false);
368
+ });
369
+ });
370
+
371
+ it('should not apply col-span at base breakpoint to prevent implicit grid columns on mobile', () => {
372
+ const section = {
373
+ title: 'Mobile Safe',
374
+ fields: Array.from({ length: 15 }, (_, i) => ({
375
+ name: `field_${i}`,
376
+ label: `Field ${i}`,
377
+ type: i === 0 ? 'textarea' : 'text',
378
+ })),
379
+ };
380
+ const { container } = render(
381
+ <DetailSection section={section} data={{}} />
382
+ );
383
+ // Ensure no bare col-span-N (N>1) classes without responsive prefix
384
+ const allElements = container.querySelectorAll('*');
385
+ allElements.forEach((el) => {
386
+ const classes = el.className?.split?.(/\s+/) || [];
387
+ classes.forEach((cls: string) => {
388
+ if (cls.match(/^col-span-[2-9]$/)) {
389
+ throw new Error(`Found bare "${cls}" class without responsive prefix — would break mobile single-column layout`);
390
+ }
391
+ });
392
+ });
393
+ });
394
+ });
395
+
396
+ describe('getResponsiveSpanClass', () => {
397
+ it('should return empty string for no span', () => {
398
+ expect(getResponsiveSpanClass(undefined, 2)).toBe('');
399
+ });
400
+
401
+ it('should return empty string for span=1', () => {
402
+ expect(getResponsiveSpanClass(1, 3)).toBe('');
403
+ });
404
+
405
+ it('should return empty string for 1-column layout', () => {
406
+ expect(getResponsiveSpanClass(3, 1)).toBe('');
407
+ });
408
+
409
+ it('should return md:col-span-2 for span=2 in 2-column layout', () => {
410
+ expect(getResponsiveSpanClass(2, 2)).toBe('md:col-span-2');
411
+ });
412
+
413
+ it('should cap span to 2 in 2-column layout', () => {
414
+ expect(getResponsiveSpanClass(3, 2)).toBe('md:col-span-2');
415
+ expect(getResponsiveSpanClass(6, 2)).toBe('md:col-span-2');
416
+ });
417
+
418
+ it('should return md:col-span-2 for span=2 in 3-column layout', () => {
419
+ expect(getResponsiveSpanClass(2, 3)).toBe('md:col-span-2');
420
+ });
421
+
422
+ it('should return responsive classes for span=3 in 3-column layout', () => {
423
+ expect(getResponsiveSpanClass(3, 3)).toBe('md:col-span-2 lg:col-span-3');
424
+ });
425
+
426
+ it('should cap span to 3 in 3-column layout', () => {
427
+ expect(getResponsiveSpanClass(6, 3)).toBe('md:col-span-2 lg:col-span-3');
428
+ });
320
429
  });
@@ -539,6 +539,37 @@ describe('DetailView', () => {
539
539
  expect(onBack).toHaveBeenCalled();
540
540
  });
541
541
 
542
+ it('should try fallback with alternate ID when first findOne throws an error', async () => {
543
+ let callCount = 0;
544
+ const mockDataSource = {
545
+ findOne: vi.fn().mockImplementation((_obj: string, id: string) => {
546
+ callCount++;
547
+ if (callCount === 1) {
548
+ // First call throws (simulate server error)
549
+ return Promise.reject(new Error('Server error'));
550
+ }
551
+ // Second call (fallback) succeeds
552
+ return Promise.resolve({ name: 'Alice' });
553
+ }),
554
+ } as any;
555
+
556
+ const schema: DetailViewSchema = {
557
+ type: 'detail-view',
558
+ title: 'Contact Details',
559
+ objectName: 'contact',
560
+ resourceId: 'contact-123',
561
+ fields: [{ name: 'name', label: 'Name' }],
562
+ };
563
+
564
+ const { findByText } = render(<DetailView schema={schema} dataSource={mockDataSource} />);
565
+ // The fallback should find the record using the stripped ID
566
+ expect(await findByText('Alice')).toBeInTheDocument();
567
+ // findOne should be called twice: first with original ID, then with stripped prefix
568
+ expect(mockDataSource.findOne).toHaveBeenCalledTimes(2);
569
+ expect(mockDataSource.findOne).toHaveBeenNthCalledWith(1, 'contact', 'contact-123');
570
+ expect(mockDataSource.findOne).toHaveBeenNthCalledWith(2, 'contact', '123');
571
+ });
572
+
542
573
  it('should call findOne with $expand when objectSchema has lookup fields', async () => {
543
574
  const mockDataSource = {
544
575
  getObjectSchema: vi.fn().mockResolvedValue({