Add component props definitions with validation and type checking

Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-08 13:43:51 +00:00
parent 7da561bd86
commit 053cb1fa84
4 changed files with 1516 additions and 0 deletions

View File

@@ -1135,6 +1135,662 @@
]
}
},
"componentProps": {
"Button": {
"description": "Material-UI Button component",
"category": "inputs",
"props": {
"text": {
"type": "string",
"description": "Button text content",
"required": false
},
"variant": {
"type": "enum",
"values": ["text", "outlined", "contained"],
"default": "text",
"description": "Button variant style"
},
"color": {
"type": "enum",
"values": ["default", "primary", "secondary", "error", "warning", "info", "success"],
"default": "primary",
"description": "Button color theme"
},
"size": {
"type": "enum",
"values": ["small", "medium", "large"],
"default": "medium",
"description": "Button size"
},
"disabled": {
"type": "boolean",
"default": false,
"description": "Whether button is disabled"
},
"fullWidth": {
"type": "boolean",
"default": false,
"description": "Whether button takes full width"
},
"startIcon": {
"type": "string",
"description": "Icon name to show at start"
},
"endIcon": {
"type": "string",
"description": "Icon name to show at end"
},
"onClick": {
"type": "function",
"description": "Click event handler function name"
}
}
},
"TextField": {
"description": "Material-UI TextField component",
"category": "inputs",
"props": {
"label": {
"type": "string",
"description": "Field label"
},
"placeholder": {
"type": "string",
"description": "Placeholder text"
},
"value": {
"type": "any",
"description": "Field value"
},
"type": {
"type": "enum",
"values": ["text", "email", "password", "number", "tel", "url"],
"default": "text",
"description": "Input type"
},
"variant": {
"type": "enum",
"values": ["standard", "outlined", "filled"],
"default": "outlined",
"description": "TextField variant"
},
"size": {
"type": "enum",
"values": ["small", "medium"],
"default": "medium",
"description": "Field size"
},
"required": {
"type": "boolean",
"default": false,
"description": "Whether field is required"
},
"disabled": {
"type": "boolean",
"default": false,
"description": "Whether field is disabled"
},
"fullWidth": {
"type": "boolean",
"default": false,
"description": "Whether field takes full width"
},
"multiline": {
"type": "boolean",
"default": false,
"description": "Whether field is multiline textarea"
},
"rows": {
"type": "number",
"description": "Number of rows for multiline"
},
"error": {
"type": "boolean",
"description": "Whether field has error"
},
"helperText": {
"type": "string",
"description": "Helper text below field"
},
"onChange": {
"type": "function",
"description": "Change event handler"
}
}
},
"Typography": {
"description": "Material-UI Typography component",
"category": "display",
"props": {
"text": {
"type": "string",
"description": "Text content"
},
"variant": {
"type": "enum",
"values": ["h1", "h2", "h3", "h4", "h5", "h6", "subtitle1", "subtitle2", "body1", "body2", "caption", "button", "overline"],
"default": "body1",
"description": "Typography variant"
},
"color": {
"type": "string",
"description": "Text color (theme color or CSS color)"
},
"align": {
"type": "enum",
"values": ["left", "center", "right", "justify"],
"default": "left",
"description": "Text alignment"
},
"gutterBottom": {
"type": "boolean",
"default": false,
"description": "Add bottom margin"
},
"noWrap": {
"type": "boolean",
"default": false,
"description": "Prevent text wrapping"
}
}
},
"Box": {
"description": "Material-UI Box component - generic container",
"category": "layout",
"props": {
"sx": {
"type": "object",
"description": "Material-UI system styles"
},
"component": {
"type": "string",
"default": "div",
"description": "HTML element or React component to render"
}
}
},
"Grid": {
"description": "Material-UI Grid component",
"category": "layout",
"props": {
"container": {
"type": "boolean",
"default": false,
"description": "Whether this is a grid container"
},
"item": {
"type": "boolean",
"default": false,
"description": "Whether this is a grid item"
},
"spacing": {
"type": "number",
"description": "Spacing between grid items (0-10)"
},
"xs": {
"type": "number",
"description": "Grid columns on extra small screens (1-12)"
},
"sm": {
"type": "number",
"description": "Grid columns on small screens (1-12)"
},
"md": {
"type": "number",
"description": "Grid columns on medium screens (1-12)"
},
"lg": {
"type": "number",
"description": "Grid columns on large screens (1-12)"
},
"xl": {
"type": "number",
"description": "Grid columns on extra large screens (1-12)"
}
}
},
"Paper": {
"description": "Material-UI Paper component - surface with elevation",
"category": "layout",
"props": {
"elevation": {
"type": "number",
"default": 1,
"description": "Shadow depth (0-24)"
},
"variant": {
"type": "enum",
"values": ["elevation", "outlined"],
"default": "elevation",
"description": "Paper variant"
},
"square": {
"type": "boolean",
"default": false,
"description": "Whether corners are square (not rounded)"
}
}
},
"Card": {
"description": "Material-UI Card component",
"category": "layout",
"props": {
"raised": {
"type": "boolean",
"default": false,
"description": "Whether card has raised appearance"
}
}
},
"Dialog": {
"description": "Material-UI Dialog component",
"category": "feedback",
"props": {
"open": {
"type": "boolean",
"required": true,
"description": "Whether dialog is open"
},
"onClose": {
"type": "function",
"description": "Close handler function"
},
"maxWidth": {
"type": "enum",
"values": ["xs", "sm", "md", "lg", "xl", false],
"default": "sm",
"description": "Maximum width of dialog"
},
"fullWidth": {
"type": "boolean",
"default": false,
"description": "Whether dialog takes full available width"
},
"fullScreen": {
"type": "boolean",
"default": false,
"description": "Whether dialog is fullscreen"
}
}
},
"DataGrid": {
"description": "Custom DataGrid component for displaying tables",
"category": "display",
"props": {
"columns": {
"type": "array",
"required": true,
"description": "Column definitions"
},
"rows": {
"type": "array",
"required": true,
"description": "Data rows"
},
"loading": {
"type": "boolean",
"default": false,
"description": "Whether data is loading"
},
"primaryKey": {
"type": "string",
"default": "id",
"description": "Primary key field name"
},
"onEdit": {
"type": "function",
"description": "Edit row handler"
},
"onDelete": {
"type": "function",
"description": "Delete row handler"
},
"size": {
"type": "enum",
"values": ["small", "medium"],
"default": "medium",
"description": "Table size"
}
}
},
"Pagination": {
"description": "Material-UI Pagination component",
"category": "navigation",
"props": {
"count": {
"type": "number",
"required": true,
"description": "Total number of pages"
},
"page": {
"type": "number",
"required": true,
"description": "Current page number"
},
"onChange": {
"type": "function",
"description": "Page change handler"
},
"color": {
"type": "enum",
"values": ["primary", "secondary", "standard"],
"default": "standard",
"description": "Pagination color"
},
"size": {
"type": "enum",
"values": ["small", "medium", "large"],
"default": "medium",
"description": "Pagination size"
},
"showFirstButton": {
"type": "boolean",
"default": false,
"description": "Show first page button"
},
"showLastButton": {
"type": "boolean",
"default": false,
"description": "Show last page button"
}
}
},
"Tabs": {
"description": "Material-UI Tabs component",
"category": "navigation",
"props": {
"value": {
"type": "any",
"required": true,
"description": "Currently selected tab value"
},
"onChange": {
"type": "function",
"description": "Tab change handler"
},
"orientation": {
"type": "enum",
"values": ["horizontal", "vertical"],
"default": "horizontal",
"description": "Tab orientation"
},
"variant": {
"type": "enum",
"values": ["standard", "scrollable", "fullWidth"],
"default": "standard",
"description": "Tabs variant"
}
}
},
"Tab": {
"description": "Material-UI Tab component",
"category": "navigation",
"props": {
"label": {
"type": "string",
"required": true,
"description": "Tab label text"
},
"value": {
"type": "any",
"required": true,
"description": "Tab value"
},
"icon": {
"type": "string",
"description": "Icon name"
},
"disabled": {
"type": "boolean",
"default": false,
"description": "Whether tab is disabled"
}
}
},
"Icon": {
"description": "Material-UI Icon component",
"category": "display",
"props": {
"name": {
"type": "string",
"required": true,
"description": "Icon name from Material Icons"
},
"color": {
"type": "enum",
"values": ["inherit", "primary", "secondary", "action", "disabled", "error"],
"default": "inherit",
"description": "Icon color"
},
"fontSize": {
"type": "enum",
"values": ["small", "medium", "large", "inherit"],
"default": "medium",
"description": "Icon size"
}
}
},
"IconButton": {
"description": "Material-UI IconButton component",
"category": "inputs",
"props": {
"icon": {
"type": "string",
"required": true,
"description": "Icon name"
},
"color": {
"type": "enum",
"values": ["default", "primary", "secondary", "error", "warning", "info", "success"],
"default": "default",
"description": "Button color"
},
"size": {
"type": "enum",
"values": ["small", "medium", "large"],
"default": "medium",
"description": "Button size"
},
"disabled": {
"type": "boolean",
"default": false,
"description": "Whether button is disabled"
},
"onClick": {
"type": "function",
"description": "Click handler"
}
}
},
"Select": {
"description": "Material-UI Select component",
"category": "inputs",
"props": {
"label": {
"type": "string",
"description": "Select label"
},
"value": {
"type": "any",
"description": "Selected value"
},
"options": {
"type": "array",
"required": true,
"description": "Array of { value, label } options"
},
"variant": {
"type": "enum",
"values": ["standard", "outlined", "filled"],
"default": "outlined",
"description": "Select variant"
},
"fullWidth": {
"type": "boolean",
"default": false,
"description": "Whether select takes full width"
},
"multiple": {
"type": "boolean",
"default": false,
"description": "Allow multiple selections"
},
"onChange": {
"type": "function",
"description": "Change handler"
}
}
},
"Checkbox": {
"description": "Material-UI Checkbox component",
"category": "inputs",
"props": {
"checked": {
"type": "boolean",
"description": "Whether checkbox is checked"
},
"label": {
"type": "string",
"description": "Checkbox label"
},
"disabled": {
"type": "boolean",
"default": false,
"description": "Whether checkbox is disabled"
},
"onChange": {
"type": "function",
"description": "Change handler"
},
"color": {
"type": "enum",
"values": ["default", "primary", "secondary"],
"default": "primary",
"description": "Checkbox color"
}
}
},
"AppBar": {
"description": "Material-UI AppBar component",
"category": "layout",
"props": {
"position": {
"type": "enum",
"values": ["fixed", "absolute", "sticky", "static", "relative"],
"default": "fixed",
"description": "AppBar position"
},
"color": {
"type": "enum",
"values": ["default", "primary", "secondary", "transparent", "inherit"],
"default": "primary",
"description": "AppBar color"
},
"elevation": {
"type": "number",
"default": 4,
"description": "Shadow elevation"
}
}
},
"Toolbar": {
"description": "Material-UI Toolbar component",
"category": "layout",
"props": {
"variant": {
"type": "enum",
"values": ["regular", "dense"],
"default": "regular",
"description": "Toolbar variant"
},
"disableGutters": {
"type": "boolean",
"default": false,
"description": "Remove padding"
}
}
},
"Drawer": {
"description": "Material-UI Drawer component",
"category": "navigation",
"props": {
"open": {
"type": "boolean",
"description": "Whether drawer is open"
},
"variant": {
"type": "enum",
"values": ["permanent", "persistent", "temporary"],
"default": "temporary",
"description": "Drawer variant"
},
"anchor": {
"type": "enum",
"values": ["left", "right", "top", "bottom"],
"default": "left",
"description": "Drawer anchor position"
},
"onClose": {
"type": "function",
"description": "Close handler"
}
}
},
"Alert": {
"description": "Material-UI Alert component",
"category": "feedback",
"props": {
"severity": {
"type": "enum",
"values": ["error", "warning", "info", "success"],
"default": "info",
"description": "Alert severity"
},
"variant": {
"type": "enum",
"values": ["standard", "filled", "outlined"],
"default": "standard",
"description": "Alert variant"
},
"onClose": {
"type": "function",
"description": "Close button handler"
},
"text": {
"type": "string",
"description": "Alert message"
}
}
},
"CircularProgress": {
"description": "Material-UI CircularProgress component",
"category": "feedback",
"props": {
"size": {
"type": "number",
"default": 40,
"description": "Progress size in pixels"
},
"color": {
"type": "enum",
"values": ["primary", "secondary", "inherit"],
"default": "primary",
"description": "Progress color"
},
"variant": {
"type": "enum",
"values": ["determinate", "indeterminate"],
"default": "indeterminate",
"description": "Progress variant"
},
"value": {
"type": "number",
"description": "Progress value (0-100) for determinate"
}
}
}
},
"features": [
{
"id": "database-crud",

View File

@@ -28,6 +28,11 @@ import {
getUiView,
getComponentTree,
getAllComponentTrees,
getComponentPropSchema,
getAllComponentPropSchemas,
getComponentPropDefinition,
validateComponentProps,
getComponentsByCategory,
} from './featureConfig';
describe('FeatureConfig', () => {
@@ -1042,4 +1047,212 @@ describe('FeatureConfig', () => {
expect(trees.DashboardStatsCards).toBeDefined();
});
});
describe('getComponentPropSchema', () => {
it('should return prop schema for Button component', () => {
const schema = getComponentPropSchema('Button');
expect(schema).toBeDefined();
expect(schema?.description).toContain('Button');
expect(schema?.category).toBe('inputs');
expect(schema?.props).toBeDefined();
});
it('should have variant prop in Button schema', () => {
const schema = getComponentPropSchema('Button');
expect(schema?.props.variant).toBeDefined();
expect(schema?.props.variant.type).toBe('enum');
expect(schema?.props.variant.values).toContain('contained');
});
it('should return prop schema for TextField component', () => {
const schema = getComponentPropSchema('TextField');
expect(schema).toBeDefined();
expect(schema?.category).toBe('inputs');
expect(schema?.props.label).toBeDefined();
});
it('should return prop schema for Typography component', () => {
const schema = getComponentPropSchema('Typography');
expect(schema).toBeDefined();
expect(schema?.category).toBe('display');
expect(schema?.props.variant).toBeDefined();
});
it('should return undefined for non-existent component', () => {
const schema = getComponentPropSchema('NonExistentComponent');
expect(schema).toBeUndefined();
});
});
describe('getAllComponentPropSchemas', () => {
it('should return all component prop schemas', () => {
const schemas = getAllComponentPropSchemas();
expect(schemas).toBeDefined();
expect(typeof schemas).toBe('object');
});
it('should include Button schema', () => {
const schemas = getAllComponentPropSchemas();
expect(schemas.Button).toBeDefined();
});
it('should include TextField schema', () => {
const schemas = getAllComponentPropSchemas();
expect(schemas.TextField).toBeDefined();
});
it('should include DataGrid schema', () => {
const schemas = getAllComponentPropSchemas();
expect(schemas.DataGrid).toBeDefined();
});
});
describe('getComponentPropDefinition', () => {
it('should return prop definition for Button variant', () => {
const propDef = getComponentPropDefinition('Button', 'variant');
expect(propDef).toBeDefined();
expect(propDef?.type).toBe('enum');
expect(propDef?.default).toBe('text');
});
it('should return prop definition for TextField label', () => {
const propDef = getComponentPropDefinition('TextField', 'label');
expect(propDef).toBeDefined();
expect(propDef?.type).toBe('string');
});
it('should return prop definition for DataGrid columns', () => {
const propDef = getComponentPropDefinition('DataGrid', 'columns');
expect(propDef).toBeDefined();
expect(propDef?.type).toBe('array');
expect(propDef?.required).toBe(true);
});
it('should return undefined for non-existent prop', () => {
const propDef = getComponentPropDefinition('Button', 'nonExistentProp');
expect(propDef).toBeUndefined();
});
});
describe('validateComponentProps', () => {
it('should validate Button props successfully', () => {
const result = validateComponentProps('Button', {
text: 'Click me',
variant: 'contained',
color: 'primary',
});
expect(result.valid).toBe(true);
expect(result.errors.length).toBe(0);
});
it('should detect invalid enum value', () => {
const result = validateComponentProps('Button', {
variant: 'invalid',
});
expect(result.valid).toBe(false);
expect(result.errors.length).toBeGreaterThan(0);
expect(result.errors[0]).toContain('Invalid value');
});
it('should detect missing required prop', () => {
const result = validateComponentProps('DataGrid', {
rows: [],
});
expect(result.valid).toBe(false);
expect(result.errors.some(e => e.includes('columns'))).toBe(true);
});
it('should detect unknown prop', () => {
const result = validateComponentProps('Button', {
unknownProp: 'value',
});
expect(result.valid).toBe(false);
expect(result.errors.some(e => e.includes('Unknown prop'))).toBe(true);
});
it('should validate TextField props', () => {
const result = validateComponentProps('TextField', {
label: 'Name',
type: 'text',
value: 'John',
});
expect(result.valid).toBe(true);
});
it('should return valid for non-existent component', () => {
const result = validateComponentProps('NonExistent', {
anyProp: 'value',
});
expect(result.valid).toBe(true);
});
});
describe('getComponentsByCategory', () => {
it('should return all input components', () => {
const components = getComponentsByCategory('inputs');
expect(Array.isArray(components)).toBe(true);
expect(components).toContain('Button');
expect(components).toContain('TextField');
});
it('should return all layout components', () => {
const components = getComponentsByCategory('layout');
expect(Array.isArray(components)).toBe(true);
expect(components).toContain('Box');
expect(components).toContain('Grid');
expect(components).toContain('Paper');
});
it('should return all display components', () => {
const components = getComponentsByCategory('display');
expect(Array.isArray(components)).toBe(true);
expect(components).toContain('Typography');
expect(components).toContain('DataGrid');
});
it('should return all navigation components', () => {
const components = getComponentsByCategory('navigation');
expect(Array.isArray(components)).toBe(true);
expect(components).toContain('Tabs');
expect(components).toContain('Drawer');
});
it('should return all feedback components', () => {
const components = getComponentsByCategory('feedback');
expect(Array.isArray(components)).toBe(true);
expect(components).toContain('Dialog');
expect(components).toContain('Alert');
});
it('should return empty array for non-existent category', () => {
const components = getComponentsByCategory('nonexistent');
expect(Array.isArray(components)).toBe(true);
expect(components.length).toBe(0);
});
});
});

View File

@@ -182,6 +182,20 @@ export type ComponentNode = {
export type ComponentTree = ComponentNode;
export type PropDefinition = {
type: 'string' | 'number' | 'boolean' | 'array' | 'object' | 'function' | 'enum' | 'any';
description: string;
required?: boolean;
default?: any;
values?: any[];
};
export type ComponentPropSchema = {
description: string;
category: 'inputs' | 'display' | 'layout' | 'navigation' | 'feedback';
props: Record<string, PropDefinition>;
};
// Type definition for the features config structure
type FeaturesConfig = {
translations?: Translations;
@@ -198,6 +212,7 @@ type FeaturesConfig = {
relationships?: Record<string, Relationships>;
uiViews?: Record<string, Record<string, UiView>>;
componentTrees?: Record<string, ComponentTree>;
componentProps?: Record<string, ComponentPropSchema>;
features: Feature[];
dataTypes: DataType[];
constraintTypes?: ConstraintType[];
@@ -334,3 +349,63 @@ export function getComponentTree(treeName: string): ComponentTree | undefined {
export function getAllComponentTrees(): Record<string, ComponentTree> {
return config.componentTrees || {};
}
export function getComponentPropSchema(componentName: string): ComponentPropSchema | undefined {
return config.componentProps?.[componentName];
}
export function getAllComponentPropSchemas(): Record<string, ComponentPropSchema> {
return config.componentProps || {};
}
export function getComponentPropDefinition(componentName: string, propName: string): PropDefinition | undefined {
return config.componentProps?.[componentName]?.props[propName];
}
export function validateComponentProps(componentName: string, props: Record<string, any>): { valid: boolean; errors: string[] } {
const schema = getComponentPropSchema(componentName);
if (!schema) {
return { valid: true, errors: [] };
}
const errors: string[] = [];
// Check required props
Object.entries(schema.props).forEach(([propName, propDef]) => {
if (propDef.required && !(propName in props)) {
errors.push(`Missing required prop: ${propName}`);
}
});
// Check prop types
Object.entries(props).forEach(([propName, propValue]) => {
const propDef = schema.props[propName];
if (!propDef) {
errors.push(`Unknown prop: ${propName}`);
return;
}
// Type checking
if (propDef.type === 'enum' && propDef.values) {
if (!propDef.values.includes(propValue)) {
errors.push(`Invalid value for ${propName}: ${propValue}. Expected one of: ${propDef.values.join(', ')}`);
}
} else if (propDef.type !== 'any') {
const actualType = Array.isArray(propValue) ? 'array' : typeof propValue;
if (actualType !== propDef.type) {
errors.push(`Invalid type for ${propName}: expected ${propDef.type}, got ${actualType}`);
}
}
});
return { valid: errors.length === 0, errors };
}
export function getComponentsByCategory(category: string): string[] {
const schemas = getAllComponentPropSchemas();
return Object.entries(schemas)
.filter(([_, schema]) => schema.category === category)
.map(([name, _]) => name);
}