mirror of
https://github.com/johndoe6345789/postgres.git
synced 2026-04-25 14:25:06 +00:00
Add component tree definitions - build complete UIs from JSON configuration
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
@@ -17,6 +17,17 @@ import {
|
||||
getTableFeatures,
|
||||
getColumnFeatures,
|
||||
getComponentLayout,
|
||||
getFormSchema,
|
||||
getValidationRule,
|
||||
getApiEndpoints,
|
||||
getApiEndpoint,
|
||||
getPermissions,
|
||||
hasPermission,
|
||||
getRelationships,
|
||||
getUiViews,
|
||||
getUiView,
|
||||
getComponentTree,
|
||||
getAllComponentTrees,
|
||||
} from './featureConfig';
|
||||
|
||||
describe('FeatureConfig', () => {
|
||||
@@ -678,4 +689,357 @@ describe('FeatureConfig', () => {
|
||||
expect(layout).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFormSchema', () => {
|
||||
it('should return form schema for users table', () => {
|
||||
const schema = getFormSchema('users');
|
||||
|
||||
expect(schema).toBeDefined();
|
||||
expect(schema?.fields).toBeDefined();
|
||||
expect(Array.isArray(schema?.fields)).toBe(true);
|
||||
expect(schema?.submitLabel).toBe('Save User');
|
||||
expect(schema?.cancelLabel).toBe('Cancel');
|
||||
});
|
||||
|
||||
it('should have name field in users schema', () => {
|
||||
const schema = getFormSchema('users');
|
||||
const nameField = schema?.fields.find(f => f.name === 'name');
|
||||
|
||||
expect(nameField).toBeDefined();
|
||||
expect(nameField?.type).toBe('text');
|
||||
expect(nameField?.required).toBe(true);
|
||||
});
|
||||
|
||||
it('should return form schema for products table', () => {
|
||||
const schema = getFormSchema('products');
|
||||
|
||||
expect(schema).toBeDefined();
|
||||
expect(schema?.fields).toBeDefined();
|
||||
expect(schema?.submitLabel).toBe('Save Product');
|
||||
});
|
||||
|
||||
it('should have price field with number type in products schema', () => {
|
||||
const schema = getFormSchema('products');
|
||||
const priceField = schema?.fields.find(f => f.name === 'price');
|
||||
|
||||
expect(priceField).toBeDefined();
|
||||
expect(priceField?.type).toBe('number');
|
||||
expect(priceField?.required).toBe(true);
|
||||
expect(priceField?.prefix).toBe('$');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getValidationRule', () => {
|
||||
it('should return validation rule for email', () => {
|
||||
const rule = getValidationRule('email');
|
||||
|
||||
expect(rule).toBeDefined();
|
||||
expect(rule?.pattern).toBeDefined();
|
||||
expect(rule?.message).toContain('email');
|
||||
});
|
||||
|
||||
it('should return validation rule for phone', () => {
|
||||
const rule = getValidationRule('phone');
|
||||
|
||||
expect(rule).toBeDefined();
|
||||
expect(rule?.pattern).toBeDefined();
|
||||
expect(rule?.message).toContain('phone');
|
||||
});
|
||||
|
||||
it('should return validation rule for number', () => {
|
||||
const rule = getValidationRule('number');
|
||||
|
||||
expect(rule).toBeDefined();
|
||||
expect(rule?.message).toContain('number');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getApiEndpoints', () => {
|
||||
it('should return all endpoints for users resource', () => {
|
||||
const endpoints = getApiEndpoints('users');
|
||||
|
||||
expect(endpoints).toBeDefined();
|
||||
expect(endpoints?.list).toBeDefined();
|
||||
expect(endpoints?.get).toBeDefined();
|
||||
expect(endpoints?.create).toBeDefined();
|
||||
expect(endpoints?.update).toBeDefined();
|
||||
expect(endpoints?.delete).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return all endpoints for products resource', () => {
|
||||
const endpoints = getApiEndpoints('products');
|
||||
|
||||
expect(endpoints).toBeDefined();
|
||||
expect(endpoints?.list).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getApiEndpoint', () => {
|
||||
it('should return list endpoint for users', () => {
|
||||
const endpoint = getApiEndpoint('users', 'list');
|
||||
|
||||
expect(endpoint).toBeDefined();
|
||||
expect(endpoint?.method).toBe('GET');
|
||||
expect(endpoint?.path).toBe('/api/admin/users');
|
||||
});
|
||||
|
||||
it('should return create endpoint for users', () => {
|
||||
const endpoint = getApiEndpoint('users', 'create');
|
||||
|
||||
expect(endpoint).toBeDefined();
|
||||
expect(endpoint?.method).toBe('POST');
|
||||
expect(endpoint?.path).toBe('/api/admin/users');
|
||||
});
|
||||
|
||||
it('should return update endpoint for products', () => {
|
||||
const endpoint = getApiEndpoint('products', 'update');
|
||||
|
||||
expect(endpoint).toBeDefined();
|
||||
expect(endpoint?.method).toBe('PUT');
|
||||
expect(endpoint?.path).toBe('/api/admin/products/:id');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPermissions', () => {
|
||||
it('should return permissions for users resource', () => {
|
||||
const permissions = getPermissions('users');
|
||||
|
||||
expect(permissions).toBeDefined();
|
||||
expect(permissions?.create).toContain('admin');
|
||||
expect(permissions?.read).toContain('admin');
|
||||
expect(permissions?.read).toContain('user');
|
||||
});
|
||||
|
||||
it('should return permissions for products resource', () => {
|
||||
const permissions = getPermissions('products');
|
||||
|
||||
expect(permissions).toBeDefined();
|
||||
expect(permissions?.create).toContain('admin');
|
||||
expect(permissions?.create).toContain('editor');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasPermission', () => {
|
||||
it('should return true when user has permission', () => {
|
||||
expect(hasPermission('users', 'create', 'admin')).toBe(true);
|
||||
expect(hasPermission('users', 'read', 'user')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when user does not have permission', () => {
|
||||
expect(hasPermission('users', 'create', 'user')).toBe(false);
|
||||
expect(hasPermission('users', 'delete', 'guest')).toBe(false);
|
||||
});
|
||||
|
||||
it('should check product permissions correctly', () => {
|
||||
expect(hasPermission('products', 'create', 'editor')).toBe(true);
|
||||
expect(hasPermission('products', 'update', 'editor')).toBe(true);
|
||||
expect(hasPermission('products', 'delete', 'editor')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRelationships', () => {
|
||||
it('should return relationships for users table', () => {
|
||||
const relationships = getRelationships('users');
|
||||
|
||||
expect(relationships).toBeDefined();
|
||||
expect(relationships?.hasMany).toContain('orders');
|
||||
expect(relationships?.hasMany).toContain('reviews');
|
||||
});
|
||||
|
||||
it('should return relationships for products table', () => {
|
||||
const relationships = getRelationships('products');
|
||||
|
||||
expect(relationships).toBeDefined();
|
||||
expect(relationships?.hasMany).toContain('reviews');
|
||||
expect(relationships?.belongsTo).toContain('category');
|
||||
});
|
||||
|
||||
it('should return relationships for orders table', () => {
|
||||
const relationships = getRelationships('orders');
|
||||
|
||||
expect(relationships).toBeDefined();
|
||||
expect(relationships?.belongsTo).toContain('users');
|
||||
expect(relationships?.hasMany).toContain('orderItems');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUiViews', () => {
|
||||
it('should return all views for users resource', () => {
|
||||
const views = getUiViews('users');
|
||||
|
||||
expect(views).toBeDefined();
|
||||
expect(views?.list).toBeDefined();
|
||||
expect(views?.detail).toBeDefined();
|
||||
expect(views?.create).toBeDefined();
|
||||
expect(views?.edit).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return all views for products resource', () => {
|
||||
const views = getUiViews('products');
|
||||
|
||||
expect(views).toBeDefined();
|
||||
expect(views?.list).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUiView', () => {
|
||||
it('should return list view configuration for users', () => {
|
||||
const view = getUiView('users', 'list');
|
||||
|
||||
expect(view).toBeDefined();
|
||||
expect(view?.component).toBe('DataGrid');
|
||||
expect(view?.showActions).toBe(true);
|
||||
expect(view?.showSearch).toBe(true);
|
||||
expect(view?.showFilters).toBe(true);
|
||||
});
|
||||
|
||||
it('should return detail view configuration for users', () => {
|
||||
const view = getUiView('users', 'detail');
|
||||
|
||||
expect(view).toBeDefined();
|
||||
expect(view?.component).toBe('DetailView');
|
||||
expect(view?.showRelated).toBe(true);
|
||||
expect(view?.tabs).toContain('info');
|
||||
expect(view?.tabs).toContain('orders');
|
||||
});
|
||||
|
||||
it('should return create view configuration with redirect', () => {
|
||||
const view = getUiView('users', 'create');
|
||||
|
||||
expect(view).toBeDefined();
|
||||
expect(view?.component).toBe('FormDialog');
|
||||
expect(view?.redirect).toBe('list');
|
||||
});
|
||||
|
||||
it('should return edit view configuration for products', () => {
|
||||
const view = getUiView('products', 'edit');
|
||||
|
||||
expect(view).toBeDefined();
|
||||
expect(view?.redirect).toBe('detail');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getComponentTree', () => {
|
||||
it('should return component tree for AdminDashboard', () => {
|
||||
const tree = getComponentTree('AdminDashboard');
|
||||
|
||||
expect(tree).toBeDefined();
|
||||
expect(tree?.component).toBe('Box');
|
||||
expect(tree?.children).toBeDefined();
|
||||
expect(Array.isArray(tree?.children)).toBe(true);
|
||||
});
|
||||
|
||||
it('should have Sidebar in AdminDashboard tree', () => {
|
||||
const tree = getComponentTree('AdminDashboard');
|
||||
const sidebar = tree?.children?.find(child => child.component === 'Sidebar');
|
||||
|
||||
expect(sidebar).toBeDefined();
|
||||
expect(sidebar?.props?.width).toBe(240);
|
||||
});
|
||||
|
||||
it('should return component tree for ResourceListPage', () => {
|
||||
const tree = getComponentTree('ResourceListPage');
|
||||
|
||||
expect(tree).toBeDefined();
|
||||
expect(tree?.component).toBe('Box');
|
||||
expect(tree?.children).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have DataGrid in ResourceListPage tree', () => {
|
||||
const tree = getComponentTree('ResourceListPage');
|
||||
|
||||
function findComponent(node: any, componentName: string): any {
|
||||
if (node.component === componentName) return node;
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
const found = findComponent(child, componentName);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const dataGrid = findComponent(tree, 'DataGrid');
|
||||
expect(dataGrid).toBeDefined();
|
||||
expect(dataGrid?.dataSource).toBe('tableData');
|
||||
});
|
||||
|
||||
it('should return component tree for FormDialogTree', () => {
|
||||
const tree = getComponentTree('FormDialogTree');
|
||||
|
||||
expect(tree).toBeDefined();
|
||||
expect(tree?.component).toBe('Dialog');
|
||||
});
|
||||
|
||||
it('should have conditional rendering in component tree', () => {
|
||||
const tree = getComponentTree('ResourceListPage');
|
||||
|
||||
function findNodeWithCondition(node: any): any {
|
||||
if (node.condition) return node;
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
const found = findNodeWithCondition(child);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const conditionalNode = findNodeWithCondition(tree);
|
||||
expect(conditionalNode).toBeDefined();
|
||||
expect(conditionalNode?.condition).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have forEach loops in component tree', () => {
|
||||
const tree = getComponentTree('ResourceDetailPage');
|
||||
|
||||
function findNodeWithForEach(node: any): any {
|
||||
if (node.forEach) return node;
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
const found = findNodeWithForEach(child);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const loopNode = findNodeWithForEach(tree);
|
||||
expect(loopNode).toBeDefined();
|
||||
expect(loopNode?.forEach).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllComponentTrees', () => {
|
||||
it('should return all component trees', () => {
|
||||
const trees = getAllComponentTrees();
|
||||
|
||||
expect(trees).toBeDefined();
|
||||
expect(typeof trees).toBe('object');
|
||||
});
|
||||
|
||||
it('should include AdminDashboard tree', () => {
|
||||
const trees = getAllComponentTrees();
|
||||
|
||||
expect(trees.AdminDashboard).toBeDefined();
|
||||
});
|
||||
|
||||
it('should include ResourceListPage tree', () => {
|
||||
const trees = getAllComponentTrees();
|
||||
|
||||
expect(trees.ResourceListPage).toBeDefined();
|
||||
});
|
||||
|
||||
it('should include FormDialogTree tree', () => {
|
||||
const trees = getAllComponentTrees();
|
||||
|
||||
expect(trees.FormDialogTree).toBeDefined();
|
||||
});
|
||||
|
||||
it('should include DashboardStatsCards tree', () => {
|
||||
const trees = getAllComponentTrees();
|
||||
|
||||
expect(trees.DashboardStatsCards).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -109,6 +109,79 @@ export type ComponentLayout = {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type FormField = {
|
||||
name: string;
|
||||
type: 'text' | 'email' | 'number' | 'textarea' | 'select' | 'checkbox' | 'date' | 'datetime';
|
||||
label: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
rows?: number;
|
||||
defaultValue?: any;
|
||||
options?: Array<{ value: string; label: string }>;
|
||||
validation?: string;
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
};
|
||||
|
||||
export type FormSchema = {
|
||||
fields: FormField[];
|
||||
submitLabel: string;
|
||||
cancelLabel: string;
|
||||
};
|
||||
|
||||
export type ValidationRule = {
|
||||
pattern: string;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type ApiEndpoint = {
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
path: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type Permissions = {
|
||||
create?: string[];
|
||||
read?: string[];
|
||||
update?: string[];
|
||||
delete?: string[];
|
||||
};
|
||||
|
||||
export type Relationships = {
|
||||
hasMany?: string[];
|
||||
belongsTo?: string[];
|
||||
hasOne?: string[];
|
||||
belongsToMany?: string[];
|
||||
};
|
||||
|
||||
export type UiView = {
|
||||
component: string;
|
||||
showActions?: boolean;
|
||||
showSearch?: boolean;
|
||||
showFilters?: boolean;
|
||||
showExport?: boolean;
|
||||
showRelated?: boolean;
|
||||
tabs?: string[];
|
||||
redirect?: string;
|
||||
};
|
||||
|
||||
export type ComponentNode = {
|
||||
component: string;
|
||||
props?: Record<string, any>;
|
||||
children?: ComponentNode[];
|
||||
condition?: string;
|
||||
forEach?: string;
|
||||
dataSource?: string;
|
||||
comment?: string;
|
||||
};
|
||||
|
||||
export type ComponentTree = ComponentNode;
|
||||
|
||||
// Type definition for the features config structure
|
||||
type FeaturesConfig = {
|
||||
translations?: Translations;
|
||||
@@ -118,6 +191,13 @@ type FeaturesConfig = {
|
||||
tableFeatures?: Record<string, TableFeatures>;
|
||||
columnFeatures?: Record<string, ColumnFeatures>;
|
||||
componentLayouts?: Record<string, ComponentLayout>;
|
||||
formSchemas?: Record<string, FormSchema>;
|
||||
validationRules?: Record<string, ValidationRule>;
|
||||
apiEndpoints?: Record<string, Record<string, ApiEndpoint>>;
|
||||
permissions?: Record<string, Permissions>;
|
||||
relationships?: Record<string, Relationships>;
|
||||
uiViews?: Record<string, Record<string, UiView>>;
|
||||
componentTrees?: Record<string, ComponentTree>;
|
||||
features: Feature[];
|
||||
dataTypes: DataType[];
|
||||
constraintTypes?: ConstraintType[];
|
||||
@@ -208,3 +288,49 @@ export function getColumnFeatures(columnName: string): ColumnFeatures | undefine
|
||||
export function getComponentLayout(componentName: string): ComponentLayout | undefined {
|
||||
return config.componentLayouts?.[componentName];
|
||||
}
|
||||
|
||||
export function getFormSchema(tableName: string): FormSchema | undefined {
|
||||
return config.formSchemas?.[tableName];
|
||||
}
|
||||
|
||||
export function getValidationRule(ruleName: string): ValidationRule | undefined {
|
||||
return config.validationRules?.[ruleName];
|
||||
}
|
||||
|
||||
export function getApiEndpoints(resourceName: string): Record<string, ApiEndpoint> | undefined {
|
||||
return config.apiEndpoints?.[resourceName];
|
||||
}
|
||||
|
||||
export function getApiEndpoint(resourceName: string, action: string): ApiEndpoint | undefined {
|
||||
return config.apiEndpoints?.[resourceName]?.[action];
|
||||
}
|
||||
|
||||
export function getPermissions(resourceName: string): Permissions | undefined {
|
||||
return config.permissions?.[resourceName];
|
||||
}
|
||||
|
||||
export function hasPermission(resourceName: string, action: string, userRole: string): boolean {
|
||||
const permissions = config.permissions?.[resourceName];
|
||||
const allowedRoles = permissions?.[action as keyof Permissions];
|
||||
return allowedRoles?.includes(userRole) ?? false;
|
||||
}
|
||||
|
||||
export function getRelationships(tableName: string): Relationships | undefined {
|
||||
return config.relationships?.[tableName];
|
||||
}
|
||||
|
||||
export function getUiViews(resourceName: string): Record<string, UiView> | undefined {
|
||||
return config.uiViews?.[resourceName];
|
||||
}
|
||||
|
||||
export function getUiView(resourceName: string, viewName: string): UiView | undefined {
|
||||
return config.uiViews?.[resourceName]?.[viewName];
|
||||
}
|
||||
|
||||
export function getComponentTree(treeName: string): ComponentTree | undefined {
|
||||
return config.componentTrees?.[treeName];
|
||||
}
|
||||
|
||||
export function getAllComponentTrees(): Record<string, ComponentTree> {
|
||||
return config.componentTrees || {};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user