meno-core 1.0.0
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/bin/cli.ts +281 -0
- package/build-static.ts +298 -0
- package/bunfig.toml +39 -0
- package/entries/client-router.tsx +111 -0
- package/entries/server-router.tsx +71 -0
- package/lib/client/ClientInitializer.test.ts +9 -0
- package/lib/client/ClientInitializer.test.ts.skip +92 -0
- package/lib/client/ClientInitializer.ts +60 -0
- package/lib/client/ErrorBoundary.test.tsx +595 -0
- package/lib/client/ErrorBoundary.tsx +230 -0
- package/lib/client/componentRegistry.test.ts +165 -0
- package/lib/client/componentRegistry.ts +18 -0
- package/lib/client/contexts/ThemeContext.tsx +73 -0
- package/lib/client/core/ComponentBuilder.test.ts +677 -0
- package/lib/client/core/ComponentBuilder.ts +660 -0
- package/lib/client/core/ComponentRenderer.test.tsx +176 -0
- package/lib/client/core/ComponentRenderer.tsx +83 -0
- package/lib/client/core/cmsTemplateProcessor.ts +129 -0
- package/lib/client/elementRegistry.ts +81 -0
- package/lib/client/hmr/HMRManager.tsx +179 -0
- package/lib/client/hmr/index.ts +5 -0
- package/lib/client/hmrWebSocket.test.ts +9 -0
- package/lib/client/hmrWebSocket.ts +250 -0
- package/lib/client/hooks/useColorVariables.test.ts +166 -0
- package/lib/client/hooks/useColorVariables.ts +249 -0
- package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
- package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
- package/lib/client/hydration/HydrationUtils.test.ts +154 -0
- package/lib/client/hydration/HydrationUtils.ts +35 -0
- package/lib/client/i18nConfigService.test.ts +74 -0
- package/lib/client/i18nConfigService.ts +78 -0
- package/lib/client/index.ts +56 -0
- package/lib/client/navigation.test.ts +441 -0
- package/lib/client/navigation.ts +23 -0
- package/lib/client/responsiveStyleResolver.test.ts +491 -0
- package/lib/client/responsiveStyleResolver.ts +184 -0
- package/lib/client/routing/RouteLoader.test.ts +635 -0
- package/lib/client/routing/RouteLoader.ts +347 -0
- package/lib/client/routing/Router.tsx +382 -0
- package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
- package/lib/client/scripts/ScriptExecutor.ts +171 -0
- package/lib/client/scripts/formHandler.ts +103 -0
- package/lib/client/styleProcessor.test.ts +126 -0
- package/lib/client/styleProcessor.ts +92 -0
- package/lib/client/styles/StyleInjector.test.ts +354 -0
- package/lib/client/styles/StyleInjector.ts +154 -0
- package/lib/client/templateEngine.test.ts +660 -0
- package/lib/client/templateEngine.ts +667 -0
- package/lib/client/theme.test.ts +173 -0
- package/lib/client/theme.ts +159 -0
- package/lib/client/utils/toast.ts +46 -0
- package/lib/server/createServer.ts +170 -0
- package/lib/server/cssGenerator.test.ts +172 -0
- package/lib/server/cssGenerator.ts +58 -0
- package/lib/server/fileWatcher.ts +134 -0
- package/lib/server/index.ts +55 -0
- package/lib/server/jsonLoader.test.ts +103 -0
- package/lib/server/jsonLoader.ts +350 -0
- package/lib/server/middleware/cors.test.ts +177 -0
- package/lib/server/middleware/cors.ts +69 -0
- package/lib/server/middleware/errorHandler.test.ts +208 -0
- package/lib/server/middleware/errorHandler.ts +63 -0
- package/lib/server/middleware/index.ts +9 -0
- package/lib/server/middleware/logger.test.ts +233 -0
- package/lib/server/middleware/logger.ts +99 -0
- package/lib/server/pageCache.test.ts +167 -0
- package/lib/server/pageCache.ts +97 -0
- package/lib/server/projectContext.ts +51 -0
- package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
- package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
- package/lib/server/providers/fileSystemPageProvider.ts +83 -0
- package/lib/server/routes/api/cms.test.ts +177 -0
- package/lib/server/routes/api/cms.ts +82 -0
- package/lib/server/routes/api/colors.ts +59 -0
- package/lib/server/routes/api/components.ts +70 -0
- package/lib/server/routes/api/config.test.ts +9 -0
- package/lib/server/routes/api/config.ts +28 -0
- package/lib/server/routes/api/core-routes.ts +182 -0
- package/lib/server/routes/api/functions.ts +170 -0
- package/lib/server/routes/api/index.ts +69 -0
- package/lib/server/routes/api/pages.ts +95 -0
- package/lib/server/routes/api/shared.test.ts +81 -0
- package/lib/server/routes/api/shared.ts +31 -0
- package/lib/server/routes/editor.test.ts +9 -0
- package/lib/server/routes/index.ts +104 -0
- package/lib/server/routes/pages.ts +161 -0
- package/lib/server/routes/static.ts +107 -0
- package/lib/server/services/ColorService.ts +193 -0
- package/lib/server/services/cmsService.test.ts +388 -0
- package/lib/server/services/cmsService.ts +296 -0
- package/lib/server/services/componentService.test.ts +276 -0
- package/lib/server/services/componentService.ts +346 -0
- package/lib/server/services/configService.ts +156 -0
- package/lib/server/services/fileWatcherService.ts +67 -0
- package/lib/server/services/index.ts +10 -0
- package/lib/server/services/pageService.test.ts +258 -0
- package/lib/server/services/pageService.ts +240 -0
- package/lib/server/ssrRenderer.test.ts +1005 -0
- package/lib/server/ssrRenderer.ts +878 -0
- package/lib/server/utilityClassGenerator.ts +11 -0
- package/lib/server/utils/index.ts +5 -0
- package/lib/server/utils/jsonLineMapper.test.ts +100 -0
- package/lib/server/utils/jsonLineMapper.ts +166 -0
- package/lib/server/validateStyleCoverage.test.ts +9 -0
- package/lib/server/validateStyleCoverage.ts +167 -0
- package/lib/server/websocketManager.test.ts +9 -0
- package/lib/server/websocketManager.ts +95 -0
- package/lib/shared/attributeNodeUtils.test.ts +152 -0
- package/lib/shared/attributeNodeUtils.ts +50 -0
- package/lib/shared/breakpoints.test.ts +166 -0
- package/lib/shared/breakpoints.ts +65 -0
- package/lib/shared/colorProperties.test.ts +111 -0
- package/lib/shared/colorProperties.ts +40 -0
- package/lib/shared/colorVariableUtils.test.ts +319 -0
- package/lib/shared/colorVariableUtils.ts +97 -0
- package/lib/shared/constants.test.ts +175 -0
- package/lib/shared/constants.ts +116 -0
- package/lib/shared/cssGeneration.ts +481 -0
- package/lib/shared/cssProperties.test.ts +252 -0
- package/lib/shared/cssProperties.ts +338 -0
- package/lib/shared/elementUtils.test.ts +245 -0
- package/lib/shared/elementUtils.ts +90 -0
- package/lib/shared/fontLoader.ts +97 -0
- package/lib/shared/i18n.test.ts +313 -0
- package/lib/shared/i18n.ts +286 -0
- package/lib/shared/index.ts +50 -0
- package/lib/shared/interfaces/contentProvider.test.ts +9 -0
- package/lib/shared/interfaces/contentProvider.ts +121 -0
- package/lib/shared/nodeUtils.test.ts +320 -0
- package/lib/shared/nodeUtils.ts +220 -0
- package/lib/shared/pathArrayUtils.test.ts +315 -0
- package/lib/shared/pathArrayUtils.ts +17 -0
- package/lib/shared/pathUtils.test.ts +260 -0
- package/lib/shared/pathUtils.ts +244 -0
- package/lib/shared/paths/Path.test.ts +74 -0
- package/lib/shared/paths/Path.ts +23 -0
- package/lib/shared/paths/PathConverter.test.ts +232 -0
- package/lib/shared/paths/PathConverter.ts +141 -0
- package/lib/shared/paths/PathUtils.ts +290 -0
- package/lib/shared/paths/PathValidator.test.ts +193 -0
- package/lib/shared/paths/PathValidator.ts +53 -0
- package/lib/shared/paths/index.ts +48 -0
- package/lib/shared/propResolver.test.ts +639 -0
- package/lib/shared/propResolver.ts +124 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
- package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
- package/lib/shared/registry/ClientRegistry.test.ts +26 -0
- package/lib/shared/registry/ClientRegistry.ts +15 -0
- package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
- package/lib/shared/registry/ComponentRegistry.ts +100 -0
- package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
- package/lib/shared/registry/NodeTypeManager.ts +94 -0
- package/lib/shared/registry/RegistryManager.test.ts +58 -0
- package/lib/shared/registry/RegistryManager.ts +60 -0
- package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
- package/lib/shared/registry/SSRRegistry.test.ts +26 -0
- package/lib/shared/registry/SSRRegistry.ts +15 -0
- package/lib/shared/registry/createNodeType.ts +175 -0
- package/lib/shared/registry/defineNodeType.ts +73 -0
- package/lib/shared/registry/fieldPresets.ts +109 -0
- package/lib/shared/registry/index.ts +50 -0
- package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
- package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
- package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
- package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
- package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
- package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
- package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
- package/lib/shared/registry/nodeTypes/index.ts +75 -0
- package/lib/shared/responsiveScaling.test.ts +268 -0
- package/lib/shared/responsiveScaling.ts +194 -0
- package/lib/shared/responsiveStyleUtils.test.ts +300 -0
- package/lib/shared/responsiveStyleUtils.ts +139 -0
- package/lib/shared/slugTranslator.test.ts +325 -0
- package/lib/shared/slugTranslator.ts +177 -0
- package/lib/shared/styleNodeUtils.test.ts +132 -0
- package/lib/shared/styleNodeUtils.ts +102 -0
- package/lib/shared/styleUtils.test.ts +238 -0
- package/lib/shared/styleUtils.ts +63 -0
- package/lib/shared/themeDefaults.test.ts +113 -0
- package/lib/shared/themeDefaults.ts +103 -0
- package/lib/shared/tree/PathBuilder.ts +383 -0
- package/lib/shared/treePathUtils.test.ts +539 -0
- package/lib/shared/treePathUtils.ts +339 -0
- package/lib/shared/types/api.ts +58 -0
- package/lib/shared/types/cms.ts +95 -0
- package/lib/shared/types/colors.ts +45 -0
- package/lib/shared/types/components.ts +121 -0
- package/lib/shared/types/errors.test.ts +103 -0
- package/lib/shared/types/errors.ts +69 -0
- package/lib/shared/types/index.ts +96 -0
- package/lib/shared/types/nodes.ts +20 -0
- package/lib/shared/types/rendering.ts +61 -0
- package/lib/shared/types/styles.ts +38 -0
- package/lib/shared/types.ts +11 -0
- package/lib/shared/utilityClassConfig.ts +287 -0
- package/lib/shared/utilityClassMapper.test.ts +140 -0
- package/lib/shared/utilityClassMapper.ts +229 -0
- package/lib/shared/utils/fileUtils.test.ts +99 -0
- package/lib/shared/utils/fileUtils.ts +56 -0
- package/lib/shared/utils.test.ts +261 -0
- package/lib/shared/utils.ts +84 -0
- package/lib/shared/validation/index.ts +7 -0
- package/lib/shared/validation/propValidator.test.ts +178 -0
- package/lib/shared/validation/propValidator.ts +238 -0
- package/lib/shared/validation/schemas.test.ts +177 -0
- package/lib/shared/validation/schemas.ts +401 -0
- package/lib/shared/validation/validators.test.ts +109 -0
- package/lib/shared/validation/validators.ts +304 -0
- package/lib/test-utils/dom-setup.ts +55 -0
- package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
- package/lib/test-utils/factories/DomMockFactory.ts +487 -0
- package/lib/test-utils/factories/EventMockFactory.ts +244 -0
- package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
- package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
- package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
- package/lib/test-utils/factories/index.ts +11 -0
- package/lib/test-utils/fixtures.ts +134 -0
- package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
- package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
- package/lib/test-utils/helpers/index.ts +6 -0
- package/lib/test-utils/helpers.test.ts +73 -0
- package/lib/test-utils/helpers.ts +90 -0
- package/lib/test-utils/index.ts +17 -0
- package/lib/test-utils/mockFactories.ts +92 -0
- package/lib/test-utils/mocks.ts +341 -0
- package/package.json +38 -0
- package/templates/index-router.html +34 -0
- package/tsconfig.json +14 -0
- package/vite.config.ts +43 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prop Validator
|
|
3
|
+
* Validates component props against their interface definitions
|
|
4
|
+
* Provides type checking and coercion for runtime safety
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PropDefinition } from '../types/components';
|
|
8
|
+
import { isI18nValue } from '../i18n';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validation error with context
|
|
12
|
+
*/
|
|
13
|
+
export interface ValidationError {
|
|
14
|
+
propName: string;
|
|
15
|
+
message: string;
|
|
16
|
+
receivedValue: unknown;
|
|
17
|
+
expectedType: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validation result
|
|
22
|
+
*/
|
|
23
|
+
export type PropValidationResult =
|
|
24
|
+
| { valid: true; props: Record<string, unknown> }
|
|
25
|
+
| { valid: false; errors: ValidationError[]; props: Record<string, unknown> };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Coerce a value to the expected type if safe
|
|
29
|
+
* Returns the coerced value or undefined if coercion is not safe
|
|
30
|
+
* Note: i18n values are passed through unchanged for later resolution
|
|
31
|
+
*/
|
|
32
|
+
function coerceValue(value: unknown, expectedType: PropDefinition['type']): unknown {
|
|
33
|
+
if (value === null || value === undefined) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Pass through i18n values unchanged - they will be resolved later by resolveI18nInProps
|
|
38
|
+
if (isI18nValue(value)) {
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
switch (expectedType) {
|
|
43
|
+
case 'string':
|
|
44
|
+
return String(value);
|
|
45
|
+
|
|
46
|
+
case 'number':
|
|
47
|
+
if (typeof value === 'number') {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
if (typeof value === 'string') {
|
|
51
|
+
const num = Number(value);
|
|
52
|
+
if (!isNaN(num) && isFinite(num)) {
|
|
53
|
+
return num;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
|
|
58
|
+
case 'boolean':
|
|
59
|
+
if (typeof value === 'boolean') {
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
if (typeof value === 'string') {
|
|
63
|
+
const lower = value.toLowerCase();
|
|
64
|
+
if (lower === 'true' || lower === '1') {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
if (lower === 'false' || lower === '0') {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (typeof value === 'number') {
|
|
72
|
+
return value !== 0;
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
|
|
76
|
+
case 'select':
|
|
77
|
+
return String(value);
|
|
78
|
+
|
|
79
|
+
default:
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Validate a single prop value against its definition
|
|
86
|
+
*/
|
|
87
|
+
function validateSingleProp(
|
|
88
|
+
propName: string,
|
|
89
|
+
propDef: PropDefinition,
|
|
90
|
+
value: unknown
|
|
91
|
+
): { valid: boolean; error?: ValidationError; coercedValue?: unknown } {
|
|
92
|
+
// If value is undefined, it's valid (will use default)
|
|
93
|
+
if (value === undefined) {
|
|
94
|
+
return { valid: true };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { type, options } = propDef;
|
|
98
|
+
|
|
99
|
+
// Type checking and coercion
|
|
100
|
+
let coercedValue: unknown;
|
|
101
|
+
let typeValid = false;
|
|
102
|
+
|
|
103
|
+
switch (type) {
|
|
104
|
+
case 'string':
|
|
105
|
+
coercedValue = coerceValue(value, 'string');
|
|
106
|
+
// Accept both strings and i18n values (i18n values will be resolved later)
|
|
107
|
+
typeValid = typeof coercedValue === 'string' || isI18nValue(coercedValue);
|
|
108
|
+
break;
|
|
109
|
+
|
|
110
|
+
case 'number':
|
|
111
|
+
coercedValue = coerceValue(value, 'number');
|
|
112
|
+
typeValid = typeof coercedValue === 'number';
|
|
113
|
+
break;
|
|
114
|
+
|
|
115
|
+
case 'boolean':
|
|
116
|
+
coercedValue = coerceValue(value, 'boolean');
|
|
117
|
+
typeValid = typeof coercedValue === 'boolean';
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case 'select':
|
|
121
|
+
coercedValue = coerceValue(value, 'select');
|
|
122
|
+
typeValid = typeof coercedValue === 'string';
|
|
123
|
+
if (typeValid && options) {
|
|
124
|
+
// Validate against allowed options
|
|
125
|
+
if (!options.includes(coercedValue as string)) {
|
|
126
|
+
return {
|
|
127
|
+
valid: false,
|
|
128
|
+
error: {
|
|
129
|
+
propName,
|
|
130
|
+
message: `Value "${coercedValue}" is not in allowed options: ${options.join(', ')}`,
|
|
131
|
+
receivedValue: value,
|
|
132
|
+
expectedType: `select(${options.join('|')})`,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
|
|
139
|
+
case 'link':
|
|
140
|
+
// Link type expects an object with href (and optional target)
|
|
141
|
+
if (typeof value === 'object' && value !== null && 'href' in value) {
|
|
142
|
+
coercedValue = value;
|
|
143
|
+
typeValid = true;
|
|
144
|
+
} else {
|
|
145
|
+
typeValid = false;
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
|
|
149
|
+
default:
|
|
150
|
+
return {
|
|
151
|
+
valid: false,
|
|
152
|
+
error: {
|
|
153
|
+
propName,
|
|
154
|
+
message: `Unknown prop type: ${type}`,
|
|
155
|
+
receivedValue: value,
|
|
156
|
+
expectedType: type,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!typeValid) {
|
|
162
|
+
return {
|
|
163
|
+
valid: false,
|
|
164
|
+
error: {
|
|
165
|
+
propName,
|
|
166
|
+
message: `Expected ${type}, got ${typeof value}`,
|
|
167
|
+
receivedValue: value,
|
|
168
|
+
expectedType: type,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return { valid: true, coercedValue };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Validate component props against their interface definition
|
|
178
|
+
*
|
|
179
|
+
* @param propDefs - Component interface definition (prop name -> PropDefinition)
|
|
180
|
+
* @param passedProps - Props passed to the component instance
|
|
181
|
+
* @returns Validation result with validated/coerced props and any errors
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```typescript
|
|
185
|
+
* const result = validateComponentProps(
|
|
186
|
+
* { title: { type: 'string', default: 'Hello' } },
|
|
187
|
+
* { title: 'World', count: 123 }
|
|
188
|
+
* );
|
|
189
|
+
* if (result.valid) {
|
|
190
|
+
* console.log(result.props); // { title: 'World', count: 123 }
|
|
191
|
+
* }
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
export function validateComponentProps(
|
|
195
|
+
propDefs: Record<string, PropDefinition>,
|
|
196
|
+
passedProps: Record<string, unknown>
|
|
197
|
+
): PropValidationResult {
|
|
198
|
+
const errors: ValidationError[] = [];
|
|
199
|
+
const validatedProps: Record<string, unknown> = {};
|
|
200
|
+
|
|
201
|
+
// Validate props defined in interface
|
|
202
|
+
for (const [propName, propDef] of Object.entries(propDefs)) {
|
|
203
|
+
const value = passedProps[propName];
|
|
204
|
+
const validation = validateSingleProp(propName, propDef, value);
|
|
205
|
+
|
|
206
|
+
if (validation.valid) {
|
|
207
|
+
// Use coerced value if available, otherwise use original or default
|
|
208
|
+
validatedProps[propName] = validation.coercedValue !== undefined
|
|
209
|
+
? validation.coercedValue
|
|
210
|
+
: (value !== undefined ? value : propDef.default);
|
|
211
|
+
} else {
|
|
212
|
+
// Validation failed - log error but use default if available (backward compatibility)
|
|
213
|
+
if (validation.error) {
|
|
214
|
+
errors.push(validation.error);
|
|
215
|
+
}
|
|
216
|
+
// Use default value if available, otherwise use original value (graceful degradation)
|
|
217
|
+
validatedProps[propName] = propDef.default !== undefined
|
|
218
|
+
? propDef.default
|
|
219
|
+
: value;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Preserve unknown props for backward compatibility (but log warnings)
|
|
224
|
+
for (const [propName, value] of Object.entries(passedProps)) {
|
|
225
|
+
if (!(propName in propDefs)) {
|
|
226
|
+
// Unknown prop - preserve it but log a warning (backward compatibility)
|
|
227
|
+
validatedProps[propName] = value;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (errors.length > 0) {
|
|
232
|
+
return { valid: false, errors, props: validatedProps };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return { valid: true, props: validatedProps };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Validation Tests
|
|
3
|
+
* Critical path tests for zod schema validation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, test, expect } from 'bun:test';
|
|
7
|
+
import {
|
|
8
|
+
PropDefinitionSchema,
|
|
9
|
+
ComponentNodeSchema,
|
|
10
|
+
ComponentDefinitionSchema,
|
|
11
|
+
PageDataSchema,
|
|
12
|
+
} from './schemas';
|
|
13
|
+
|
|
14
|
+
describe('Schema Validation', () => {
|
|
15
|
+
describe('Valid component definitions pass', () => {
|
|
16
|
+
test('valid prop definition', () => {
|
|
17
|
+
const propDef = {
|
|
18
|
+
type: 'string',
|
|
19
|
+
default: 'Hello',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const result = PropDefinitionSchema.safeParse(propDef);
|
|
23
|
+
|
|
24
|
+
expect(result.success).toBe(true);
|
|
25
|
+
if (result.success) {
|
|
26
|
+
expect(result.data.type).toBe('string');
|
|
27
|
+
expect(result.data.default).toBe('Hello');
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('valid select prop definition', () => {
|
|
32
|
+
const propDef = {
|
|
33
|
+
type: 'select',
|
|
34
|
+
default: 'primary',
|
|
35
|
+
options: ['primary', 'secondary'],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const result = PropDefinitionSchema.safeParse(propDef);
|
|
39
|
+
|
|
40
|
+
expect(result.success).toBe(true);
|
|
41
|
+
if (result.success) {
|
|
42
|
+
expect(result.data.type).toBe('select');
|
|
43
|
+
expect(result.data.options).toEqual(['primary', 'secondary']);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('valid component node', () => {
|
|
48
|
+
const node = {
|
|
49
|
+
type: 'node',
|
|
50
|
+
tag: 'div',
|
|
51
|
+
children: [],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const result = ComponentNodeSchema.safeParse(node);
|
|
55
|
+
|
|
56
|
+
expect(result.success).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('valid component definition', () => {
|
|
60
|
+
const def = {
|
|
61
|
+
type: 'component',
|
|
62
|
+
component: {
|
|
63
|
+
interface: {
|
|
64
|
+
title: { type: 'string', default: 'Hello' },
|
|
65
|
+
},
|
|
66
|
+
structure: {
|
|
67
|
+
type: 'node',
|
|
68
|
+
tag: 'div',
|
|
69
|
+
children: [],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const result = ComponentDefinitionSchema.safeParse(def);
|
|
75
|
+
|
|
76
|
+
expect(result.success).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('Invalid structures caught', () => {
|
|
81
|
+
test('invalid prop type', () => {
|
|
82
|
+
const propDef = {
|
|
83
|
+
type: 'invalid-type',
|
|
84
|
+
default: 'Hello',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const result = PropDefinitionSchema.safeParse(propDef);
|
|
88
|
+
|
|
89
|
+
expect(result.success).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('missing required node type', () => {
|
|
93
|
+
const node = {
|
|
94
|
+
tag: 'div',
|
|
95
|
+
children: [],
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const result = ComponentNodeSchema.safeParse(node);
|
|
99
|
+
|
|
100
|
+
expect(result.success).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('invalid component definition structure', () => {
|
|
104
|
+
const def = {
|
|
105
|
+
component: {
|
|
106
|
+
// Missing required structure
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const result = ComponentDefinitionSchema.safeParse(def);
|
|
111
|
+
|
|
112
|
+
// Should fail or pass with passthrough (backward compatibility)
|
|
113
|
+
// The schema uses passthrough, so it might pass but log warnings
|
|
114
|
+
expect(result).toBeDefined();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('Missing required fields handled', () => {
|
|
119
|
+
test('prop definition without default', () => {
|
|
120
|
+
const propDef = {
|
|
121
|
+
type: 'string',
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const result = PropDefinitionSchema.safeParse(propDef);
|
|
125
|
+
|
|
126
|
+
// Should pass (default is optional)
|
|
127
|
+
expect(result.success).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('component node without children', () => {
|
|
131
|
+
const node = {
|
|
132
|
+
type: 'node',
|
|
133
|
+
tag: 'div',
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const result = ComponentNodeSchema.safeParse(node);
|
|
137
|
+
|
|
138
|
+
// Should pass (children is optional)
|
|
139
|
+
expect(result.success).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('Unknown fields preserved (backward compatibility)', () => {
|
|
144
|
+
test('prop definition with unknown fields', () => {
|
|
145
|
+
const propDef = {
|
|
146
|
+
type: 'string',
|
|
147
|
+
default: 'Hello',
|
|
148
|
+
unknownField: 'preserved',
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const result = PropDefinitionSchema.safeParse(propDef);
|
|
152
|
+
|
|
153
|
+
expect(result.success).toBe(true);
|
|
154
|
+
if (result.success) {
|
|
155
|
+
// Passthrough should preserve unknown fields
|
|
156
|
+
expect((propDef as any).unknownField).toBe('preserved');
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('component node with unknown fields', () => {
|
|
161
|
+
const node = {
|
|
162
|
+
type: 'node',
|
|
163
|
+
tag: 'div',
|
|
164
|
+
unknownField: 'preserved',
|
|
165
|
+
children: [],
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const result = ComponentNodeSchema.safeParse(node);
|
|
169
|
+
|
|
170
|
+
expect(result.success).toBe(true);
|
|
171
|
+
// Passthrough preserves unknown fields
|
|
172
|
+
expect((node as any).unknownField).toBe('preserved');
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
|