/**
* Block Tester Utility Functions
*/
import { cn } from '../../lib/utils';
import {
WorkingBlock,
ValidationResult,
ValidationError,
ValidationWarning,
AttributeSchema,
BlockCategory,
} from '../../types/block-tester.types';
export { cn };
/**
* Generate a unique ID
*/
export function generateId(): string {
return Math.random().toString(36).substring(2) + Date.now().toString(36);
}
/**
* Create a new working block with default values
*/
export function createDefaultBlock(): WorkingBlock {
return {
id: generateId(),
name: 'untitled-block',
title: 'Untitled Block',
description: '',
category: 'display' as BlockCategory,
template: DEFAULT_TEMPLATE,
templateSource: {
type: 'inline',
value: DEFAULT_TEMPLATE,
},
attributes: {
title: {
type: 'string',
label: 'Title',
description: 'Block title',
required: true,
default: 'Untitled Block',
placeholder: 'Enter block title...',
},
description: {
type: 'string',
label: 'Description',
description: 'Block description',
required: false,
default: '',
placeholder: 'Enter block description...',
},
},
actions: {},
keywords: [],
icon: 'block',
created: new Date(),
modified: new Date(),
};
}
/**
* Default template for new blocks
*/
export const DEFAULT_TEMPLATE = `
{{attributes.title}}
{{attributes.description}}
Created with Block Tester
`;
/**
* Validate a working block
*/
export function validateBlock(block: WorkingBlock): ValidationResult {
const errors: ValidationError[] = [];
const warnings: ValidationWarning[] = [];
if (!block.name || block.name.trim() === '') {
errors.push({
field: 'name',
message: 'Block name is required',
severity: 'error',
});
} else if (!/^[a-z0-9-]+$/.test(block.name)) {
errors.push({
field: 'name',
message:
'Block name must contain only lowercase letters, numbers, and hyphens',
severity: 'error',
});
}
if (!block.title || block.title.trim() === '') {
errors.push({
field: 'title',
message: 'Block title is required',
severity: 'error',
});
}
if (!block.template || block.template.trim() === '') {
errors.push({
field: 'template',
message: 'Block template is required',
severity: 'error',
});
} else {
const templateIssues = validateTemplate(block.template, block.attributes);
errors.push(...templateIssues.errors);
warnings.push(...templateIssues.warnings);
}
Object.entries(block.attributes).forEach(([key, schema]) => {
if (!schema.label) {
warnings.push({
field: `attributes.${key}.label`,
message: `Attribute "${key}" should have a label for better UX`,
suggestion: 'Add a descriptive label for this attribute',
});
}
});
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate template syntax and structure
*/
export function validateTemplate(
template: string,
attributes: Record
): { errors: ValidationError[]; warnings: ValidationWarning[] } {
const errors: ValidationError[] = [];
const warnings: ValidationWarning[] = [];
const openTags = template.match(/<[^/][^>]*>/g) || [];
const closeTags = template.match(/<\/[^>]*>/g) || [];
if (openTags.length !== closeTags.length) {
warnings.push({
field: 'template',
message: 'Template may have unbalanced HTML tags',
suggestion: 'Check that all opening tags have corresponding closing tags',
});
}
const handlebarsMatches = template.match(/\{\{[^}]+\}\}/g) || [];
handlebarsMatches.forEach((match: string) => {
const expression = match.slice(2, -2).trim();
if (expression.startsWith('attributes.')) {
const attributeName = expression.split('.')[1];
if (!attributes[attributeName]) {
warnings.push({
field: 'template',
message: `Template references undefined attribute "${attributeName}"`,
suggestion: `Add "${attributeName}" to block attributes or remove the reference`,
});
}
}
if (expression.startsWith('actions.')) {
warnings.push({
field: 'template',
message:
'Action references in templates are not yet supported in preview mode',
suggestion:
'Action bindings will work when deployed but not in preview',
});
}
});
if (template.includes('
${processedTemplate}