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

@@ -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);
}