diff --git a/src/utils/exam.differences.ts b/src/utils/exam.differences.ts index ed78bac4..40cc518f 100644 --- a/src/utils/exam.differences.ts +++ b/src/utils/exam.differences.ts @@ -1,7 +1,26 @@ import { Exam } from "@/interfaces/exam"; import { diff, Diff } from "deep-diff"; -const EXCLUDED_FIELDS = new Set(["_id", "id", "createdAt", "createdBy", "entities", "isDiagnostic", "private"]); +const EXCLUDED_FIELDS = new Set(["_id", "id", "createdAt", "createdBy", "entities", "isDiagnostic", "private", "access", "requiresApproval", "score"]); + +const PATH_LABELS: Record = { + parts: "Parts", + exercises: "Exercises", + userSolutions: "User Solutions", + words: "Words", + options: "Options", + prompt: "Prompt", + text: "Text", + audio: "Audio", + script: "Script", + difficulty: "Difficulty", + shuffle: "Shuffle", + solutions: "Solutions", + variant: "Variant", + prefix: "Prefix", + suffix: "Suffix", + topic: "Topic", +}; export function generateExamDifferences(oldExam: Exam, newExam: Exam): string[] { const differences = diff(oldExam, newExam) || []; @@ -17,24 +36,19 @@ function formatDifference(change: Diff): string | undefined { return; } - // Convert path array to something human-readable - const pathString = change.path.join(" \u2192 "); // e.g. "parts → 0 → exercises → 1 → prompt" + const pathString = pathToHumanReadable(change.path); switch (change.kind) { - case "N": - // A new property/element was added - return `\u{2022} Added \`${pathString}\` with value: ${formatValue(change.rhs)}`; + case "N": // A new property/element was added + return `• Added ${pathString} with value: ${formatValue(change.rhs)}\n`; - case "D": - // A property/element was deleted - return `\u{2022} Removed \`${pathString}\` which had value: ${formatValue(change.lhs)}`; + case "D": // A property/element was deleted + return `• Removed ${pathString} which had value: ${formatValue(change.lhs)}\n`; - case "E": - // A property/element was edited - return `\u{2022} Changed \`${pathString}\` from ${formatValue(change.lhs)} to ${formatValue(change.rhs)}`; + case "E": // A property/element was edited + return `• Changed ${pathString} from ${formatValue(change.lhs)} to ${formatValue(change.rhs)}\n`; - case "A": - // An array change; change.item describes what happened at array index change.index + case "A": // An array change; `change.item` describes what happened at array index `change.index` return formatArrayChange(change); default: @@ -44,12 +58,12 @@ function formatDifference(change: Diff): string | undefined { function formatArrayChange(change: Diff): string | undefined { if (!change.path) return; + if (change.path.some((segment) => EXCLUDED_FIELDS.has(segment))) { return; } - - const pathString = change.path.join(" \u2192 "); + const pathString = pathToHumanReadable(change.path); const arrayChange = (change as any).item; const idx = (change as any).index; @@ -57,14 +71,13 @@ function formatArrayChange(change: Diff): string | undefined { switch (arrayChange.kind) { case "N": - return `\u{2022} Added an item at index [${idx}] in \`${pathString}\`: ${formatValue(arrayChange.rhs)}`; + return `• Added an item at [#${idx + 1}] in ${pathString}: ${formatValue(arrayChange.rhs)}\n`; case "D": - return `\u{2022} Removed an item at index [${idx}] in \`${pathString}\`: ${formatValue(arrayChange.lhs)}`; + return `• Removed an item at [#${idx + 1}] in ${pathString}: ${formatValue(arrayChange.lhs)}\n`; case "E": - return `\u{2022} Edited an item at index [${idx}] in \`${pathString}\` from ${formatValue(arrayChange.lhs)} to ${formatValue(arrayChange.rhs)}`; + return `• Edited an item at [#${idx + 1}] in ${pathString} from ${formatValue(arrayChange.lhs)} to ${formatValue(arrayChange.rhs)}\n`; case "A": - // Nested array changes could happen theoretically; handle or ignore similarly - return `\u{2022} Complex array change at index [${idx}] in \`${pathString}\`: ${JSON.stringify(arrayChange)}`; + return `• Complex array change at [#${idx + 1}] in ${pathString}: ${JSON.stringify(arrayChange)}\n`; default: return; } @@ -82,3 +95,21 @@ function formatValue(value: any): string { } return JSON.stringify(value); } + +/** + * Convert an array of path segments into a friendlier string. + * Example: + * ["parts", 0, "exercises", 1, "prompt"] + * becomes: + * "Parts → [#1] → Exercises → [#2] → Prompt" + */ +function pathToHumanReadable(pathSegments: Array): string { + return pathSegments + .map((seg) => { + if (typeof seg === "number") { + return `[#${seg + 1}]`; + } + return PATH_LABELS[seg] ?? seg; + }) + .join(" → "); +}