Add internal mix tag input for calculator and goal tree formulas
All checks were successful
Deploy Sokko G / deploy (push) Successful in 15s

This commit is contained in:
Shinuwa 2026-08-24 13:24:44 +02:00
parent 5c8148c905
commit ae503c7260
13 changed files with 929 additions and 23 deletions

View file

@ -266,6 +266,8 @@ export function validateSiteContent(site) {
"toolboxes.modules.calculator.formulaTitle",
"toolboxes.modules.calculator.childFormulaHelp",
"toolboxes.modules.calculator.rootFormulaHelp",
"toolboxes.modules.calculator.baseTokenLabel",
"toolboxes.modules.calculator.insertBaseTitle",
"toolboxes.modules.calculator.useEntryTitle",
"toolboxes.modules.calculator.renameTitle",
"toolboxes.modules.calculator.deleteTitle",
@ -318,6 +320,8 @@ export function validateSiteContent(site) {
"toolboxes.modules.goalTree.addContentItemButton",
"toolboxes.modules.goalTree.formulaPlaceholder",
"toolboxes.modules.goalTree.formulaHelp",
"toolboxes.modules.goalTree.baseTokenLabel",
"toolboxes.modules.goalTree.insertBaseTitle",
"toolboxes.modules.goalTree.resetTypeLabel",
"toolboxes.modules.goalTree.noneReset",
"toolboxes.modules.goalTree.dailyReset",

View file

@ -3,6 +3,7 @@ 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 { buildInsertedMixTagValue, normalizeMixTagSpacing, normalizeMixTagText, removeMixTagAtOffset, tokenizeMixTagValue } from "../website/src/utils/mixTagsInputUtils.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, normalizeGoalTreeData, normalizeImageAnnotationData, normalizeLinksData, normalizeModuleOrder, normalizeNotepadData, normalizeTableData, normalizeTaskPlannerData, normalizeTimerData, normalizeToolbox, normalizeUrl, summarizeEquipmentPlannerData } from "../website/src/features/toolboxes/storage/toolboxStorage.js";
@ -41,6 +42,35 @@ test("colon text import accepts labels without value", () => {
]);
});
test("mix tags input parses isolated token values only", () => {
const tokens = [{ value: "base", label: "Valeur du parent" }, { value: "loot", label: "Butin" }];
assert.deepEqual(tokenizeMixTagValue("base * 3", tokens).map((segment) => segment.type), ["token", "text"]);
assert.deepEqual(tokenizeMixTagValue("X - Base", tokens).map((segment) => segment.type), ["text", "token"]);
assert.deepEqual(tokenizeMixTagValue("database", tokens).map((segment) => ({ type: segment.type, text: segment.text })), [{ type: "text", text: "database" }]);
assert.deepEqual(tokenizeMixTagValue("base + loot", tokens).map((segment) => segment.type), ["token", "text", "token"]);
assert.deepEqual(tokenizeMixTagValue("", tokens), []);
assert.deepEqual(tokenizeMixTagValue("base * 2,5", tokens).map((segment) => segment.type), ["token", "text"]);
assert.equal(normalizeMixTagText("X - Base", tokens), "X - base");
assert.equal(normalizeMixTagText("database", tokens), "database");
assert.equal(normalizeMixTagSpacing(" 1 base + 2 "), "1 base + 2");
});
test("mix tags input insertion preserves formula spacing when requested", () => {
assert.deepEqual(buildInsertedMixTagValue("* 3", 0, 0, "base", { formulaSpacing: true }), { value: "base* 3", cursor: 4 });
assert.deepEqual(buildInsertedMixTagValue("2", 1, 1, "base", { formulaSpacing: true }), { value: "2 base", cursor: 6 });
assert.deepEqual(buildInsertedMixTagValue("2+3", 2, 2, "base", { formulaSpacing: true }), { value: "2+base 3", cursor: 7 });
assert.deepEqual(buildInsertedMixTagValue("2+old*3", 2, 5, "base", { formulaSpacing: true }), { value: "2+base*3", cursor: 6 });
assert.deepEqual(buildInsertedMixTagValue("route", 5, 5, "@item"), { value: "route@item", cursor: 10 });
});
test("mix tags input deletion removes the whole token", () => {
const tokens = [{ value: "base", label: "Valeur du parent" }];
assert.deepEqual(removeMixTagAtOffset("base * 3", 4, 4, tokens, "backward"), { value: " * 3", cursor: 0 });
assert.deepEqual(removeMixTagAtOffset("2 + base", 4, 4, tokens, "forward"), { value: "2 + ", cursor: 4 });
assert.deepEqual(removeMixTagAtOffset("2 + base * 3", 6, 8, tokens, "backward"), { value: "2 + * 3", cursor: 4 });
assert.equal(removeMixTagAtOffset("2 + 3", 5, 5, tokens, "backward"), null);
});
test("text exchange round trips simple toolbox modules and resets checklist progress", () => {
const context = createTextImportContext();
const notepad = normalizeNotepadData({