mirror of
https://github.com/johndoe6345789/postgres.git
synced 2026-05-01 01:05:02 +00:00
Add component props definitions with validation and type checking
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user