@object-ui/components 3.3.0 → 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.
- package/CHANGELOG.md +20 -0
- package/README.md +21 -1
- package/dist/index.css +6339 -2
- package/dist/index.js +17600 -17481
- package/dist/index.umd.cjs +36 -36
- package/dist/packages/components/src/custom/empty.d.ts +12 -1
- package/dist/packages/components/src/renderers/action/action-bar.d.ts +12 -1
- package/dist/packages/components/src/ui/chart.d.ts +10 -29
- package/package.json +65 -44
- package/.turbo/turbo-build.log +0 -84
- package/README_SHADCN_SYNC.md +0 -281
- package/TESTING.md +0 -335
- package/docs/FilterBuilder.md +0 -268
- package/metadata/Chart.component.yml +0 -30
- package/metadata/FilterBuilder.component.yml +0 -39
- package/metadata/GridLayout.component.yml +0 -27
- package/metadata/Menu.component.yml +0 -31
- package/metadata/ObjectForm.component.yml +0 -34
- package/metadata/ObjectGrid.component.yml +0 -72
- package/metadata/Page.component.yml +0 -24
- package/postcss.config.js +0 -14
- package/shadcn-components.json +0 -440
- package/src/SchemaRenderer.tsx +0 -28
- package/src/__tests__/PageRendererRegions.test.tsx +0 -668
- package/src/__tests__/README.md +0 -124
- package/src/__tests__/__snapshots__/snapshot-critical.test.tsx.snap +0 -811
- package/src/__tests__/__snapshots__/snapshot.test.tsx.snap +0 -327
- package/src/__tests__/accessibility.test.tsx +0 -137
- package/src/__tests__/action-bar.test.tsx +0 -206
- package/src/__tests__/api-consistency.test.tsx +0 -596
- package/src/__tests__/basic-renderers.test.tsx +0 -255
- package/src/__tests__/color-contrast.test.tsx +0 -212
- package/src/__tests__/complex-disclosure-renderers.test.tsx +0 -302
- package/src/__tests__/compliance.test.tsx +0 -72
- package/src/__tests__/config-field-renderer.test.tsx +0 -307
- package/src/__tests__/config-panel-renderer.test.tsx +0 -580
- package/src/__tests__/config-primitives.test.tsx +0 -106
- package/src/__tests__/edge-cases.test.tsx +0 -285
- package/src/__tests__/feedback-overlay-renderers.test.tsx +0 -349
- package/src/__tests__/filter-builder.test.tsx +0 -409
- package/src/__tests__/form-renderers.test.tsx +0 -364
- package/src/__tests__/layout-data-renderers.test.tsx +0 -340
- package/src/__tests__/mobile-accessibility.test.tsx +0 -120
- package/src/__tests__/navigation-overlay.test.tsx +0 -370
- package/src/__tests__/snapshot-critical.test.tsx +0 -317
- package/src/__tests__/snapshot.test.tsx +0 -205
- package/src/__tests__/test-utils.tsx +0 -190
- package/src/__tests__/use-config-draft.test.tsx +0 -295
- package/src/__tests__/view-compliance.test.tsx +0 -153
- package/src/__tests__/wcag-audit.test.tsx +0 -493
- package/src/custom/action-param-dialog.tsx +0 -264
- package/src/custom/button-group.tsx +0 -91
- package/src/custom/combobox.tsx +0 -104
- package/src/custom/config-field-renderer.tsx +0 -276
- package/src/custom/config-panel-renderer.tsx +0 -306
- package/src/custom/config-row.tsx +0 -50
- package/src/custom/date-picker.tsx +0 -61
- package/src/custom/empty.tsx +0 -112
- package/src/custom/field.tsx +0 -81
- package/src/custom/filter-builder.tsx +0 -418
- package/src/custom/index.ts +0 -21
- package/src/custom/input-group.tsx +0 -53
- package/src/custom/item.tsx +0 -201
- package/src/custom/kbd.tsx +0 -36
- package/src/custom/mobile-dialog-content.tsx +0 -67
- package/src/custom/native-select.tsx +0 -33
- package/src/custom/navigation-overlay.tsx +0 -334
- package/src/custom/section-header.tsx +0 -68
- package/src/custom/sort-builder.tsx +0 -129
- package/src/custom/spinner.tsx +0 -26
- package/src/custom/view-skeleton.tsx +0 -243
- package/src/custom/view-states.tsx +0 -153
- package/src/debug/DebugPanel.tsx +0 -313
- package/src/debug/__tests__/DebugPanel.test.tsx +0 -134
- package/src/debug/index.ts +0 -10
- package/src/hooks/use-config-draft.ts +0 -127
- package/src/hooks/use-mobile.tsx +0 -27
- package/src/index.css +0 -245
- package/src/index.ts +0 -47
- package/src/lib/use-sync-external-store-shim.ts +0 -10
- package/src/lib/use-sync-external-store-with-selector-shim.ts +0 -90
- package/src/lib/utils.tsx +0 -35
- package/src/new-components.test.ts +0 -73
- package/src/renderers/action/action-bar.tsx +0 -221
- package/src/renderers/action/action-button.tsx +0 -158
- package/src/renderers/action/action-group.tsx +0 -270
- package/src/renderers/action/action-icon.tsx +0 -150
- package/src/renderers/action/action-menu.tsx +0 -203
- package/src/renderers/action/index.ts +0 -19
- package/src/renderers/action/resolve-icon.ts +0 -35
- package/src/renderers/basic/button-group.tsx +0 -79
- package/src/renderers/basic/div.tsx +0 -60
- package/src/renderers/basic/html.tsx +0 -43
- package/src/renderers/basic/icon.tsx +0 -89
- package/src/renderers/basic/image.tsx +0 -49
- package/src/renderers/basic/index.ts +0 -18
- package/src/renderers/basic/navigation-menu.tsx +0 -81
- package/src/renderers/basic/pagination.tsx +0 -109
- package/src/renderers/basic/separator.tsx +0 -57
- package/src/renderers/basic/span.tsx +0 -63
- package/src/renderers/basic/text.tsx +0 -52
- package/src/renderers/complex/README-KANBAN.md +0 -208
- package/src/renderers/complex/TIMELINE.md +0 -353
- package/src/renderers/complex/__tests__/data-table-airtable-ux.test.tsx +0 -239
- package/src/renderers/complex/__tests__/data-table-batch-editing.test.tsx +0 -275
- package/src/renderers/complex/__tests__/data-table-cell-renderer.test.tsx +0 -120
- package/src/renderers/complex/__tests__/data-table-editing.test.tsx +0 -221
- package/src/renderers/complex/__tests__/data-table.test.ts +0 -76
- package/src/renderers/complex/carousel.tsx +0 -69
- package/src/renderers/complex/data-table.tsx +0 -1243
- package/src/renderers/complex/filter-builder.tsx +0 -77
- package/src/renderers/complex/index.ts +0 -16
- package/src/renderers/complex/resizable.tsx +0 -66
- package/src/renderers/complex/scroll-area.tsx +0 -58
- package/src/renderers/complex/table.tsx +0 -95
- package/src/renderers/data-display/alert.tsx +0 -46
- package/src/renderers/data-display/avatar.tsx +0 -38
- package/src/renderers/data-display/badge.tsx +0 -55
- package/src/renderers/data-display/breadcrumb.tsx +0 -61
- package/src/renderers/data-display/index.ts +0 -18
- package/src/renderers/data-display/kbd.tsx +0 -50
- package/src/renderers/data-display/list.tsx +0 -75
- package/src/renderers/data-display/statistic.tsx +0 -95
- package/src/renderers/data-display/table.tsx +0 -78
- package/src/renderers/data-display/tree-view.tsx +0 -176
- package/src/renderers/disclosure/accordion.tsx +0 -69
- package/src/renderers/disclosure/collapsible.tsx +0 -53
- package/src/renderers/disclosure/index.ts +0 -11
- package/src/renderers/disclosure/toggle-group.tsx +0 -79
- package/src/renderers/feedback/empty.tsx +0 -49
- package/src/renderers/feedback/index.ts +0 -16
- package/src/renderers/feedback/loading.tsx +0 -78
- package/src/renderers/feedback/progress.tsx +0 -29
- package/src/renderers/feedback/skeleton.tsx +0 -31
- package/src/renderers/feedback/sonner.tsx +0 -56
- package/src/renderers/feedback/spinner.tsx +0 -55
- package/src/renderers/feedback/toast.tsx +0 -59
- package/src/renderers/feedback/toaster.tsx +0 -23
- package/src/renderers/form/button.tsx +0 -103
- package/src/renderers/form/calendar.tsx +0 -34
- package/src/renderers/form/checkbox.tsx +0 -71
- package/src/renderers/form/combobox.tsx +0 -48
- package/src/renderers/form/command.tsx +0 -58
- package/src/renderers/form/date-picker.tsx +0 -84
- package/src/renderers/form/file-upload.tsx +0 -184
- package/src/renderers/form/form.tsx +0 -540
- package/src/renderers/form/index.ts +0 -26
- package/src/renderers/form/input-otp.tsx +0 -51
- package/src/renderers/form/input.tsx +0 -121
- package/src/renderers/form/label.tsx +0 -45
- package/src/renderers/form/radio-group.tsx +0 -63
- package/src/renderers/form/select.tsx +0 -94
- package/src/renderers/form/slider.tsx +0 -61
- package/src/renderers/form/switch.tsx +0 -48
- package/src/renderers/form/textarea.tsx +0 -76
- package/src/renderers/form/toggle.tsx +0 -42
- package/src/renderers/index.ts +0 -18
- package/src/renderers/layout/aspect-ratio.tsx +0 -51
- package/src/renderers/layout/card.tsx +0 -85
- package/src/renderers/layout/container.tsx +0 -122
- package/src/renderers/layout/flex.tsx +0 -132
- package/src/renderers/layout/grid.tsx +0 -178
- package/src/renderers/layout/index.ts +0 -19
- package/src/renderers/layout/page.tsx +0 -466
- package/src/renderers/layout/semantic.tsx +0 -48
- package/src/renderers/layout/stack.tsx +0 -132
- package/src/renderers/layout/tabs.tsx +0 -97
- package/src/renderers/navigation/header-bar.tsx +0 -118
- package/src/renderers/navigation/index.ts +0 -10
- package/src/renderers/navigation/sidebar.tsx +0 -208
- package/src/renderers/overlay/alert-dialog.tsx +0 -72
- package/src/renderers/overlay/context-menu.tsx +0 -100
- package/src/renderers/overlay/dialog.tsx +0 -77
- package/src/renderers/overlay/drawer.tsx +0 -77
- package/src/renderers/overlay/dropdown-menu.tsx +0 -99
- package/src/renderers/overlay/hover-card.tsx +0 -55
- package/src/renderers/overlay/index.ts +0 -18
- package/src/renderers/overlay/menubar.tsx +0 -76
- package/src/renderers/overlay/popover.tsx +0 -56
- package/src/renderers/overlay/sheet.tsx +0 -77
- package/src/renderers/overlay/tooltip.tsx +0 -67
- package/src/renderers/placeholders.tsx +0 -107
- package/src/stories/CRMApp.stories.tsx +0 -706
- package/src/stories/ConfigPanel.stories.tsx +0 -232
- package/src/stories/Guide.mdx +0 -55
- package/src/stories/MockedData.stories.tsx +0 -121
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +0 -1
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/avif-test-image.avif +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +0 -1
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +0 -1
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +0 -1
- package/src/stories/assets/youtube.svg +0 -1
- package/src/stories/button.css +0 -30
- package/src/stories/header.css +0 -32
- package/src/stories/page.css +0 -68
- package/src/stories-json/Accessibility.mdx +0 -297
- package/src/stories-json/EdgeCases.stories.tsx +0 -160
- package/src/stories-json/GettingStarted.mdx +0 -89
- package/src/stories-json/Introduction.mdx +0 -127
- package/src/stories-json/accordion.stories.tsx +0 -43
- package/src/stories-json/aggrid.stories.tsx +0 -103
- package/src/stories-json/alert.stories.tsx +0 -39
- package/src/stories-json/aspect-ratio.stories.tsx +0 -34
- package/src/stories-json/avatar.stories.tsx +0 -38
- package/src/stories-json/badge.stories.tsx +0 -53
- package/src/stories-json/breadcrumb.stories.tsx +0 -30
- package/src/stories-json/button-group.stories.tsx +0 -43
- package/src/stories-json/button.stories.tsx +0 -73
- package/src/stories-json/calendar.stories.tsx +0 -85
- package/src/stories-json/card.stories.tsx +0 -48
- package/src/stories-json/carousel.stories.tsx +0 -33
- package/src/stories-json/charts.stories.tsx +0 -195
- package/src/stories-json/chatbot.stories.tsx +0 -349
- package/src/stories-json/code-editor.stories.tsx +0 -92
- package/src/stories-json/collapsible.stories.tsx +0 -40
- package/src/stories-json/controls.stories.tsx +0 -36
- package/src/stories-json/crm-live-data.stories.tsx +0 -154
- package/src/stories-json/dashboard.stories.tsx +0 -318
- package/src/stories-json/data-table.stories.tsx +0 -136
- package/src/stories-json/data_display_extras.stories.tsx +0 -102
- package/src/stories-json/date-picker.stories.tsx +0 -28
- package/src/stories-json/detail-view.stories.tsx +0 -258
- package/src/stories-json/dialog.stories.tsx +0 -43
- package/src/stories-json/feedback_extras.stories.tsx +0 -40
- package/src/stories-json/feedback_others.stories.tsx +0 -46
- package/src/stories-json/form-variants.stories.tsx +0 -210
- package/src/stories-json/form_advanced.stories.tsx +0 -117
- package/src/stories-json/form_extras.stories.tsx +0 -123
- package/src/stories-json/grid.stories.tsx +0 -56
- package/src/stories-json/icon.stories.tsx +0 -36
- package/src/stories-json/input.stories.tsx +0 -52
- package/src/stories-json/kanban.stories.tsx +0 -295
- package/src/stories-json/layout_extended.stories.tsx +0 -76
- package/src/stories-json/layout_flex.stories.tsx +0 -107
- package/src/stories-json/list-view.stories.tsx +0 -97
- package/src/stories-json/markdown.stories.tsx +0 -129
- package/src/stories-json/menus.stories.tsx +0 -63
- package/src/stories-json/metric-card.stories.tsx +0 -143
- package/src/stories-json/navigation-menu.stories.tsx +0 -37
- package/src/stories-json/object-aggrid-advanced.stories.tsx +0 -389
- package/src/stories-json/object-aggrid.stories.tsx +0 -252
- package/src/stories-json/object-form.stories.tsx +0 -130
- package/src/stories-json/object-gantt.stories.tsx +0 -114
- package/src/stories-json/object-grid.stories.tsx +0 -315
- package/src/stories-json/object-map.stories.tsx +0 -116
- package/src/stories-json/object-view.stories.tsx +0 -118
- package/src/stories-json/overlay_extras.stories.tsx +0 -113
- package/src/stories-json/overlay_others.stories.tsx +0 -76
- package/src/stories-json/page.stories.tsx +0 -55
- package/src/stories-json/reports.stories.tsx +0 -163
- package/src/stories-json/resizable.stories.tsx +0 -44
- package/src/stories-json/select.stories.tsx +0 -34
- package/src/stories-json/separator.stories.tsx +0 -41
- package/src/stories-json/sidebar.stories.tsx +0 -147
- package/src/stories-json/statistic.stories.tsx +0 -44
- package/src/stories-json/tabs.stories.tsx +0 -51
- package/src/stories-json/timeline.stories.tsx +0 -188
- package/src/stories-json/typography.stories.tsx +0 -45
- package/src/types/config-panel.ts +0 -101
- package/src/ui/accordion.tsx +0 -66
- package/src/ui/alert-dialog.tsx +0 -149
- package/src/ui/alert.tsx +0 -67
- package/src/ui/aspect-ratio.tsx +0 -15
- package/src/ui/avatar.tsx +0 -58
- package/src/ui/badge.tsx +0 -44
- package/src/ui/breadcrumb.tsx +0 -123
- package/src/ui/button.tsx +0 -64
- package/src/ui/calendar.tsx +0 -221
- package/src/ui/card.tsx +0 -87
- package/src/ui/carousel.tsx +0 -270
- package/src/ui/chart.tsx +0 -377
- package/src/ui/checkbox.tsx +0 -38
- package/src/ui/collapsible.tsx +0 -19
- package/src/ui/command.tsx +0 -161
- package/src/ui/context-menu.tsx +0 -208
- package/src/ui/dialog.tsx +0 -130
- package/src/ui/drawer.tsx +0 -126
- package/src/ui/dropdown-menu.tsx +0 -208
- package/src/ui/form.tsx +0 -186
- package/src/ui/hover-card.tsx +0 -37
- package/src/ui/index.ts +0 -56
- package/src/ui/input-otp.tsx +0 -79
- package/src/ui/input.tsx +0 -30
- package/src/ui/label.tsx +0 -34
- package/src/ui/menubar.tsx +0 -264
- package/src/ui/navigation-menu.tsx +0 -136
- package/src/ui/pagination.tsx +0 -125
- package/src/ui/popover.tsx +0 -39
- package/src/ui/progress.tsx +0 -36
- package/src/ui/radio-group.tsx +0 -52
- package/src/ui/resizable.tsx +0 -53
- package/src/ui/scroll-area.tsx +0 -56
- package/src/ui/select.tsx +0 -168
- package/src/ui/separator.tsx +0 -39
- package/src/ui/sheet.tsx +0 -150
- package/src/ui/sidebar.tsx +0 -781
- package/src/ui/skeleton.tsx +0 -23
- package/src/ui/slider.tsx +0 -39
- package/src/ui/sonner.tsx +0 -53
- package/src/ui/switch.tsx +0 -37
- package/src/ui/table.tsx +0 -125
- package/src/ui/tabs.tsx +0 -63
- package/src/ui/textarea.tsx +0 -30
- package/src/ui/toast.tsx +0 -137
- package/src/ui/toggle-group.tsx +0 -69
- package/src/ui/toggle.tsx +0 -53
- package/src/ui/tooltip.tsx +0 -38
- package/src/ui/typography.tsx +0 -85
- package/tsconfig.json +0 -19
- package/vite.config.ts +0 -71
- package/vitest.config.ts +0 -5
|
@@ -1,190 +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 { render, RenderOptions } from '@testing-library/react';
|
|
10
|
-
import { ComponentRegistry } from '@object-ui/core';
|
|
11
|
-
import type { SchemaNode } from '@object-ui/types';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Test utility for rendering components from schema
|
|
15
|
-
*/
|
|
16
|
-
export function renderComponent(schema: SchemaNode, options?: RenderOptions) {
|
|
17
|
-
const Component = ComponentRegistry.get(schema.type);
|
|
18
|
-
|
|
19
|
-
if (!Component) {
|
|
20
|
-
throw new Error(`Component "${schema.type}" is not registered`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return render(<Component schema={schema} />, options);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Check if a component has proper accessibility attributes
|
|
28
|
-
*/
|
|
29
|
-
export function checkAccessibility(element: HTMLElement): {
|
|
30
|
-
hasRole: boolean;
|
|
31
|
-
hasAriaLabel: boolean;
|
|
32
|
-
hasAriaDescribedBy: boolean;
|
|
33
|
-
issues: string[];
|
|
34
|
-
} {
|
|
35
|
-
const issues: string[] = [];
|
|
36
|
-
const hasRole = element.hasAttribute('role');
|
|
37
|
-
const hasAriaLabel = element.hasAttribute('aria-label') || element.hasAttribute('aria-labelledby');
|
|
38
|
-
const hasAriaDescribedBy = element.hasAttribute('aria-describedby');
|
|
39
|
-
|
|
40
|
-
// Check for interactive elements without labels
|
|
41
|
-
if (
|
|
42
|
-
element.tagName === 'BUTTON' ||
|
|
43
|
-
element.tagName === 'A' ||
|
|
44
|
-
element.getAttribute('role') === 'button'
|
|
45
|
-
) {
|
|
46
|
-
if (!element.textContent?.trim() && !hasAriaLabel) {
|
|
47
|
-
issues.push('Interactive element missing accessible label');
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Check for form inputs without labels
|
|
52
|
-
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA' || element.tagName === 'SELECT') {
|
|
53
|
-
const id = element.getAttribute('id');
|
|
54
|
-
const doc = element.ownerDocument || document;
|
|
55
|
-
const hasAssociatedLabel =
|
|
56
|
-
!!element.closest('label') ||
|
|
57
|
-
(!!id && !!doc.querySelector(`label[for="${id}"]`));
|
|
58
|
-
const hasLabel = hasAriaLabel || hasAssociatedLabel;
|
|
59
|
-
if (!hasLabel) {
|
|
60
|
-
issues.push('Form element missing label association');
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
hasRole,
|
|
66
|
-
hasAriaLabel,
|
|
67
|
-
hasAriaDescribedBy,
|
|
68
|
-
issues,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Check DOM structure for common issues
|
|
74
|
-
*/
|
|
75
|
-
export function checkDOMStructure(container: HTMLElement): {
|
|
76
|
-
hasContent: boolean;
|
|
77
|
-
isEmpty: boolean;
|
|
78
|
-
hasChildren: boolean;
|
|
79
|
-
nestedDepth: number;
|
|
80
|
-
issues: string[];
|
|
81
|
-
} {
|
|
82
|
-
const issues: string[] = [];
|
|
83
|
-
const hasContent = container.textContent !== null && container.textContent.trim().length > 0;
|
|
84
|
-
const isEmpty = container.children.length === 0 && !hasContent;
|
|
85
|
-
const hasChildren = container.children.length > 0;
|
|
86
|
-
|
|
87
|
-
// Calculate nesting depth
|
|
88
|
-
let maxDepth = 0;
|
|
89
|
-
function getDepth(el: Element, depth = 0): number {
|
|
90
|
-
if (el.children.length === 0) return depth;
|
|
91
|
-
let max = depth;
|
|
92
|
-
for (const child of Array.from(el.children)) {
|
|
93
|
-
max = Math.max(max, getDepth(child, depth + 1));
|
|
94
|
-
}
|
|
95
|
-
return max;
|
|
96
|
-
}
|
|
97
|
-
maxDepth = getDepth(container);
|
|
98
|
-
|
|
99
|
-
// Check for excessive nesting (potential performance issue)
|
|
100
|
-
if (maxDepth > 20) {
|
|
101
|
-
issues.push(`Excessive DOM nesting detected: ${maxDepth} levels`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Check for empty elements
|
|
105
|
-
if (isEmpty) {
|
|
106
|
-
issues.push('Component renders empty content');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
hasContent,
|
|
111
|
-
isEmpty,
|
|
112
|
-
hasChildren,
|
|
113
|
-
nestedDepth: maxDepth,
|
|
114
|
-
issues,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Validate component registration
|
|
120
|
-
*/
|
|
121
|
-
export function validateComponentRegistration(componentType: string): {
|
|
122
|
-
isRegistered: boolean;
|
|
123
|
-
hasConfig: boolean;
|
|
124
|
-
hasRenderer: boolean;
|
|
125
|
-
hasLabel: boolean;
|
|
126
|
-
hasInputs: boolean;
|
|
127
|
-
hasDefaultProps: boolean;
|
|
128
|
-
config: ReturnType<typeof ComponentRegistry.getConfig>;
|
|
129
|
-
} {
|
|
130
|
-
const isRegistered = ComponentRegistry.has(componentType);
|
|
131
|
-
const renderer = ComponentRegistry.get(componentType);
|
|
132
|
-
const config = ComponentRegistry.getConfig(componentType);
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
isRegistered,
|
|
136
|
-
hasConfig: !!config,
|
|
137
|
-
hasRenderer: !!renderer,
|
|
138
|
-
hasLabel: !!config?.label,
|
|
139
|
-
hasInputs: !!config?.inputs && config.inputs.length > 0,
|
|
140
|
-
hasDefaultProps: !!config?.defaultProps,
|
|
141
|
-
config,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Get all display issues for a rendered component
|
|
147
|
-
*/
|
|
148
|
-
export function getAllDisplayIssues(container: HTMLElement): string[] {
|
|
149
|
-
const issues: string[] = [];
|
|
150
|
-
|
|
151
|
-
// Check DOM structure
|
|
152
|
-
const domCheck = checkDOMStructure(container);
|
|
153
|
-
issues.push(...domCheck.issues);
|
|
154
|
-
|
|
155
|
-
// Check accessibility for all interactive elements
|
|
156
|
-
const buttons = container.querySelectorAll('button, a, [role="button"], input, textarea, select');
|
|
157
|
-
buttons.forEach((element) => {
|
|
158
|
-
const a11yCheck = checkAccessibility(element as HTMLElement);
|
|
159
|
-
issues.push(...a11yCheck.issues);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// Check for missing keys in lists
|
|
163
|
-
const lists = container.querySelectorAll('[role="list"], ul, ol');
|
|
164
|
-
lists.forEach((list) => {
|
|
165
|
-
const items = list.children;
|
|
166
|
-
if (items.length > 0) {
|
|
167
|
-
// This is a simplified check - in React, keys are not in the DOM
|
|
168
|
-
// but we can check for duplicate content which might indicate missing keys
|
|
169
|
-
const contents = Array.from(items).map(item => item.textContent);
|
|
170
|
-
const contentCounts = new Map<string, number>();
|
|
171
|
-
contents.forEach(content => {
|
|
172
|
-
contentCounts.set(content || '', (contentCounts.get(content || '') || 0) + 1);
|
|
173
|
-
});
|
|
174
|
-
const hasDuplicates = Array.from(contentCounts.values()).some(count => count > 1);
|
|
175
|
-
if (hasDuplicates) {
|
|
176
|
-
issues.push(`Potential duplicate list items detected`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
// Check for images without alt text
|
|
182
|
-
const images = container.querySelectorAll('img');
|
|
183
|
-
images.forEach((img) => {
|
|
184
|
-
if (!img.hasAttribute('alt')) {
|
|
185
|
-
issues.push('Image missing alt attribute');
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
return issues;
|
|
190
|
-
}
|
|
@@ -1,295 +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 { renderHook, act } from '@testing-library/react';
|
|
11
|
-
import { useConfigDraft } from '../hooks/use-config-draft';
|
|
12
|
-
|
|
13
|
-
describe('useConfigDraft', () => {
|
|
14
|
-
const source = { name: 'Test', columns: 3, gap: 4, enabled: true };
|
|
15
|
-
|
|
16
|
-
it('should initialize draft from source', () => {
|
|
17
|
-
const { result } = renderHook(() => useConfigDraft(source));
|
|
18
|
-
expect(result.current.draft).toEqual(source);
|
|
19
|
-
expect(result.current.isDirty).toBe(false);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should start dirty in create mode', () => {
|
|
23
|
-
const { result } = renderHook(() =>
|
|
24
|
-
useConfigDraft(source, { mode: 'create' }),
|
|
25
|
-
);
|
|
26
|
-
expect(result.current.isDirty).toBe(true);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should start clean in edit mode', () => {
|
|
30
|
-
const { result } = renderHook(() =>
|
|
31
|
-
useConfigDraft(source, { mode: 'edit' }),
|
|
32
|
-
);
|
|
33
|
-
expect(result.current.isDirty).toBe(false);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should update a single field and mark dirty', () => {
|
|
37
|
-
const { result } = renderHook(() => useConfigDraft(source));
|
|
38
|
-
act(() => {
|
|
39
|
-
result.current.updateField('columns', 6);
|
|
40
|
-
});
|
|
41
|
-
expect(result.current.draft.columns).toBe(6);
|
|
42
|
-
expect(result.current.isDirty).toBe(true);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should preserve other fields when updating one', () => {
|
|
46
|
-
const { result } = renderHook(() => useConfigDraft(source));
|
|
47
|
-
act(() => {
|
|
48
|
-
result.current.updateField('gap', 8);
|
|
49
|
-
});
|
|
50
|
-
expect(result.current.draft.name).toBe('Test');
|
|
51
|
-
expect(result.current.draft.columns).toBe(3);
|
|
52
|
-
expect(result.current.draft.gap).toBe(8);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should discard changes and revert to source', () => {
|
|
56
|
-
const { result } = renderHook(() => useConfigDraft(source));
|
|
57
|
-
act(() => {
|
|
58
|
-
result.current.updateField('name', 'Modified');
|
|
59
|
-
result.current.updateField('columns', 12);
|
|
60
|
-
});
|
|
61
|
-
expect(result.current.isDirty).toBe(true);
|
|
62
|
-
|
|
63
|
-
act(() => {
|
|
64
|
-
result.current.discard();
|
|
65
|
-
});
|
|
66
|
-
expect(result.current.draft).toEqual(source);
|
|
67
|
-
expect(result.current.isDirty).toBe(false);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should call onUpdate callback when field changes', () => {
|
|
71
|
-
const onUpdate = vi.fn();
|
|
72
|
-
const { result } = renderHook(() =>
|
|
73
|
-
useConfigDraft(source, { onUpdate }),
|
|
74
|
-
);
|
|
75
|
-
act(() => {
|
|
76
|
-
result.current.updateField('gap', 10);
|
|
77
|
-
});
|
|
78
|
-
expect(onUpdate).toHaveBeenCalledWith('gap', 10);
|
|
79
|
-
expect(onUpdate).toHaveBeenCalledTimes(1);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('should reset draft when source changes', () => {
|
|
83
|
-
const source1 = { name: 'A', value: 1 };
|
|
84
|
-
const source2 = { name: 'B', value: 2 };
|
|
85
|
-
|
|
86
|
-
const { result, rerender } = renderHook(
|
|
87
|
-
({ src }) => useConfigDraft(src),
|
|
88
|
-
{ initialProps: { src: source1 } },
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
act(() => {
|
|
92
|
-
result.current.updateField('name', 'Modified');
|
|
93
|
-
});
|
|
94
|
-
expect(result.current.isDirty).toBe(true);
|
|
95
|
-
|
|
96
|
-
rerender({ src: source2 });
|
|
97
|
-
expect(result.current.draft).toEqual(source2);
|
|
98
|
-
expect(result.current.isDirty).toBe(false);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should handle setDraft for advanced use cases', () => {
|
|
102
|
-
const { result } = renderHook(() => useConfigDraft(source));
|
|
103
|
-
act(() => {
|
|
104
|
-
result.current.setDraft({ ...source, name: 'Advanced', extra: 42 });
|
|
105
|
-
});
|
|
106
|
-
expect(result.current.draft.name).toBe('Advanced');
|
|
107
|
-
expect(result.current.draft.extra).toBe(42);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('should not share reference with source', () => {
|
|
111
|
-
const mutableSource = { name: 'Original' };
|
|
112
|
-
const { result } = renderHook(() => useConfigDraft(mutableSource));
|
|
113
|
-
expect(result.current.draft).not.toBe(mutableSource);
|
|
114
|
-
expect(result.current.draft).toEqual(mutableSource);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// ── Undo / Redo ──────────────────────────────────────────────
|
|
118
|
-
|
|
119
|
-
describe('undo/redo', () => {
|
|
120
|
-
it('should start with canUndo=false and canRedo=false', () => {
|
|
121
|
-
const { result } = renderHook(() => useConfigDraft(source));
|
|
122
|
-
expect(result.current.canUndo).toBe(false);
|
|
123
|
-
expect(result.current.canRedo).toBe(false);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('should enable undo after a field update', () => {
|
|
127
|
-
const { result } = renderHook(() => useConfigDraft(source));
|
|
128
|
-
act(() => {
|
|
129
|
-
result.current.updateField('name', 'Changed');
|
|
130
|
-
});
|
|
131
|
-
expect(result.current.canUndo).toBe(true);
|
|
132
|
-
expect(result.current.canRedo).toBe(false);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('should undo to previous state', () => {
|
|
136
|
-
const { result } = renderHook(() => useConfigDraft(source));
|
|
137
|
-
act(() => {
|
|
138
|
-
result.current.updateField('name', 'Changed');
|
|
139
|
-
});
|
|
140
|
-
expect(result.current.draft.name).toBe('Changed');
|
|
141
|
-
|
|
142
|
-
act(() => {
|
|
143
|
-
result.current.undo();
|
|
144
|
-
});
|
|
145
|
-
expect(result.current.draft.name).toBe('Test');
|
|
146
|
-
expect(result.current.canUndo).toBe(false);
|
|
147
|
-
expect(result.current.canRedo).toBe(true);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('should redo after undo', () => {
|
|
151
|
-
const { result } = renderHook(() => useConfigDraft(source));
|
|
152
|
-
act(() => {
|
|
153
|
-
result.current.updateField('name', 'Changed');
|
|
154
|
-
});
|
|
155
|
-
act(() => {
|
|
156
|
-
result.current.undo();
|
|
157
|
-
});
|
|
158
|
-
act(() => {
|
|
159
|
-
result.current.redo();
|
|
160
|
-
});
|
|
161
|
-
expect(result.current.draft.name).toBe('Changed');
|
|
162
|
-
expect(result.current.canUndo).toBe(true);
|
|
163
|
-
expect(result.current.canRedo).toBe(false);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('should clear redo stack when a new change is made after undo', () => {
|
|
167
|
-
const { result } = renderHook(() => useConfigDraft(source));
|
|
168
|
-
act(() => {
|
|
169
|
-
result.current.updateField('name', 'First');
|
|
170
|
-
});
|
|
171
|
-
act(() => {
|
|
172
|
-
result.current.updateField('name', 'Second');
|
|
173
|
-
});
|
|
174
|
-
act(() => {
|
|
175
|
-
result.current.undo();
|
|
176
|
-
});
|
|
177
|
-
expect(result.current.canRedo).toBe(true);
|
|
178
|
-
|
|
179
|
-
act(() => {
|
|
180
|
-
result.current.updateField('name', 'Third');
|
|
181
|
-
});
|
|
182
|
-
expect(result.current.canRedo).toBe(false);
|
|
183
|
-
expect(result.current.draft.name).toBe('Third');
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('should support multiple undo steps', () => {
|
|
187
|
-
const { result } = renderHook(() => useConfigDraft(source));
|
|
188
|
-
act(() => {
|
|
189
|
-
result.current.updateField('name', 'A');
|
|
190
|
-
});
|
|
191
|
-
act(() => {
|
|
192
|
-
result.current.updateField('name', 'B');
|
|
193
|
-
});
|
|
194
|
-
act(() => {
|
|
195
|
-
result.current.updateField('name', 'C');
|
|
196
|
-
});
|
|
197
|
-
expect(result.current.draft.name).toBe('C');
|
|
198
|
-
|
|
199
|
-
act(() => {
|
|
200
|
-
result.current.undo();
|
|
201
|
-
});
|
|
202
|
-
expect(result.current.draft.name).toBe('B');
|
|
203
|
-
|
|
204
|
-
act(() => {
|
|
205
|
-
result.current.undo();
|
|
206
|
-
});
|
|
207
|
-
expect(result.current.draft.name).toBe('A');
|
|
208
|
-
|
|
209
|
-
act(() => {
|
|
210
|
-
result.current.undo();
|
|
211
|
-
});
|
|
212
|
-
expect(result.current.draft.name).toBe('Test');
|
|
213
|
-
expect(result.current.canUndo).toBe(false);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it('should do nothing when undoing with empty history', () => {
|
|
217
|
-
const { result } = renderHook(() => useConfigDraft(source));
|
|
218
|
-
act(() => {
|
|
219
|
-
result.current.undo();
|
|
220
|
-
});
|
|
221
|
-
expect(result.current.draft).toEqual(source);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it('should do nothing when redoing with empty future', () => {
|
|
225
|
-
const { result } = renderHook(() => useConfigDraft(source));
|
|
226
|
-
act(() => {
|
|
227
|
-
result.current.redo();
|
|
228
|
-
});
|
|
229
|
-
expect(result.current.draft).toEqual(source);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('should clear history on discard', () => {
|
|
233
|
-
const { result } = renderHook(() => useConfigDraft(source));
|
|
234
|
-
act(() => {
|
|
235
|
-
result.current.updateField('name', 'Changed');
|
|
236
|
-
});
|
|
237
|
-
expect(result.current.canUndo).toBe(true);
|
|
238
|
-
|
|
239
|
-
act(() => {
|
|
240
|
-
result.current.discard();
|
|
241
|
-
});
|
|
242
|
-
expect(result.current.canUndo).toBe(false);
|
|
243
|
-
expect(result.current.canRedo).toBe(false);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it('should clear history when source changes', () => {
|
|
247
|
-
const source1 = { name: 'A', value: 1 };
|
|
248
|
-
const source2 = { name: 'B', value: 2 };
|
|
249
|
-
const { result, rerender } = renderHook(
|
|
250
|
-
({ src }) => useConfigDraft(src),
|
|
251
|
-
{ initialProps: { src: source1 } },
|
|
252
|
-
);
|
|
253
|
-
act(() => {
|
|
254
|
-
result.current.updateField('name', 'Modified');
|
|
255
|
-
});
|
|
256
|
-
expect(result.current.canUndo).toBe(true);
|
|
257
|
-
|
|
258
|
-
rerender({ src: source2 });
|
|
259
|
-
expect(result.current.canUndo).toBe(false);
|
|
260
|
-
expect(result.current.canRedo).toBe(false);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it('should respect maxHistory limit', () => {
|
|
264
|
-
const { result } = renderHook(() =>
|
|
265
|
-
useConfigDraft(source, { maxHistory: 3 }),
|
|
266
|
-
);
|
|
267
|
-
act(() => {
|
|
268
|
-
result.current.updateField('name', 'A');
|
|
269
|
-
});
|
|
270
|
-
act(() => {
|
|
271
|
-
result.current.updateField('name', 'B');
|
|
272
|
-
});
|
|
273
|
-
act(() => {
|
|
274
|
-
result.current.updateField('name', 'C');
|
|
275
|
-
});
|
|
276
|
-
act(() => {
|
|
277
|
-
result.current.updateField('name', 'D');
|
|
278
|
-
});
|
|
279
|
-
// maxHistory=3, so only 3 undo steps (not 4)
|
|
280
|
-
act(() => {
|
|
281
|
-
result.current.undo();
|
|
282
|
-
});
|
|
283
|
-
expect(result.current.draft.name).toBe('C');
|
|
284
|
-
act(() => {
|
|
285
|
-
result.current.undo();
|
|
286
|
-
});
|
|
287
|
-
expect(result.current.draft.name).toBe('B');
|
|
288
|
-
act(() => {
|
|
289
|
-
result.current.undo();
|
|
290
|
-
});
|
|
291
|
-
// Should stop here — oldest entry was trimmed
|
|
292
|
-
expect(result.current.canUndo).toBe(false);
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
});
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { render, waitFor } from '@testing-library/react';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { ComponentRegistry } from '@object-ui/core';
|
|
5
|
-
import type { DataSource } from '@object-ui/types';
|
|
6
|
-
|
|
7
|
-
// Import all available plugins to ensure they register their views
|
|
8
|
-
import '@object-ui/plugin-aggrid';
|
|
9
|
-
import '@object-ui/plugin-calendar';
|
|
10
|
-
import '@object-ui/plugin-charts';
|
|
11
|
-
import '@object-ui/plugin-dashboard';
|
|
12
|
-
import '@object-ui/plugin-gantt';
|
|
13
|
-
import '@object-ui/plugin-grid';
|
|
14
|
-
import '@object-ui/plugin-kanban';
|
|
15
|
-
import '@object-ui/plugin-map';
|
|
16
|
-
import '@object-ui/plugin-timeline';
|
|
17
|
-
import '@object-ui/plugin-list';
|
|
18
|
-
import '@object-ui/plugin-detail';
|
|
19
|
-
import '@object-ui/plugin-form';
|
|
20
|
-
// Import main components in case they provide default views
|
|
21
|
-
import '../index';
|
|
22
|
-
|
|
23
|
-
// Create a Mock DataSource type compatible with the system
|
|
24
|
-
const createMockDataSource = (): DataSource => ({
|
|
25
|
-
find: vi.fn().mockResolvedValue([]),
|
|
26
|
-
findOne: vi.fn().mockResolvedValue({}),
|
|
27
|
-
create: vi.fn().mockResolvedValue({}),
|
|
28
|
-
update: vi.fn().mockResolvedValue({}),
|
|
29
|
-
delete: vi.fn().mockResolvedValue(true),
|
|
30
|
-
count: vi.fn().mockResolvedValue(0),
|
|
31
|
-
} as unknown as DataSource);
|
|
32
|
-
|
|
33
|
-
describe('View Component Compliance', () => {
|
|
34
|
-
|
|
35
|
-
// Expected standard views based on supported plugins and types
|
|
36
|
-
// These should coincide with packages/types/src/views.ts or objectql view types
|
|
37
|
-
const EXPECTED_STANDARD_VIEWS = [
|
|
38
|
-
'grid', // plugin-grid
|
|
39
|
-
'kanban', // plugin-kanban
|
|
40
|
-
'calendar', // plugin-calendar
|
|
41
|
-
'timeline', // plugin-timeline
|
|
42
|
-
'map', // plugin-map
|
|
43
|
-
'gantt', // plugin-gantt
|
|
44
|
-
'chart', // plugin-charts
|
|
45
|
-
'dashboard', // plugin-dashboard
|
|
46
|
-
'list', // plugin-list
|
|
47
|
-
'detail', // plugin-detail
|
|
48
|
-
'form', // plugin-form
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
// Assert registration of expected standard views
|
|
52
|
-
EXPECTED_STANDARD_VIEWS.forEach(viewType => {
|
|
53
|
-
it(`should have registered standard view: view:${viewType}`, () => {
|
|
54
|
-
// We look for components registered with 'view' namespace or starting with 'view:'
|
|
55
|
-
// Example: 'view:grid'
|
|
56
|
-
const viewKey = `view:${viewType}`;
|
|
57
|
-
|
|
58
|
-
// Check direct registration or via namespace aliasing
|
|
59
|
-
// ComponentRegistry.get checks namespaces.
|
|
60
|
-
// If registered as { type: 'grid', namespace: 'view' }, fullKey is 'view:grid'.
|
|
61
|
-
const hasView = ComponentRegistry.getAllConfigs().some(c => c.type === viewKey);
|
|
62
|
-
|
|
63
|
-
if (!hasView) {
|
|
64
|
-
// Try looking for non-namespaced if it is a view category
|
|
65
|
-
const fallback = ComponentRegistry.getAllConfigs().some(c =>
|
|
66
|
-
(c.category === 'view' || c.category === 'Complex') &&
|
|
67
|
-
(c.type === viewType || c.type.endsWith(':' + viewType))
|
|
68
|
-
);
|
|
69
|
-
if (fallback) {
|
|
70
|
-
// Warn but accept if instructions allow? instructions strict on "view:*"
|
|
71
|
-
// I will fail if not registered as view:*
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (!hasView) {
|
|
76
|
-
console.warn(`MISSING VIEW IMPLEMENTATION: ${viewKey}. Ensure the plugin (e.g. plugin-${viewType}) is imported and registers with namespace: 'view'.`);
|
|
77
|
-
// Fail the test as per requirements
|
|
78
|
-
// We expect TRUE. If hasView is false, it fails.
|
|
79
|
-
expect(hasView, `View '${viewKey}' should be registered`).toBe(true);
|
|
80
|
-
} else {
|
|
81
|
-
expect(hasView).toBe(true);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// Filter for valid view components for deeper method compliance
|
|
87
|
-
// We include anything that claims to be a view
|
|
88
|
-
const viewComponents = ComponentRegistry.getAllConfigs().filter(c =>
|
|
89
|
-
c.category === 'view' || c.namespace === 'view' || c.type.startsWith('view:')
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
it('should have some view components registered from plugins', () => {
|
|
93
|
-
expect(viewComponents.length).toBeGreaterThan(0);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
viewComponents.forEach(config => {
|
|
97
|
-
const componentName = config.type;
|
|
98
|
-
|
|
99
|
-
describe(`View: ${componentName}`, () => {
|
|
100
|
-
|
|
101
|
-
it('should have required metadata for views', () => {
|
|
102
|
-
// Either category is view OR namespace is view (which implies it's a view)
|
|
103
|
-
const isView = config.category === 'view' || config.namespace === 'view' || config.type.startsWith('view:');
|
|
104
|
-
expect(isView).toBe(true);
|
|
105
|
-
expect(config.component).toBeDefined();
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('should define data binding inputs (object/bind) or data input', () => {
|
|
109
|
-
const inputs = config.inputs || [];
|
|
110
|
-
// Views usually need an objectName to bind to ObjectStack OR a direct data array
|
|
111
|
-
const hasObjectInput = inputs.some(i => i.name === 'objectName' || i.name === 'object' || i.name === 'entity');
|
|
112
|
-
const hasDataInput = inputs.some(i => i.name === 'data' || i.name === 'items' || i.name === 'events' || i.name === 'tasks');
|
|
113
|
-
|
|
114
|
-
// Warn but don't unnecessary fail if complex logic exists
|
|
115
|
-
if (!hasObjectInput && hasDataInput) {
|
|
116
|
-
// Acceptable
|
|
117
|
-
} else if (!hasObjectInput && !config.inputs) {
|
|
118
|
-
// Might be purely props driven
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
expect(true).toBe(true);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should attempt to fetchData when rendered with dataSource', async () => {
|
|
125
|
-
const Cmp = config.component as React.ComponentType<any>;
|
|
126
|
-
const mockSource = createMockDataSource();
|
|
127
|
-
|
|
128
|
-
const schema = {
|
|
129
|
-
type: config.type,
|
|
130
|
-
objectName: 'test_object',
|
|
131
|
-
columns: [{ name: 'name', label: 'Name' }],
|
|
132
|
-
data: [],
|
|
133
|
-
...config.defaultProps
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// Render test
|
|
137
|
-
try {
|
|
138
|
-
const { unmount } = render(
|
|
139
|
-
<Cmp
|
|
140
|
-
schema={schema}
|
|
141
|
-
dataSource={mockSource}
|
|
142
|
-
className="test-view-class"
|
|
143
|
-
/>
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
unmount();
|
|
147
|
-
} catch (e) {
|
|
148
|
-
// console.error(`Failed to verify view render ${componentName}`, e);
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
});
|