235 lines
6.2 KiB
JavaScript
235 lines
6.2 KiB
JavaScript
/**
|
|
* 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,
|
|
validCases: 0,
|
|
totalCases: validations.length ?? 0,
|
|
message: "Your code looks CRISPY!"
|
|
};
|
|
|
|
// Process each validation rule
|
|
for (const validation of validations) {
|
|
const { type, value, message, options } = validation;
|
|
let validationPassed = false;
|
|
|
|
switch (type) {
|
|
case "contains":
|
|
validationPassed = containsValidation(userCode, value, options);
|
|
if (!validationPassed) {
|
|
result = {
|
|
isValid: false,
|
|
validCases: result.validCases,
|
|
totalCases: result.totalCases,
|
|
message: message || `Your code should include "${value}".`
|
|
};
|
|
}
|
|
break;
|
|
|
|
case "not_contains":
|
|
validationPassed = !containsValidation(userCode, value, options);
|
|
if (!validationPassed) {
|
|
result = {
|
|
isValid: false,
|
|
validCases: result.validCases,
|
|
totalCases: result.totalCases,
|
|
message: message || `Your code should not include "${value}".`
|
|
};
|
|
}
|
|
break;
|
|
|
|
case "regex":
|
|
validationPassed = regexValidation(userCode, value, options);
|
|
if (!validationPassed) {
|
|
result = {
|
|
isValid: false,
|
|
validCases: result.validCases,
|
|
totalCases: result.totalCases,
|
|
message: message || "Your code does not match the expected pattern."
|
|
};
|
|
}
|
|
break;
|
|
|
|
case "property_value":
|
|
validationPassed = propertyValueValidation(userCode, value, options);
|
|
if (!validationPassed) {
|
|
result = {
|
|
isValid: false,
|
|
validCases: result.validCases,
|
|
totalCases: result.totalCases,
|
|
message: message || `The "${value.property}" property should be set to "${value.expected}".`
|
|
};
|
|
}
|
|
break;
|
|
|
|
case "syntax":
|
|
const syntaxResult = syntaxValidation(userCode);
|
|
validationPassed = syntaxResult.isValid;
|
|
if (!validationPassed) {
|
|
result = {
|
|
isValid: false,
|
|
validCases: result.validCases,
|
|
totalCases: result.totalCases,
|
|
message: message || `CSS syntax error: ${syntaxResult.error}`
|
|
};
|
|
}
|
|
break;
|
|
|
|
case "custom":
|
|
if (validation.validator && typeof validation.validator === "function") {
|
|
const customResult = validation.validator(userCode);
|
|
validationPassed = customResult.isValid;
|
|
if (!validationPassed) {
|
|
result = {
|
|
isValid: false,
|
|
validCases: result.validCases,
|
|
totalCases: result.totalCases,
|
|
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}`);
|
|
continue; // Skip counting this validation
|
|
}
|
|
|
|
// Count valid cases
|
|
if (validationPassed) {
|
|
result.validCases++;
|
|
}
|
|
|
|
// Return early if validation failed
|
|
if (!validationPassed) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// If we've passed all validations, return success with all cases passed
|
|
result.validCases = validations.length;
|
|
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, "\\$&");
|
|
}
|