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
|
|
@ -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