@dkoul/auto-testid-core
Version:
Core AST parsing and transformation logic for React and Vue.js attribute generation
318 lines • 10.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validation = exports.ValidationUtils = void 0;
class ValidationUtils {
/**
* Validate element structure
*/
static validateElement(element) {
const errors = [];
// Validate required fields
if (!element.tag || typeof element.tag !== 'string') {
errors.push({
message: 'Element tag is required and must be a string',
path: 'tag',
value: element.tag,
});
}
if (!element.attributes || typeof element.attributes !== 'object') {
errors.push({
message: 'Element attributes must be an object',
path: 'attributes',
value: element.attributes,
});
}
// Validate position if provided
if (element.position) {
if (typeof element.position.line !== 'number' || element.position.line < 0) {
errors.push({
message: 'Position line must be a non-negative number',
path: 'position.line',
value: element.position.line,
});
}
if (typeof element.position.column !== 'number' || element.position.column < 0) {
errors.push({
message: 'Position column must be a non-negative number',
path: 'position.column',
value: element.position.column,
});
}
}
// Validate framework
if (element.framework) {
const validFrameworks = ['react', 'vue', 'angular', 'html'];
if (!validFrameworks.includes(element.framework)) {
errors.push({
message: `Invalid framework: ${element.framework}`,
path: 'framework',
value: element.framework,
});
}
}
return {
valid: errors.length === 0,
errors,
warnings: [],
};
}
/**
* Validate file path
*/
static validateFilePath(filePath) {
const errors = [];
if (!filePath || typeof filePath !== 'string') {
errors.push({
message: 'File path is required and must be a string',
path: 'filePath',
value: filePath,
});
}
if (filePath && filePath.trim() === '') {
errors.push({
message: 'File path cannot be empty',
path: 'filePath',
value: filePath,
});
}
return {
valid: errors.length === 0,
errors,
warnings: [],
};
}
/**
* Validate test ID format
*/
static validateTestId(id, maxLength) {
const errors = [];
const warnings = [];
if (!id || typeof id !== 'string') {
errors.push({
message: 'Test ID is required and must be a string',
path: 'id',
value: id,
});
return { valid: false, errors, warnings };
}
// Check length
if (maxLength && id.length > maxLength) {
errors.push({
message: `Test ID exceeds maximum length of ${maxLength} characters`,
path: 'id',
value: id,
});
}
// Check for invalid characters
if (!/^[a-zA-Z0-9_-]+$/.test(id)) {
errors.push({
message: 'Test ID contains invalid characters. Only alphanumeric, hyphens, and underscores are allowed',
path: 'id',
value: id,
});
}
// Check if starts with number
if (/^[0-9]/.test(id)) {
warnings.push({
message: 'Test ID starts with a number, which may cause issues in some frameworks',
path: 'id',
value: id,
});
}
// Check for common patterns that might be problematic
if (id.includes('--') || id.includes('__')) {
warnings.push({
message: 'Test ID contains double hyphens or underscores, which may cause conflicts',
path: 'id',
value: id,
});
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate framework detection
*/
static validateFramework(framework) {
const errors = [];
const validFrameworks = ['react', 'vue', 'angular', 'html'];
if (!validFrameworks.includes(framework)) {
errors.push({
message: `Invalid framework: ${framework}`,
path: 'framework',
value: framework,
});
}
return {
valid: errors.length === 0,
errors,
warnings: [],
};
}
/**
* Validate HTML tag name
*/
static validateTagName(tag) {
const errors = [];
const warnings = [];
if (!tag || typeof tag !== 'string') {
errors.push({
message: 'Tag name is required and must be a string',
path: 'tag',
value: tag,
});
return { valid: false, errors, warnings };
}
// Check for valid HTML tag format
if (!/^[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z0-9]+)*$/.test(tag)) {
if (tag.includes('-')) {
// Might be a custom element
warnings.push({
message: 'Tag name appears to be a custom element',
path: 'tag',
value: tag,
});
}
else {
errors.push({
message: 'Invalid tag name format',
path: 'tag',
value: tag,
});
}
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* Sanitize and normalize test ID
*/
static sanitizeTestId(id, maxLength) {
if (!id || typeof id !== 'string') {
return '';
}
// Convert to lowercase and replace invalid characters
let sanitized = id
.toLowerCase()
.replace(/[^a-z0-9_-]/g, '-')
.replace(/^[0-9]+/, '') // Remove leading numbers
.replace(/-+/g, '-') // Replace multiple hyphens with single
.replace(/^-+|-+$/g, ''); // Remove leading/trailing hyphens
// Truncate if necessary
if (maxLength && sanitized.length > maxLength) {
sanitized = sanitized.substring(0, maxLength).replace(/-+$/, '');
}
// Ensure it's not empty
if (!sanitized) {
sanitized = 'element';
}
return sanitized;
}
/**
* Check if element should have test ID
*/
static shouldAddTestId(element, includeElementTypes) {
// Skip if already has data-testid
if (element.attributes['data-testid']) {
return false;
}
// Check if element type is in include list
if (!includeElementTypes.includes(element.tag.toLowerCase())) {
return false;
}
// Skip certain elements that typically don't need test IDs
const skipElements = ['html', 'head', 'meta', 'title', 'style', 'script', 'link'];
if (skipElements.includes(element.tag.toLowerCase())) {
return false;
}
return true;
}
/**
* Validate attribute value
*/
static validateAttributeValue(value) {
const errors = [];
const warnings = [];
if (value === null || value === undefined) {
errors.push({
message: 'Attribute value cannot be null or undefined',
path: 'value',
value: value,
});
return { valid: false, errors, warnings };
}
if (typeof value !== 'string') {
warnings.push({
message: 'Attribute value should be a string',
path: 'value',
value: value,
});
}
// Check for potentially dangerous values
if (typeof value === 'string') {
if (value.includes('<script>') || value.includes('javascript:')) {
errors.push({
message: 'Attribute value contains potentially dangerous content',
path: 'value',
value: value,
});
}
// Check for extremely long values
if (value.length > 1000) {
warnings.push({
message: 'Attribute value is very long and may cause performance issues',
path: 'value',
value: value,
});
}
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate unique ID within scope
*/
static validateUniqueness(id, existingIds) {
const errors = [];
if (existingIds.has(id)) {
errors.push({
message: `Test ID '${id}' already exists in the current scope`,
path: 'id',
value: id,
});
}
return {
valid: errors.length === 0,
errors,
warnings: [],
};
}
/**
* Combine multiple validation results
*/
static combineValidationResults(results) {
const allErrors = [];
const allWarnings = [];
results.forEach(result => {
allErrors.push(...result.errors);
allWarnings.push(...result.warnings);
});
return {
valid: allErrors.length === 0,
errors: allErrors,
warnings: allWarnings,
};
}
}
exports.ValidationUtils = ValidationUtils;
exports.validation = new ValidationUtils();
//# sourceMappingURL=validation.js.map