Add goal tree toolbox module
All checks were successful
Deploy Sokko G / deploy (push) Successful in 15s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 15s
This commit is contained in:
parent
e4cbe3e087
commit
59fdcb0927
63 changed files with 4062 additions and 252 deletions
|
|
@ -101,9 +101,9 @@ test("diablo4 data and route are available", async () => {
|
|||
assert.match(listingSource, /diablo4\.filterOptions\.affixes/);
|
||||
await readFile("website/public/static/img/games/diablo4/card-cover.png");
|
||||
await readFile("website/public/static/img/games/diablo4/hero-bg.png");
|
||||
await readFile("website/public/static/img/diablo4/axe.svg");
|
||||
await readFile("website/public/static/img/diablo4/cube.svg");
|
||||
await readFile("website/public/static/img/diablo4/fire.svg");
|
||||
await readFile("website/public/static/img/diablo4/shield.svg");
|
||||
await readFile("website/public/static/img/diablo4/wing.svg");
|
||||
await readFile("website/public/static/icons/axe.svg");
|
||||
await readFile("website/public/static/icons/cube.svg");
|
||||
await readFile("website/public/static/icons/fire.svg");
|
||||
await readFile("website/public/static/icons/shield.svg");
|
||||
await readFile("website/public/static/icons/wing.svg");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -269,6 +269,67 @@ export function validateSiteContent(site) {
|
|||
"toolboxes.modules.calculator.useEntryTitle",
|
||||
"toolboxes.modules.calculator.renameTitle",
|
||||
"toolboxes.modules.calculator.deleteTitle",
|
||||
"toolboxes.modules.goalTree.editorTitle",
|
||||
"toolboxes.modules.goalTree.treeTitle",
|
||||
"toolboxes.modules.goalTree.addRootButton",
|
||||
"toolboxes.modules.goalTree.addRootSchemaButton",
|
||||
"toolboxes.modules.goalTree.addChildButton",
|
||||
"toolboxes.modules.goalTree.addChildTitle",
|
||||
"toolboxes.modules.goalTree.addContentItemTitle",
|
||||
"toolboxes.modules.goalTree.addContentItemTooltip",
|
||||
"toolboxes.modules.goalTree.verticalViewTitle",
|
||||
"toolboxes.modules.goalTree.horizontalViewTitle",
|
||||
"toolboxes.modules.goalTree.cardActionsLabel",
|
||||
"toolboxes.modules.goalTree.editTitle",
|
||||
"toolboxes.modules.goalTree.baseTab",
|
||||
"toolboxes.modules.goalTree.contentTab",
|
||||
"toolboxes.modules.goalTree.advancedTab",
|
||||
"toolboxes.modules.goalTree.resetTimeLabel",
|
||||
"toolboxes.modules.goalTree.weeklyResetDayLabel",
|
||||
"toolboxes.modules.goalTree.titleLabel",
|
||||
"toolboxes.modules.goalTree.listTitleLabel",
|
||||
"toolboxes.modules.goalTree.kindLabel",
|
||||
"toolboxes.modules.goalTree.stepKind",
|
||||
"toolboxes.modules.goalTree.textKind",
|
||||
"toolboxes.modules.goalTree.checklistKind",
|
||||
"toolboxes.modules.goalTree.iconLabel",
|
||||
"toolboxes.modules.goalTree.iconColorLabel",
|
||||
"toolboxes.modules.goalTree.targetQuantityLabel",
|
||||
"toolboxes.modules.goalTree.quantitySectionTitle",
|
||||
"toolboxes.modules.goalTree.progressSectionTitle",
|
||||
"toolboxes.modules.goalTree.autoProgressLabel",
|
||||
"toolboxes.modules.goalTree.manualProgressLabel",
|
||||
"toolboxes.modules.goalTree.autoProgressTitle",
|
||||
"toolboxes.modules.goalTree.manualProgressTitle",
|
||||
"toolboxes.modules.goalTree.autoProgressHint",
|
||||
"toolboxes.modules.goalTree.manualProgressHint",
|
||||
"toolboxes.modules.goalTree.contentSectionTitle",
|
||||
"toolboxes.modules.goalTree.itemLabelLabel",
|
||||
"toolboxes.modules.goalTree.itemLabelPlaceholder",
|
||||
"toolboxes.modules.goalTree.itemCheckedLabel",
|
||||
"toolboxes.modules.goalTree.itemTotalLabel",
|
||||
"toolboxes.modules.goalTree.itemTotalPlaceholder",
|
||||
"toolboxes.modules.goalTree.itemTotalHelp",
|
||||
"toolboxes.modules.goalTree.prerequisitesSectionTitle",
|
||||
"toolboxes.modules.goalTree.contentTabHint",
|
||||
"toolboxes.modules.goalTree.stepContentUnavailable",
|
||||
"toolboxes.modules.goalTree.decrementItemTitle",
|
||||
"toolboxes.modules.goalTree.incrementItemTitle",
|
||||
"toolboxes.modules.goalTree.addContentItemButton",
|
||||
"toolboxes.modules.goalTree.formulaPlaceholder",
|
||||
"toolboxes.modules.goalTree.formulaHelp",
|
||||
"toolboxes.modules.goalTree.resetTypeLabel",
|
||||
"toolboxes.modules.goalTree.noneReset",
|
||||
"toolboxes.modules.goalTree.dailyReset",
|
||||
"toolboxes.modules.goalTree.weeklyReset",
|
||||
"toolboxes.modules.goalTree.closeButton",
|
||||
"toolboxes.modules.goalTree.deleteButton",
|
||||
"toolboxes.modules.goalTree.deleteTitle",
|
||||
"toolboxes.modules.goalTree.reorderTitle",
|
||||
"toolboxes.modules.goalTree.showChildrenTitle",
|
||||
"toolboxes.modules.goalTree.hideChildrenTitle",
|
||||
"toolboxes.modules.goalTree.emptySelection",
|
||||
"toolboxes.modules.goalTree.emptyTree",
|
||||
"toolboxes.modules.table.toolbarLabel",
|
||||
"toolboxes.modules.table.gridLabel",
|
||||
"toolboxes.modules.table.cellLabel",
|
||||
|
|
@ -557,7 +618,7 @@ export function validateSiteContent(site) {
|
|||
});
|
||||
|
||||
const libraryToolDocs = valueAt(site, "library.toolDocs");
|
||||
["notepad", "checklist", "counters", "images", "imageAnnotation", "links", "calculator", "table", "timer", "calendar", "taskPlanner", "combos", "equipmentPlanner"].forEach((toolType) => {
|
||||
["notepad", "checklist", "counters", "images", "imageAnnotation", "links", "calculator", "table", "goalTree", "timer", "calendar", "taskPlanner", "combos", "equipmentPlanner"].forEach((toolType) => {
|
||||
const docs = libraryToolDocs?.[toolType];
|
||||
assert.equal(typeof docs, "object", `library.toolDocs.${toolType} must be an object`);
|
||||
assert.ok(Array.isArray(docs.controls), `library.toolDocs.${toolType}.controls must be an array`);
|
||||
|
|
@ -770,7 +831,7 @@ export function validateLibraryData(payload) {
|
|||
assert.deepEqual(new Set(twoColumnIds), moduleIds, "library.toolbox.moduleOrder.two must contain every module exactly once");
|
||||
assert.equal(twoColumnIds.length, moduleIds.size, "library.toolbox.moduleOrder.two must not contain duplicate modules");
|
||||
|
||||
["notepad", "checklist", "images", "links", "counters", "combos", "calculator", "table", "timer", "calendar", "taskPlanner", "equipmentPlanner", "imageAnnotation"].forEach((type) => {
|
||||
["notepad", "checklist", "images", "links", "counters", "combos", "calculator", "table", "goalTree", "timer", "calendar", "taskPlanner", "equipmentPlanner", "imageAnnotation"].forEach((type) => {
|
||||
assert.ok(payload.toolbox.modules.some((module) => module.type === type), `library must include a ${type} example`);
|
||||
});
|
||||
|
||||
|
|
@ -790,6 +851,11 @@ export function validateLibraryData(payload) {
|
|||
assert.ok(Array.isArray(taskData.relations), "library task planner example must contain relations");
|
||||
assert.ok(taskData.relations.length >= 1, "library task planner example must include a parent/child relation");
|
||||
|
||||
const goalTreeModule = payload.toolbox.modules.find((module) => module.type === "goalTree");
|
||||
const goalTreeData = payload.modules[goalTreeModule.id];
|
||||
assert.ok(Array.isArray(goalTreeData.nodes), "library goal tree example must contain nodes");
|
||||
assert.ok(goalTreeData.nodes.some((node) => node.parentId), "library goal tree example must include a parent/child node");
|
||||
|
||||
const equipmentModule = payload.toolbox.modules.find((module) => module.type === "equipmentPlanner");
|
||||
const equipmentData = payload.modules[equipmentModule.id];
|
||||
assert.ok(Array.isArray(equipmentData.types), "library equipment planner example must contain types");
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ test("legacy icons and primary images are available", async () => {
|
|||
await readFile("website/public/static/icons/copy.svg", "utf8");
|
||||
await readFile("website/public/static/icons/abacus.svg", "utf8");
|
||||
await readFile("website/public/static/icons/calculator.svg", "utf8");
|
||||
await readFile("website/public/static/icons/diagram.svg", "utf8");
|
||||
await readFile("website/public/static/icons/map.svg", "utf8");
|
||||
await readFile("website/public/static/icons/enter.svg", "utf8");
|
||||
await readFile("website/public/static/icons/export.svg", "utf8");
|
||||
|
|
@ -142,6 +143,11 @@ test("legacy icons and primary images are available", async () => {
|
|||
await readFile("website/public/static/icons/trashcan.svg", "utf8");
|
||||
await readFile("website/public/static/icons/zoom.svg", "utf8");
|
||||
await readFile("website/public/static/icons/warning.svg", "utf8");
|
||||
await readFile("website/public/static/icons/axe.svg", "utf8");
|
||||
await readFile("website/public/static/icons/cube.svg", "utf8");
|
||||
await readFile("website/public/static/icons/fire.svg", "utf8");
|
||||
await readFile("website/public/static/icons/shield.svg", "utf8");
|
||||
await readFile("website/public/static/icons/wing.svg", "utf8");
|
||||
await readFile("website/public/favicon.ico");
|
||||
await readFile("website/public/static/img/dragon-home.png");
|
||||
await readFile("website/public/static/img/dragon-sidebar.png");
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ test("toolbox storage, cards and pages are wired", async () => {
|
|||
assert.match(toolboxStorage, /normalizeTimerData/);
|
||||
assert.match(toolboxStorage, /normalizeCalendarData/);
|
||||
assert.match(toolboxStorage, /normalizeTaskPlannerData/);
|
||||
assert.match(toolboxStorage, /normalizeGoalTreeData/);
|
||||
assert.match(toolboxStorage, /normalizeEquipmentPlannerData/);
|
||||
assert.match(toolboxStorage, /summarizeEquipmentPlannerData/);
|
||||
assert.match(toolboxStorage, /normalizeImageAnnotationData/);
|
||||
|
|
@ -69,6 +70,7 @@ test("toolbox storage, cards and pages are wired", async () => {
|
|||
assert.match(storageCompactorRegistry, /export const MODULE_DATA_COMPACTORS/);
|
||||
assert.match(storageCompactorRegistry, /timer: compactTimerDataForStorage/);
|
||||
assert.match(storageCompactorRegistry, /calendar: compactCalendarDataForStorage/);
|
||||
assert.match(storageCompactorRegistry, /goalTree: compactGoalTreeDataForStorage/);
|
||||
assert.match(timerStorage, /scrollResults/);
|
||||
assert.match(calendarStorage, /normalizeCalendarData/);
|
||||
assert.match(toolboxStorage, /nextId\("calendar"\)/);
|
||||
|
|
@ -153,6 +155,8 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
|||
const taskPlannerModule = await readFile("website/src/features/toolboxes/modules/TaskPlannerModule.jsx", "utf8");
|
||||
const taskPlannerItems = await readFile("website/src/features/toolboxes/modules/TaskPlannerItems.jsx", "utf8");
|
||||
const taskPlannerUtils = await readFile("website/src/features/toolboxes/modules/taskPlannerUtils.js", "utf8");
|
||||
const goalTreeModule = await readFile("website/src/features/toolboxes/modules/GoalTreeModule.jsx", "utf8");
|
||||
const goalTreeUtils = await readFile("website/src/features/toolboxes/modules/goalTreeUtils.js", "utf8");
|
||||
const equipmentPlannerModule = await readFile("website/src/features/toolboxes/modules/EquipmentPlannerModule.jsx", "utf8");
|
||||
const equipmentPlannerEditors = await readFile("website/src/features/toolboxes/modules/EquipmentPlannerEditors.jsx", "utf8");
|
||||
const equipmentPlannerItem = await readFile("website/src/features/toolboxes/modules/EquipmentPlannerItem.jsx", "utf8");
|
||||
|
|
@ -189,6 +193,7 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
|||
assert.match(moduleRegistry, /timer:/);
|
||||
assert.match(moduleRegistry, /calendar:/);
|
||||
assert.match(moduleRegistry, /taskPlanner:/);
|
||||
assert.match(moduleRegistry, /goalTree:/);
|
||||
assert.match(moduleRegistry, /equipmentPlanner:/);
|
||||
assert.match(moduleMetadataRegistry, /Planificateur d'équipements/);
|
||||
assert.match(moduleRegistry, /imageAnnotation:/);
|
||||
|
|
@ -346,6 +351,38 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
|||
assert.match(taskPlannerUtils, /export function applyDueResets/);
|
||||
assert.doesNotMatch(taskPlannerModule, /canMoveItem:/);
|
||||
assert.doesNotMatch(taskPlannerModule, /canMoveGroup:/);
|
||||
assert.match(goalTreeModule, /export function GoalTreeModule/);
|
||||
assert.match(goalTreeModule, /normalizeGoalTreeData/);
|
||||
assert.match(goalTreeModule, /applyGoalTreeDueResets/);
|
||||
assert.match(goalTreeModule, /TextExchangeActions/);
|
||||
assert.match(goalTreeModule, /DiagramConnectors/);
|
||||
assert.match(goalTreeModule, /Tooltip/);
|
||||
assert.match(goalTreeModule, /GoalTreeIconPicker/);
|
||||
assert.match(goalTreeModule, /GoalTreeContentEditor/);
|
||||
assert.match(goalTreeModule, /GoalTreeNodeContentPreview/);
|
||||
assert.match(goalTreeModule, /GoalTreeChecklistInlineControl/);
|
||||
assert.match(goalTreeModule, /GoalTreeStepLeafControl/);
|
||||
assert.match(goalTreeModule, /GoalTreePrerequisitePicker/);
|
||||
assert.match(goalTreeModule, /function toggleBranch/);
|
||||
assert.match(goalTreeModule, /function editNode/);
|
||||
assert.match(goalTreeModule, /function addNodeContentItem/);
|
||||
assert.match(goalTreeModule, /closedSiblingIds/);
|
||||
assert.match(goalTreeModule, /useGroupedReorder/);
|
||||
assert.match(goalTreeModule, /const treeOrientation = data\.orientation \|\| "horizontal"/);
|
||||
assert.match(goalTreeModule, /const reorderOrientation = treeOrientation === "horizontal" \? "vertical" : "horizontal"/);
|
||||
assert.match(goalTreeModule, /orientation: reorderOrientation/);
|
||||
assert.match(goalTreeModule, /orientation={treeOrientation}/);
|
||||
assert.match(goalTreeModule, /module-scroll-button goal-tree-orientation-switch/);
|
||||
assert.match(goalTreeModule, /Icon name="rotate"/);
|
||||
assert.match(goalTreeModule, /function toggleLinkedParent/);
|
||||
assert.match(goalTreeModule, /grouped: false/);
|
||||
assert.match(goalTreeModule, /getGoalTreeConvergenceKeepOpenIds/);
|
||||
assert.match(goalTreeModule, /progress\.current/);
|
||||
assert.match(goalTreeModule, /label: textContent\.itemLabelPlaceholder \|\| "Élément"/);
|
||||
assert.doesNotMatch(goalTreeUtils, /"calculator"/);
|
||||
assert.match(goalTreeModule, /getItemProps\(\{ itemId: node\.id, parentId \}\)/);
|
||||
assert.match(goalTreeUtils, /export function calculateGoalTreeFormula/);
|
||||
assert.match(goalTreeUtils, /export function evaluateGoalTreeNodes/);
|
||||
assert.match(equipmentPlannerModule, /export function EquipmentPlannerModule/);
|
||||
assert.match(equipmentPlannerModule, /normalizeEquipmentPlannerData/);
|
||||
assert.match(equipmentPlannerModule, /summarizeEquipmentPlannerData/);
|
||||
|
|
@ -439,6 +476,8 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
|||
assert.match(textImport, /line\.indexOf\(":"\)/);
|
||||
assert.match(textImport, /exportCalendarText/);
|
||||
assert.match(textImport, /importCalendarText/);
|
||||
assert.match(textImport, /exportGoalTreeText/);
|
||||
assert.match(textImport, /importGoalTreeText/);
|
||||
assert.match(textImport, /CALENDAR_WEEKDAYS/);
|
||||
assert.match(imageViewer, /image-viewer-media/);
|
||||
assert.match(imageViewer, /image-viewer-image-frame/);
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ import { test } from "node:test";
|
|||
import assert from "node:assert/strict";
|
||||
import { evaluateTableCell } from "../website/src/features/toolboxes/modules/tableFormulaEngine.js";
|
||||
import { exportModuleText, importModuleText, parseColonImportLines } from "../website/src/features/toolboxes/modules/textImport.js";
|
||||
import { calculateGoalTreeFormula, evaluateGoalTreeNodes } from "../website/src/features/toolboxes/modules/goalTreeUtils.js";
|
||||
import { getTimePatternRecurrenceMs, getTimePatternTargetMs } from "../website/src/features/toolboxes/modules/timerUtils.js";
|
||||
import { compactModuleDataForStorage, createToolboxExportPayload, normalizeCalculatorData, normalizeChecklistData, normalizeCombosData, normalizeCountersData, normalizeEquipmentPlannerData, normalizeImageAnnotationData, normalizeLinksData, normalizeModuleOrder, normalizeNotepadData, normalizeTableData, normalizeTaskPlannerData, normalizeTimerData, normalizeToolbox, normalizeUrl, summarizeEquipmentPlannerData } from "../website/src/features/toolboxes/storage/toolboxStorage.js";
|
||||
import { compactModuleDataForStorage, createToolboxExportPayload, normalizeCalculatorData, normalizeChecklistData, normalizeCombosData, normalizeCountersData, normalizeEquipmentPlannerData, normalizeGoalTreeData, normalizeImageAnnotationData, normalizeLinksData, normalizeModuleOrder, normalizeNotepadData, normalizeTableData, normalizeTaskPlannerData, normalizeTimerData, normalizeToolbox, normalizeUrl, summarizeEquipmentPlannerData } from "../website/src/features/toolboxes/storage/toolboxStorage.js";
|
||||
import { applyGroupedReorderOperation, completeGroupOrder, getBoundaryItemId, getGroupedEntries, moveGroupOrder, moveGroupOrderToEnd, moveGroupOrderToStart, moveItem, moveItemGroup } from "../website/src/hooks/useGroupedReorder.js";
|
||||
|
||||
function createTextImportContext() {
|
||||
|
|
@ -20,6 +21,7 @@ function createTextImportContext() {
|
|||
normalizeTableData,
|
||||
normalizeTimerData,
|
||||
normalizeTaskPlannerData,
|
||||
normalizeGoalTreeData,
|
||||
normalizeEquipmentPlannerData,
|
||||
normalizeImageAnnotationData,
|
||||
normalizeUrl
|
||||
|
|
@ -118,6 +120,22 @@ test("text exchange imports table TSV and calculator hierarchy", () => {
|
|||
assert.equal(calculatorImport.data.entries[1].formula, "base*5");
|
||||
});
|
||||
|
||||
test("goal tree text exchange imports formulas and hierarchy", () => {
|
||||
const context = createTextImportContext();
|
||||
const imported = importModuleText("goalTree", "- Breuvage de rage x10\n - Élixir de destruction = base * 3\n - Poudre de flamme x5\n - Sève de cèdre x7\n - Catalyseur magique x10", context);
|
||||
assert.equal(imported.ok, true);
|
||||
assert.equal(imported.data.nodes.length, 5);
|
||||
assert.equal(imported.data.nodes[0].kind, "step");
|
||||
assert.equal(imported.data.nodes[1].parentId, imported.data.nodes[0].id);
|
||||
assert.equal(imported.data.nodes[1].formula, "base * 3");
|
||||
assert.equal(imported.data.nodes[2].kind, "step");
|
||||
assert.equal(imported.data.nodes[2].qtyCurrent, 0);
|
||||
|
||||
const exported = exportModuleText("goalTree", imported.data);
|
||||
assert.match(exported, /Breuvage de rage x10/);
|
||||
assert.match(exported, /Élixir de destruction = base \* 3/);
|
||||
});
|
||||
|
||||
test("text exchange round trips combos, tasks, equipment, images and timers", () => {
|
||||
const context = createTextImportContext();
|
||||
const combos = normalizeCombosData({
|
||||
|
|
@ -910,3 +928,107 @@ test("task planner storage compacts defaults and export remaps task relations",
|
|||
assert.equal(exportedPlanner.relations[0].toTaskId, "a2");
|
||||
assert.equal(exportedPlanner.relations[0].prerequisite, true);
|
||||
});
|
||||
|
||||
test("goal tree data normalizes, compacts, calculates and remaps nodes", () => {
|
||||
assert.equal(calculateGoalTreeFormula("base * 3", 10), 30);
|
||||
assert.equal(calculateGoalTreeFormula("base / 2", 10), 5);
|
||||
assert.equal(calculateGoalTreeFormula("base - 12", 10, { clamp: false }), -2);
|
||||
assert.equal(calculateGoalTreeFormula("base,5", 10), null);
|
||||
assert.equal(calculateGoalTreeFormula("alert(1)", 10), null);
|
||||
|
||||
const normalized = normalizeGoalTreeData({
|
||||
orientation: "vertical",
|
||||
nodes: [
|
||||
{ id: "root", title: "Root", kind: "step", icon: "fire", color: "cyan", qtyTarget: 10, qtyCurrent: 1 },
|
||||
{ id: "child", parentId: "root", title: "Child", formula: "base*3", qtyCurrent: 15, resetType: "daily", resetTime: "09:30", checkedAt: 99 },
|
||||
{ id: "bad", parentId: "missing", title: "Bad", kind: "weird", resetType: "later" },
|
||||
{ id: "list", title: "List", kind: "checklist", items: [{ id: "i1", label: "Done", qtyCurrent: 1 }, { id: "i2", label: "Todo", formula: "5", qtyCurrent: 2 }, { id: "i3", label: "" }] },
|
||||
{ id: "formulaList", parentId: "root", parentIds: ["root", "list"], title: "Formula list", kind: "checklist", items: [{ id: "i4", label: "Triple", formula: "base * 3", qtyCurrent: 30 }, { id: "i5", label: "Delta", formula: "base - 12" }] }
|
||||
]
|
||||
});
|
||||
assert.equal(normalized.orientation, "vertical");
|
||||
assert.equal(normalized.nodes[2].parentId, "");
|
||||
assert.equal(normalized.nodes[2].kind, "step");
|
||||
assert.equal(normalized.nodes[2].resetType, "none");
|
||||
assert.equal(normalized.nodes[1].resetTime, "09:30");
|
||||
assert.deepEqual(normalized.nodes[4].parentIds, ["root", "list"]);
|
||||
|
||||
const evaluated = evaluateGoalTreeNodes(normalized.nodes);
|
||||
assert.equal(evaluated[1].computedQtyTarget, 30);
|
||||
assert.equal(evaluated[1].progress.ratio, 0.5);
|
||||
assert.equal(evaluated[0].progress.current, 45);
|
||||
assert.equal(evaluated[0].progress.target, 61);
|
||||
assert.equal(evaluated[3].progress.current, 3);
|
||||
assert.equal(evaluated[3].progress.target, 7);
|
||||
assert.equal(evaluated[4].items[0].qtyTarget, 30);
|
||||
assert.equal(evaluated[4].items[1].qtyTarget, 1);
|
||||
assert.equal(evaluated[4].progress.current, 30);
|
||||
|
||||
const compact = compactModuleDataForStorage("goalTree", normalized);
|
||||
assert.equal(compact.settings, undefined);
|
||||
assert.equal(compact.orientation, "vertical");
|
||||
assert.equal(compact.nodes[0].kind, undefined);
|
||||
assert.equal(compact.nodes[0].icon, "fire");
|
||||
assert.equal(compact.nodes[0].color, "cyan");
|
||||
assert.equal(compact.nodes[0].qtyCurrent, 1);
|
||||
assert.equal(compact.nodes[1].formula, "base*3");
|
||||
assert.equal(compact.nodes[1].resetType, "daily");
|
||||
assert.equal(compact.nodes[1].resetTime, "09:30");
|
||||
assert.deepEqual(compact.nodes[3].items, [{ id: "i1", label: "Done", qtyCurrent: 1 }, { id: "i2", label: "Todo", formula: "5", qtyCurrent: 2 }, { id: "i3", label: "" }]);
|
||||
assert.deepEqual(compact.nodes[4].parentIds, ["root", "list"]);
|
||||
assert.deepEqual(compact.nodes[4].items, [{ id: "i4", label: "Triple", formula: "base * 3", qtyCurrent: 30 }, { id: "i5", label: "Delta", formula: "base - 12" }]);
|
||||
|
||||
const exported = createToolboxExportPayload(
|
||||
{ id: "toolbox1", name: "Goals", modules: [{ id: "module1", type: "goalTree" }], updatedAt: "2026-01-01T00:00:00.000Z" },
|
||||
{ "toolbox1:module1": compact }
|
||||
);
|
||||
const exportedTree = exported.modules.m1;
|
||||
assert.deepEqual(exportedTree.nodes.map((node) => node.id), ["g1", "g2", "g3", "g4", "g5"]);
|
||||
assert.equal(exportedTree.nodes[1].parentId, "g1");
|
||||
assert.deepEqual(exportedTree.nodes[4].parentIds, ["g1", "g4"]);
|
||||
assert.deepEqual(exportedTree.nodes[3].items.map((item) => item.id), ["g6", "g7", "g8"]);
|
||||
assert.deepEqual(exportedTree.nodes[4].items.map((item) => item.id), ["g9", "ga"]);
|
||||
});
|
||||
|
||||
test("goal tree parent progress uses base-scaled child bottlenecks", () => {
|
||||
const normalized = normalizeGoalTreeData({
|
||||
nodes: [
|
||||
{ id: "root", title: "Potion de vie", kind: "step", qtyTarget: 10 },
|
||||
{
|
||||
id: "ingredients",
|
||||
parentId: "root",
|
||||
title: "Ingrédients",
|
||||
kind: "checklist",
|
||||
items: [
|
||||
{ id: "herb", label: "Herbe magique", formula: "base", qtyCurrent: 7 },
|
||||
{ id: "water", label: "Eau rajeunissante", formula: "base", qtyCurrent: 5 }
|
||||
]
|
||||
},
|
||||
{ id: "manual", title: "Objectif manuel", kind: "step", qtyTarget: 10, qtyCurrent: 3, progressMode: "manual" },
|
||||
{ id: "manualChild", parentId: "manual", title: "Enfant ignoré", kind: "step", formula: "base", qtyCurrent: 10 },
|
||||
{ id: "defaultManual", title: "Validation manuelle par défaut", kind: "step", qtyTarget: 1, qtyCurrent: 1 },
|
||||
{ id: "defaultManualChild", parentId: "defaultManual", title: "Validation enfant", kind: "step", qtyTarget: 1, qtyCurrent: 0 },
|
||||
{ id: "forcedAuto", title: "Validation auto forcée", kind: "step", qtyTarget: 1, qtyCurrent: 1, progressMode: "auto" },
|
||||
{ id: "forcedAutoChild", parentId: "forcedAuto", title: "Validation enfant auto", kind: "step", qtyTarget: 1, qtyCurrent: 0 }
|
||||
]
|
||||
});
|
||||
const evaluated = evaluateGoalTreeNodes(normalized.nodes);
|
||||
|
||||
assert.equal(evaluated[0].progress.current, 5);
|
||||
assert.equal(evaluated[0].progress.target, 10);
|
||||
assert.equal(evaluated[0].progress.ratio, 0.5);
|
||||
assert.equal(evaluated[1].progress.current, 12);
|
||||
assert.equal(evaluated[1].progress.target, 20);
|
||||
assert.equal(evaluated[2].progress.current, 3);
|
||||
assert.equal(evaluated[2].progress.target, 10);
|
||||
assert.equal(evaluated[4].progress.current, 1);
|
||||
assert.equal(evaluated[4].progress.target, 1);
|
||||
assert.equal(evaluated[6].progress.current, 0);
|
||||
assert.equal(evaluated[6].progress.target, 1);
|
||||
|
||||
const compact = compactModuleDataForStorage("goalTree", normalized);
|
||||
assert.equal(compact.nodes[0].progressMode, undefined);
|
||||
assert.equal(compact.nodes[2].progressMode, "manual");
|
||||
assert.equal(compact.nodes[4].progressMode, undefined);
|
||||
assert.equal(compact.nodes[6].progressMode, "auto");
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue