/** * Validator - Functions to validate user CSS code */ /** * Validate user CSS code against the lesson requirements * @param {string} userCode - User submitted CSS code * @param {Object} lesson - The current lesson object * @returns {Object} Validation result with isValid and message properties */ export function validateUserCode(userCode, lesson) { if (!lesson || !lesson.validations) { return { isValid: true, message: 'No validations specified for this lesson.' }; } // Get the validations array from the lesson const validations = lesson.validations; // Default validation result let result = { isValid: true, message: 'Your code looks good!' }; // Process each validation rule for (const validation of validations) { const { type, value, message, options } = validation; switch (type) { case 'contains': if (!containsValidation(userCode, value, options)) { return { isValid: false, message: message || `Your code should include "${value}".` }; } break; case 'not_contains': if (containsValidation(userCode, value, options)) { return { isValid: false, message: message || `Your code should not include "${value}".` }; } break; case 'regex': if (!regexValidation(userCode, value, options)) { return { isValid: false, message: message || 'Your code does not match the expected pattern.' }; } break; case 'property_value': if (!propertyValueValidation(userCode, value, options)) { return { isValid: false, message: message || `The "${value.property}" property should be set to "${value.expected}".` }; } break; case 'syntax': const syntaxResult = syntaxValidation(userCode); if (!syntaxResult.isValid) { return { isValid: false, message: message || `CSS syntax error: ${syntaxResult.error}` }; } break; case 'custom': if (validation.validator && typeof validation.validator === 'function') { const customResult = validation.validator(userCode); if (!customResult.isValid) { return { isValid: false, message: customResult.message || message || 'Your code does not meet the requirements.' }; } } break; // Add more validation types as needed default: console.warn(`Unknown validation type: ${type}`); } } // If we've passed all validations, return success return result; } /** * Check if code contains a specific string or pattern * @param {string} code - User CSS code * @param {string} value - String to check for * @param {Object} options - Validation options * @returns {boolean} Whether the validation passes */ function containsValidation(code, value, options = {}) { const { caseSensitive = true, wholeWord = false } = options; if (!caseSensitive) { code = code.toLowerCase(); value = value.toLowerCase(); } if (wholeWord) { const regex = new RegExp(`\\b${escapeRegExp(value)}\\b`, caseSensitive ? '' : 'i'); return regex.test(code); } return code.includes(value); } /** * Check if code matches a regex pattern * @param {string} code - User CSS code * @param {string} pattern - Regex pattern to check * @param {Object} options - Validation options * @returns {boolean} Whether the validation passes */ function regexValidation(code, pattern, options = {}) { const { caseSensitive = true, multiline = true } = options; let flags = ''; if (!caseSensitive) flags += 'i'; if (multiline) flags += 'm'; try { const regex = new RegExp(pattern, flags); return regex.test(code); } catch (e) { console.error('Invalid regex in validation:', e); return false; } } /** * Check if a CSS property has the expected value * @param {string} code - User CSS code * @param {Object} value - Object with property and expected value * @param {Object} options - Validation options * @returns {boolean} Whether the validation passes */ function propertyValueValidation(code, value, options = {}) { const { property, expected } = value; const { exact = false } = options; // Create a regex to extract the property value // This is a simplified version and might not handle all CSS syntax nuances const propertyRegex = new RegExp(`${escapeRegExp(property)}\\s*:\\s*([^;\\}]+)`, 'i'); const match = code.match(propertyRegex); if (!match) { // Property not found return false; } const actualValue = match[1].trim(); if (exact) { return actualValue === expected; } else { // Allow for flexible matching return actualValue.toLowerCase().includes(expected.toLowerCase()); } } /** * Validate CSS syntax * @param {string} code - User CSS code * @returns {Object} Validation result */ function syntaxValidation(code) { try { // Create a hidden style element to test the CSS const style = document.createElement('style'); style.textContent = code; document.head.appendChild(style); document.head.removeChild(style); return { isValid: true }; } catch (e) { return { isValid: false, error: e.message }; } } /** * Escape string for safe use in regex * @param {string} string - String to escape * @returns {string} Escaped string */ function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }