WIP: enhance validation feedback in code editor, add support for multiple validation indicators and new validation types

This commit is contained in:
Michael Czechowski
2025-05-19 12:55:43 +02:00
parent 4f5154b795
commit e4d03c6b7f
7 changed files with 230 additions and 81 deletions

View File

@@ -299,6 +299,38 @@ function runCode() {
const validationResult = validateUserCode(userCode, lesson);
// Remove any existing validation indicators before adding new ones
const existingIndicators = elements.codeEditor.querySelectorAll(".validation-success-indicator");
existingIndicators.forEach((indicator) => indicator.remove());
// Add validation indicators based on validCases count if available
if (validationResult.validCases) {
const casesCount =
typeof validationResult.validCases === "number"
? validationResult.validCases
: Array.isArray(validationResult.validCases)
? validationResult.validCases.length
: 1;
// Create a container for indicators if it doesn't exist
let indicatorContainer = elements.codeEditor.querySelector(".validation-indicators-container");
if (!indicatorContainer) {
indicatorContainer = document.createElement("div");
indicatorContainer.className = "validation-indicators-container";
elements.codeEditor.appendChild(indicatorContainer);
} else {
indicatorContainer.innerHTML = "";
}
// Add the appropriate number of checkmarks
for (let i = 0; i < casesCount; i++) {
const validationIndicator = document.createElement("div");
validationIndicator.className = "validation-success-indicator";
validationIndicator.innerHTML = "✓";
indicatorContainer.appendChild(validationIndicator);
}
}
if (validationResult.isValid) {
// Mark lesson as completed
const moduleProgress = state.userProgress[state.currentModule.id];
@@ -308,12 +340,6 @@ function runCode() {
updateModuleSelectorButtonProgress();
}
// Add validation indicator to editor
const validationIndicator = document.createElement("div");
validationIndicator.className = "validation-success-indicator";
validationIndicator.innerHTML = "✓";
elements.codeEditor.appendChild(validationIndicator);
// Show success feedback with visual indicators
showFeedback(true, validationResult.message || "Great job! Your code works correctly.");

View File

@@ -16,7 +16,7 @@ import responsiveConfig from "../../lessons/08-responsive.json";
// Module store
const moduleStore = [
basicSelectorsConfig
basicSelectorsConfig,
// basicsConfig,
// boxModelConfig,
// selectorsConfig,

View File

@@ -19,50 +19,82 @@ export function validateUserCode(userCode, lesson) {
// Default validation result
let result = {
isValid: true,
validCases: 0,
message: "Your code looks good!"
};
// Process each validation rule
for (const validation of validations) {
const { type, value, message, options } = validation;
let validationPassed = false;
switch (type) {
case "contains":
if (!containsValidation(userCode, value, options)) {
return { isValid: false, message: message || `Your code should include "${value}".` };
validationPassed = containsValidation(userCode, value, options);
if (!validationPassed) {
result = {
isValid: false,
validCases: result.validCases,
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}".` };
validationPassed = !containsValidation(userCode, value, options);
if (!validationPassed) {
result = {
isValid: false,
validCases: result.validCases,
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." };
validationPassed = regexValidation(userCode, value, options);
if (!validationPassed) {
result = {
isValid: false,
validCases: result.validCases,
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}".` };
validationPassed = propertyValueValidation(userCode, value, options);
if (!validationPassed) {
result = {
isValid: false,
validCases: result.validCases,
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}` };
validationPassed = syntaxResult.isValid;
if (!validationPassed) {
result = {
isValid: false,
validCases: result.validCases,
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." };
validationPassed = customResult.isValid;
if (!validationPassed) {
result = {
isValid: false,
validCases: result.validCases,
message: customResult.message || message || "Your code does not meet the requirements."
};
}
}
break;
@@ -71,10 +103,22 @@ export function validateUserCode(userCode, lesson) {
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
// If we've passed all validations, return success with all cases passed
result.validCases = validations.length;
return result;
}

View File

@@ -51,6 +51,7 @@
<div class="code-editor">
<div class="editor-header">
<span>CSS Editor</span>
<div class="validation-indicators-container"></div>
<button id="run-btn" class="btn btn-secondary"><img src="./gear.svg" />Run</button>
</div>
<div class="editor-content">

View File

@@ -343,15 +343,6 @@ code {
transition: background-color 0.2s ease;
}
.code-input.inline-input {
display: inline-block;
min-height: 30px;
height: auto;
padding: 2px 4px;
margin: 0 4px;
border-bottom: 1px dashed #666;
}
/* Controls */
.controls {
display: flex;
@@ -424,10 +415,16 @@ code {
border: 1px solid var(--success-color);
}
.validation-success-indicator {
.validation-indicators-container {
position: absolute;
top: 1rem;
right: 7rem;
display: flex;
gap: 5px;
z-index: 5;
}
.validation-success-indicator {
background-color: var(--success-color);
color: white;
width: 20px;
@@ -437,7 +434,6 @@ code {
align-items: center;
justify-content: center;
font-size: 12px;
z-index: 5;
}
/* Module selector button with progress */