@object-ui/react 3.1.5 → 3.3.1

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 (109) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +28 -3
  3. package/dist/SchemaRenderer.js +2 -2
  4. package/dist/components/form/FieldFactory.js +5 -2
  5. package/dist/context/AppShellContext.d.ts +26 -0
  6. package/dist/context/AppShellContext.js +67 -0
  7. package/dist/context/index.d.ts +1 -7
  8. package/dist/context/index.js +1 -7
  9. package/dist/hooks/index.d.ts +1 -0
  10. package/dist/hooks/index.js +1 -0
  11. package/dist/hooks/useDataRefresh.d.ts +36 -0
  12. package/dist/hooks/useDataRefresh.js +50 -0
  13. package/dist/hooks/useDiscovery.d.ts +12 -0
  14. package/dist/hooks/useDiscovery.js +6 -0
  15. package/dist/hooks/usePerformanceBudget.js +1 -1
  16. package/package.json +32 -9
  17. package/.turbo/turbo-build.log +0 -4
  18. package/src/LazyPluginLoader.tsx +0 -174
  19. package/src/SchemaRenderer.tsx +0 -266
  20. package/src/__tests__/LazyPluginLoader.test.tsx +0 -176
  21. package/src/__tests__/SchemaRenderer.aria.test.tsx +0 -118
  22. package/src/__tests__/SchemaRenderer.debug.test.tsx +0 -69
  23. package/src/__tests__/SchemaRenderer.errorBoundary.test.tsx +0 -114
  24. package/src/__tests__/SchemaRenderer.expressions.test.tsx +0 -131
  25. package/src/__tests__/SchemaRendererProvider.smoke.test.tsx +0 -132
  26. package/src/__tests__/ThemeProvider.test.tsx +0 -451
  27. package/src/__tests__/useNavigationOverlay.test.ts +0 -578
  28. package/src/__tests__/useOffline.test.ts +0 -198
  29. package/src/__tests__/usePageTransition.test.ts +0 -214
  30. package/src/__tests__/usePageVariables.test.tsx +0 -334
  31. package/src/__tests__/usePerformance.test.ts +0 -137
  32. package/src/__tests__/useViewData.test.tsx +0 -422
  33. package/src/__tests__/view-switching.test.tsx +0 -456
  34. package/src/components/form/FieldFactory.tsx +0 -509
  35. package/src/components/form/FormRenderer.test.tsx +0 -744
  36. package/src/components/form/FormRenderer.tsx +0 -258
  37. package/src/components/form/README.md +0 -352
  38. package/src/components/form/index.ts +0 -12
  39. package/src/context/ActionContext.tsx +0 -213
  40. package/src/context/DndContext.tsx +0 -260
  41. package/src/context/NotificationContext.tsx +0 -280
  42. package/src/context/SchemaRendererContext.tsx +0 -48
  43. package/src/context/ThemeContext.tsx +0 -325
  44. package/src/context/__tests__/ActionContext.test.tsx +0 -174
  45. package/src/context/__tests__/DndContext.test.tsx +0 -150
  46. package/src/context/__tests__/NotificationContext.test.tsx +0 -176
  47. package/src/context/__tests__/useDataScope.test.tsx +0 -91
  48. package/src/context/index.ts +0 -13
  49. package/src/hooks/__tests__/useActionEngine.test.ts +0 -170
  50. package/src/hooks/__tests__/useActionRunner.test.ts +0 -153
  51. package/src/hooks/__tests__/useAnimation.test.ts +0 -82
  52. package/src/hooks/__tests__/useColumnSummary.test.ts +0 -93
  53. package/src/hooks/__tests__/useCrudShortcuts.test.ts +0 -85
  54. package/src/hooks/__tests__/useDebugMode.test.ts +0 -83
  55. package/src/hooks/__tests__/useDensityMode.test.ts +0 -81
  56. package/src/hooks/__tests__/useDiscovery.test.tsx +0 -149
  57. package/src/hooks/__tests__/useDynamicApp.test.ts +0 -161
  58. package/src/hooks/__tests__/useETagCache.test.ts +0 -153
  59. package/src/hooks/__tests__/useExpression.test.ts +0 -86
  60. package/src/hooks/__tests__/useFocusTrap.test.ts +0 -294
  61. package/src/hooks/__tests__/useKeyboardShortcuts.test.ts +0 -144
  62. package/src/hooks/__tests__/usePageTransition.test.ts +0 -158
  63. package/src/hooks/__tests__/usePerformanceBudget.test.ts +0 -157
  64. package/src/hooks/__tests__/useReducedMotion.test.ts +0 -104
  65. package/src/hooks/__tests__/useSchemaPersistence.test.ts +0 -220
  66. package/src/hooks/__tests__/useViewSharing.test.ts +0 -121
  67. package/src/hooks/__tests__/useViewTransition.test.ts +0 -61
  68. package/src/hooks/index.ts +0 -35
  69. package/src/hooks/useActionEngine.ts +0 -84
  70. package/src/hooks/useActionRunner.ts +0 -119
  71. package/src/hooks/useAnimation.ts +0 -117
  72. package/src/hooks/useClientNotifications.ts +0 -251
  73. package/src/hooks/useColumnSummary.ts +0 -87
  74. package/src/hooks/useCrudShortcuts.ts +0 -126
  75. package/src/hooks/useDebugMode.ts +0 -95
  76. package/src/hooks/useDensityMode.ts +0 -102
  77. package/src/hooks/useDiscovery.ts +0 -154
  78. package/src/hooks/useDynamicApp.ts +0 -142
  79. package/src/hooks/useETagCache.ts +0 -404
  80. package/src/hooks/useExpression.ts +0 -56
  81. package/src/hooks/useFocusManagement.ts +0 -248
  82. package/src/hooks/useFocusTrap.ts +0 -138
  83. package/src/hooks/useGlobalUndo.ts +0 -98
  84. package/src/hooks/useKeyboardShortcuts.ts +0 -119
  85. package/src/hooks/useNavigationOverlay.ts +0 -190
  86. package/src/hooks/useOffline.ts +0 -301
  87. package/src/hooks/usePageTransition.ts +0 -187
  88. package/src/hooks/usePageVariables.tsx +0 -157
  89. package/src/hooks/usePerformance.ts +0 -246
  90. package/src/hooks/usePerformanceBudget.ts +0 -313
  91. package/src/hooks/useReducedMotion.ts +0 -53
  92. package/src/hooks/useSchemaPersistence.ts +0 -222
  93. package/src/hooks/useViewData.ts +0 -201
  94. package/src/hooks/useViewSharing.ts +0 -140
  95. package/src/hooks/useViewTransition.ts +0 -144
  96. package/src/index.ts +0 -41
  97. package/src/spec-bridge/SpecBridge.ts +0 -64
  98. package/src/spec-bridge/__tests__/P1SpecBridge.test.ts +0 -855
  99. package/src/spec-bridge/__tests__/SpecBridge.test.ts +0 -550
  100. package/src/spec-bridge/bridges/dashboard.ts +0 -180
  101. package/src/spec-bridge/bridges/form-view.ts +0 -109
  102. package/src/spec-bridge/bridges/list-view.ts +0 -187
  103. package/src/spec-bridge/bridges/page.ts +0 -136
  104. package/src/spec-bridge/index.ts +0 -14
  105. package/src/spec-bridge/types.ts +0 -38
  106. package/src/utils/i18n.ts +0 -10
  107. package/tsconfig.json +0 -17
  108. package/tsconfig.tsbuildinfo +0 -1
  109. package/vitest.config.ts +0 -2
@@ -1,266 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import React, { forwardRef, useContext, useMemo, Component } from 'react';
10
- import {
11
- SchemaNode,
12
- ComponentRegistry,
13
- ExpressionEvaluator,
14
- isObjectUIError,
15
- type ObjectUIError,
16
- ERROR_CODES,
17
- debugLog,
18
- debugTime,
19
- debugTimeEnd,
20
- DebugCollector,
21
- } from '@object-ui/core';
22
- import { SchemaRendererContext } from './context/SchemaRendererContext';
23
- import { resolveI18nLabel } from './utils/i18n';
24
-
25
- /**
26
- * Extract AriaPropsSchema properties from a schema node and convert
27
- * them to standard HTML ARIA attributes.
28
- *
29
- * @objectstack/spec AriaPropsSchema defines:
30
- * ariaLabel: string | I18nLabel (→ aria-label)
31
- * ariaDescribedBy: string (→ aria-describedby)
32
- * role: string (→ role)
33
- */
34
- function resolveAriaProps(schema: Record<string, any>): Record<string, string | undefined> {
35
- const aria: Record<string, string | undefined> = {};
36
- if (schema.ariaLabel) {
37
- aria['aria-label'] = resolveI18nLabel(schema.ariaLabel);
38
- }
39
- if (schema.ariaDescribedBy) {
40
- aria['aria-describedby'] = schema.ariaDescribedBy;
41
- }
42
- if (schema.role) {
43
- aria['role'] = schema.role;
44
- }
45
- return aria;
46
- }
47
-
48
- /**
49
- * Per-component Error Boundary for SchemaRenderer.
50
- * Catches render errors in individual components, preventing one broken
51
- * component from crashing the entire page.
52
- */
53
- interface SchemaErrorBoundaryState {
54
- hasError: boolean;
55
- error: Error | null;
56
- }
57
-
58
- export class SchemaErrorBoundary extends Component<
59
- { componentType?: string; children: React.ReactNode },
60
- SchemaErrorBoundaryState
61
- > {
62
- state: SchemaErrorBoundaryState = { hasError: false, error: null };
63
-
64
- static getDerivedStateFromError(error: Error): SchemaErrorBoundaryState {
65
- return { hasError: true, error };
66
- }
67
-
68
- handleRetry = () => {
69
- this.setState({ hasError: false, error: null });
70
- };
71
-
72
- render() {
73
- if (this.state.hasError && this.state.error) {
74
- const error = this.state.error;
75
- const isDev = process.env.NODE_ENV !== 'production';
76
- const objuiError = isObjectUIError(error) ? error as ObjectUIError : null;
77
-
78
- return (
79
- <div className="p-4 border border-orange-400 rounded bg-orange-50 text-orange-700 my-2" role="alert">
80
- <p className="font-medium">
81
- Component{this.props.componentType ? ` "${this.props.componentType}"` : ''} failed to render
82
- </p>
83
- <p className="text-sm mt-1">{error.message}</p>
84
- {isDev && objuiError?.code && (
85
- <p className="text-xs mt-1 text-orange-500">
86
- Error code: {objuiError.code}
87
- {objuiError.details?.suggestion ? (
88
- <span className="block mt-0.5">💡 {String(objuiError.details.suggestion)}</span>
89
- ) : null}
90
- </p>
91
- )}
92
- <button
93
- onClick={this.handleRetry}
94
- className="mt-2 text-sm underline hover:no-underline"
95
- >
96
- Retry
97
- </button>
98
- </div>
99
- );
100
- }
101
- return this.props.children;
102
- }
103
- }
104
-
105
- export const SchemaRenderer = forwardRef<any, { schema: SchemaNode } & Record<string, any>>(({ schema, ...props }, _ref) => {
106
- const context = useContext(SchemaRendererContext);
107
- const dataSource = context?.dataSource || {};
108
-
109
- // Evaluate schema expressions against the data source
110
- const evaluatedSchema = useMemo(() => {
111
- if (!schema || typeof schema === 'string') return schema;
112
-
113
- const evaluator = new ExpressionEvaluator({ data: dataSource });
114
- // Shallow copy
115
- const newSchema = { ...schema };
116
-
117
- // COMPAT: Hoist 'properties' up to schema level
118
- // This allows support for strict configs that wrap all props in 'properties'
119
- if (newSchema.properties) {
120
- Object.assign(newSchema, newSchema.properties);
121
- }
122
-
123
- // Evaluate 'content' (common in Text, Button)
124
- if (typeof newSchema.content === 'string') {
125
- newSchema.content = evaluator.evaluate(newSchema.content);
126
- }
127
-
128
- // Evaluate 'props'
129
- if (newSchema.props) {
130
- const newProps = { ...newSchema.props };
131
- for (const [key, val] of Object.entries(newProps)) {
132
- newProps[key] = evaluator.evaluate(val as any);
133
- }
134
- newSchema.props = newProps;
135
- }
136
-
137
- // Evaluate visibility: visible / visibleOn / hidden / hiddenOn
138
- const shouldHide = (() => {
139
- if (newSchema.visible !== undefined) {
140
- return !evaluator.evaluateCondition(newSchema.visible);
141
- }
142
- if (newSchema.visibleOn !== undefined) {
143
- return !evaluator.evaluateCondition(newSchema.visibleOn);
144
- }
145
- if (newSchema.hidden !== undefined) {
146
- return evaluator.evaluateCondition(newSchema.hidden);
147
- }
148
- if (newSchema.hiddenOn !== undefined) {
149
- return evaluator.evaluateCondition(newSchema.hiddenOn);
150
- }
151
- return false;
152
- })();
153
-
154
- if (shouldHide) {
155
- newSchema._hidden = true;
156
- }
157
-
158
- // Evaluate disabled: disabled / disabledOn
159
- const isDisabled = (() => {
160
- if (newSchema.disabled !== undefined) {
161
- return evaluator.evaluateCondition(newSchema.disabled);
162
- }
163
- if (newSchema.disabledOn !== undefined) {
164
- return evaluator.evaluateCondition(newSchema.disabledOn);
165
- }
166
- return false;
167
- })();
168
-
169
- if (isDisabled) {
170
- newSchema._disabled = true;
171
- }
172
-
173
- return newSchema;
174
- }, [schema, dataSource]);
175
-
176
- if (!evaluatedSchema) return null;
177
- // Handle visibility: if evaluated schema is hidden, render nothing
178
- if (evaluatedSchema?._hidden) return null;
179
- // If schema is just a string, render it as text
180
- if (typeof evaluatedSchema === 'string') return <>{evaluatedSchema}</>;
181
-
182
- debugLog('schema', 'Rendering schema node', { type: evaluatedSchema.type, id: evaluatedSchema.id });
183
-
184
- const Component = ComponentRegistry.get(evaluatedSchema.type);
185
-
186
- if (!Component) {
187
- debugLog('schema', 'Component not found in registry', { type: evaluatedSchema.type });
188
- const errorInfo = ERROR_CODES['OBJUI-001'];
189
- return (
190
- <div className="p-4 border border-red-500 rounded text-red-500 bg-red-50 my-2" role="alert">
191
- <p className="font-medium">Unknown component type: <strong>{evaluatedSchema.type}</strong></p>
192
- {process.env.NODE_ENV !== 'production' && (
193
- <p className="text-xs mt-1">💡 {errorInfo.suggestion} (OBJUI-001)</p>
194
- )}
195
- <pre className="text-xs mt-2 overflow-auto">{JSON.stringify(evaluatedSchema, null, 2)}</pre>
196
- </div>
197
- );
198
- }
199
-
200
- // Note: We don't forward the ref to the Component because components in the registry
201
- // may not support refs. The SchemaRenderer itself can still receive refs for its own use.
202
-
203
- // Extract schema metadata properties that should NOT be passed as React props
204
- const {
205
- type: _type,
206
- children: _children,
207
- body: _body,
208
- schema: _schema,
209
- visible: _visible,
210
- visibleOn: _visibleOn,
211
- hidden: _hidden,
212
- hiddenOn: _hiddenOn,
213
- disabled: _disabled,
214
- disabledOn: _disabledOn,
215
- _hidden: __hidden, // stripped: internal visibility flag
216
- _disabled: __disabled, // stripped: internal disabled flag
217
- ...componentProps
218
- } = evaluatedSchema;
219
-
220
- // Extract AriaPropsSchema properties for accessibility
221
- const ariaProps = resolveAriaProps(evaluatedSchema);
222
-
223
- // Debug-mode enhancements: extra data attributes + perf tracking
224
- const isDebug = context?.debug || context?.debugFlags?.enabled;
225
- const debugAttrs: Record<string, string> = {};
226
- if (isDebug) {
227
- debugAttrs['data-debug-type'] = evaluatedSchema.type;
228
- if (evaluatedSchema.id) {
229
- debugAttrs['data-debug-id'] = evaluatedSchema.id;
230
- }
231
- }
232
-
233
- debugTime(`render:${evaluatedSchema.type}:${evaluatedSchema.id ?? 'anon'}`);
234
- const renderStart = isDebug ? performance.now() : 0;
235
- const rendered = (
236
- <SchemaErrorBoundary componentType={evaluatedSchema.type}>
237
- {React.createElement(Component, {
238
- schema: evaluatedSchema,
239
- ...componentProps, // Spread non-metadata schema properties as props
240
- ...(evaluatedSchema.props || {}), // Override with explicit props if provided
241
- ...ariaProps, // Inject ARIA attributes from AriaPropsSchema
242
- ...debugAttrs, // Debug-mode data attributes
243
- disabled: __disabled || undefined,
244
- className: evaluatedSchema.className,
245
- 'data-obj-id': evaluatedSchema.id,
246
- 'data-obj-type': evaluatedSchema.type,
247
- ...props
248
- })}
249
- </SchemaErrorBoundary>
250
- );
251
- debugTimeEnd(`render:${evaluatedSchema.type}:${evaluatedSchema.id ?? 'anon'}`);
252
-
253
- // Report render perf to DebugCollector when debug mode is active
254
- if (isDebug && renderStart) {
255
- const durationMs = performance.now() - renderStart;
256
- DebugCollector.getInstance().addPerf({
257
- type: evaluatedSchema.type,
258
- id: evaluatedSchema.id,
259
- durationMs,
260
- timestamp: Date.now(),
261
- });
262
- }
263
-
264
- return rendered;
265
- });
266
- SchemaRenderer.displayName = 'SchemaRenderer';
@@ -1,176 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import { describe, it, expect, vi } from 'vitest';
10
- import { render, screen, waitFor } from '@testing-library/react';
11
- import React from 'react';
12
- import { createLazyPlugin, preloadPlugin } from '../LazyPluginLoader';
13
-
14
- describe('createLazyPlugin', () => {
15
- it('should create a lazy-loaded component', async () => {
16
- const TestComponent = () => <div>Test Component</div>;
17
-
18
- const LazyComponent = createLazyPlugin(
19
- () => Promise.resolve({ default: TestComponent })
20
- );
21
-
22
- render(<LazyComponent />);
23
-
24
- await waitFor(() => {
25
- expect(screen.getByText('Test Component')).toBeInTheDocument();
26
- });
27
- });
28
-
29
- it('should show custom fallback while loading', async () => {
30
- const TestComponent = () => <div>Test Component</div>;
31
-
32
- const LazyComponent = createLazyPlugin(
33
- () => new Promise<{ default: React.ComponentType }>((resolve) => {
34
- setTimeout(() => resolve({ default: TestComponent }), 100);
35
- }),
36
- { fallback: <div>Loading...</div> }
37
- );
38
-
39
- render(<LazyComponent />);
40
-
41
- expect(screen.getByText('Loading...')).toBeInTheDocument();
42
-
43
- await waitFor(() => {
44
- expect(screen.getByText('Test Component')).toBeInTheDocument();
45
- });
46
- });
47
-
48
- it('should pass props to the lazy component', async () => {
49
- interface TestProps {
50
- message: string;
51
- }
52
-
53
- const TestComponent = ({ message }: TestProps) => <div>{message}</div>;
54
-
55
- const LazyComponent = createLazyPlugin<TestProps>(
56
- () => Promise.resolve({ default: TestComponent })
57
- );
58
-
59
- render(<LazyComponent message="Hello World" />);
60
-
61
- await waitFor(() => {
62
- expect(screen.getByText('Hello World')).toBeInTheDocument();
63
- });
64
- });
65
-
66
- it('should handle loading errors gracefully', async () => {
67
- // Mock console.error to avoid noise in test output
68
- const originalError = console.error;
69
- console.error = vi.fn();
70
-
71
- const LazyComponent = createLazyPlugin<any>(
72
- () => Promise.reject(new Error('Load error'))
73
- );
74
-
75
- expect(() => render(<LazyComponent />)).not.toThrow();
76
-
77
- // Restore console.error
78
- console.error = originalError;
79
- });
80
-
81
- it('should preload a plugin without rendering', async () => {
82
- const TestComponent = () => <div>Preloaded</div>;
83
- const importFn = vi.fn(() => Promise.resolve({ default: TestComponent }));
84
-
85
- const result = await preloadPlugin(importFn);
86
-
87
- expect(importFn).toHaveBeenCalledTimes(1);
88
- expect(result).toEqual({ default: TestComponent });
89
- });
90
-
91
- it('should render errorFallback when component fails to load', async () => {
92
- const originalError = console.error;
93
- console.error = vi.fn();
94
-
95
- const ErrorFallback = ({ error, retry }: { error: Error; retry: () => void }) => (
96
- <div>
97
- <span>Error: {error.message}</span>
98
- <button onClick={retry}>Retry</button>
99
- </div>
100
- );
101
-
102
- const LazyComponent = createLazyPlugin(
103
- () => Promise.reject(new Error('chunk failed')),
104
- { retries: 0, errorFallback: ErrorFallback }
105
- );
106
-
107
- render(<LazyComponent />);
108
-
109
- await waitFor(() => {
110
- expect(screen.getByText('Error: chunk failed')).toBeInTheDocument();
111
- });
112
-
113
- expect(screen.getByRole('button', { name: 'Retry' })).toBeInTheDocument();
114
-
115
- console.error = originalError;
116
- });
117
-
118
- it('should not retry when retries=0', async () => {
119
- const originalError = console.error;
120
- console.error = vi.fn();
121
-
122
- const importFn = vi.fn(() => Promise.reject(new Error('fail')));
123
-
124
- const ErrorFallback = ({ error }: { error: Error; retry: () => void }) => (
125
- <div>Error: {error.message}</div>
126
- );
127
-
128
- const LazyComponent = createLazyPlugin(importFn, {
129
- retries: 0,
130
- errorFallback: ErrorFallback,
131
- });
132
-
133
- render(<LazyComponent />);
134
-
135
- await waitFor(() => {
136
- expect(screen.getByText('Error: fail')).toBeInTheDocument();
137
- });
138
-
139
- // Called only once — no retries
140
- expect(importFn).toHaveBeenCalledTimes(1);
141
-
142
- console.error = originalError;
143
- });
144
-
145
- it('should retry on failure then succeed', async () => {
146
- const originalError = console.error;
147
- console.error = vi.fn();
148
-
149
- const TestComponent = () => <div>Success after retry</div>;
150
-
151
- let callCount = 0;
152
- const importFn = vi.fn(() => {
153
- callCount++;
154
- if (callCount <= 2) {
155
- return Promise.reject(new Error('transient error'));
156
- }
157
- return Promise.resolve({ default: TestComponent as React.ComponentType });
158
- });
159
-
160
- const LazyComponent = createLazyPlugin(importFn, {
161
- retries: 3,
162
- retryDelay: 10,
163
- });
164
-
165
- render(<LazyComponent />);
166
-
167
- await waitFor(() => {
168
- expect(screen.getByText('Success after retry')).toBeInTheDocument();
169
- }, { timeout: 2000 });
170
-
171
- // First call + 2 retries (fails) + 1 success = called 3 times
172
- expect(importFn).toHaveBeenCalledTimes(3);
173
-
174
- console.error = originalError;
175
- });
176
- });
@@ -1,118 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
10
- import { render, screen } from '@testing-library/react';
11
- import React from 'react';
12
- import { ComponentRegistry } from '@object-ui/core';
13
- import { SchemaRenderer } from '../SchemaRenderer';
14
-
15
- // A simple test component that forwards ARIA attributes
16
- const TestWidget: React.FC<any> = (props) => (
17
- <div
18
- data-testid="test-widget"
19
- aria-label={props['aria-label']}
20
- aria-describedby={props['aria-describedby']}
21
- role={props['role']}
22
- >
23
- {props.content || 'Test'}
24
- </div>
25
- );
26
-
27
- describe('SchemaRenderer AriaProps injection', () => {
28
- beforeEach(() => {
29
- ComponentRegistry.register('test-widget', TestWidget);
30
- });
31
-
32
- afterEach(() => {
33
- ComponentRegistry.unregister?.('test-widget');
34
- });
35
-
36
- it('should inject aria-label from ariaLabel string', () => {
37
- render(
38
- <SchemaRenderer
39
- schema={{
40
- type: 'test-widget',
41
- ariaLabel: 'Close dialog',
42
- }}
43
- />
44
- );
45
- const el = screen.getByTestId('test-widget');
46
- expect(el).toHaveAttribute('aria-label', 'Close dialog');
47
- });
48
-
49
- it('should resolve ariaLabel from I18nLabel object', () => {
50
- render(
51
- <SchemaRenderer
52
- schema={{
53
- type: 'test-widget',
54
- ariaLabel: { key: 'dialog.close', defaultValue: 'Close dialog' },
55
- }}
56
- />
57
- );
58
- const el = screen.getByTestId('test-widget');
59
- expect(el).toHaveAttribute('aria-label', 'Close dialog');
60
- });
61
-
62
- it('should inject aria-describedby from ariaDescribedBy', () => {
63
- render(
64
- <SchemaRenderer
65
- schema={{
66
- type: 'test-widget',
67
- ariaDescribedBy: 'help-text-1',
68
- }}
69
- />
70
- );
71
- const el = screen.getByTestId('test-widget');
72
- expect(el).toHaveAttribute('aria-describedby', 'help-text-1');
73
- });
74
-
75
- it('should inject role from schema', () => {
76
- render(
77
- <SchemaRenderer
78
- schema={{
79
- type: 'test-widget',
80
- role: 'navigation',
81
- }}
82
- />
83
- );
84
- const el = screen.getByTestId('test-widget');
85
- expect(el).toHaveAttribute('role', 'navigation');
86
- });
87
-
88
- it('should inject all ARIA props together', () => {
89
- render(
90
- <SchemaRenderer
91
- schema={{
92
- type: 'test-widget',
93
- ariaLabel: 'Main nav',
94
- ariaDescribedBy: 'nav-desc',
95
- role: 'navigation',
96
- }}
97
- />
98
- );
99
- const el = screen.getByTestId('test-widget');
100
- expect(el).toHaveAttribute('aria-label', 'Main nav');
101
- expect(el).toHaveAttribute('aria-describedby', 'nav-desc');
102
- expect(el).toHaveAttribute('role', 'navigation');
103
- });
104
-
105
- it('should not inject ARIA attrs when not in schema', () => {
106
- render(
107
- <SchemaRenderer
108
- schema={{
109
- type: 'test-widget',
110
- content: 'Hello',
111
- }}
112
- />
113
- );
114
- const el = screen.getByTestId('test-widget');
115
- expect(el).not.toHaveAttribute('aria-label');
116
- expect(el).not.toHaveAttribute('aria-describedby');
117
- });
118
- });
@@ -1,69 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
10
- import { render } from '@testing-library/react';
11
- import React from 'react';
12
- import { ComponentRegistry } from '@object-ui/core';
13
- import { SchemaRenderer } from '../SchemaRenderer';
14
- import { SchemaRendererProvider } from '../context/SchemaRendererContext';
15
-
16
- // Suppress console.warn from deprecated namespace registration
17
- const originalWarn = console.warn;
18
- beforeEach(() => {
19
- console.warn = vi.fn();
20
- });
21
- afterEach(() => {
22
- console.warn = originalWarn;
23
- });
24
-
25
- // A simple test component that passes all props through to a div
26
- const TestDiv: React.FC<any> = (props) => {
27
- const { schema, ...rest } = props;
28
- return <div data-testid="test-div" {...rest} />;
29
- };
30
-
31
- describe('SchemaRenderer debug attributes', () => {
32
- beforeEach(() => {
33
- ComponentRegistry.register('test-debug-div', TestDiv);
34
- });
35
-
36
- it('should NOT inject data-debug-* attributes when debug is off', () => {
37
- const { getByTestId } = render(
38
- <SchemaRendererProvider dataSource={{}}>
39
- <SchemaRenderer schema={{ type: 'test-debug-div', id: 'myBtn' }} />
40
- </SchemaRendererProvider>,
41
- );
42
- const el = getByTestId('test-div');
43
- expect(el.getAttribute('data-debug-type')).toBeNull();
44
- expect(el.getAttribute('data-debug-id')).toBeNull();
45
- });
46
-
47
- it('should inject data-debug-type when debug is enabled', () => {
48
- const { getByTestId } = render(
49
- <SchemaRendererProvider dataSource={{}} debug={true}>
50
- <SchemaRenderer schema={{ type: 'test-debug-div', id: 'myBtn' }} />
51
- </SchemaRendererProvider>,
52
- );
53
- const el = getByTestId('test-div');
54
- expect(el.getAttribute('data-debug-type')).toBe('test-debug-div');
55
- expect(el.getAttribute('data-debug-id')).toBe('myBtn');
56
- });
57
-
58
- it('should inject data-debug-type when debugFlags.enabled is true', () => {
59
- const { getByTestId } = render(
60
- <SchemaRendererProvider dataSource={{}} debugFlags={{ enabled: true }}>
61
- <SchemaRenderer schema={{ type: 'test-debug-div' }} />
62
- </SchemaRendererProvider>,
63
- );
64
- const el = getByTestId('test-div');
65
- expect(el.getAttribute('data-debug-type')).toBe('test-debug-div');
66
- // No id on schema, so data-debug-id should not be present
67
- expect(el.getAttribute('data-debug-id')).toBeNull();
68
- });
69
- });