242 lines
9.9 KiB
JavaScript
242 lines
9.9 KiB
JavaScript
// Rôle : teste les parseurs d'import texte utilisés par les outils toolbox.
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { evaluateTableCell } from "../website/src/features/toolboxes/modules/tableFormulaEngine.js";
|
|
import { parseColonImportLines } from "../website/src/features/toolboxes/modules/textImport.js";
|
|
import { getTimePatternRecurrenceMs, getTimePatternTargetMs } from "../website/src/features/toolboxes/modules/timerUtils.js";
|
|
import { compactModuleDataForStorage, createToolboxExportPayload, normalizeNotepadData, normalizeTableData, normalizeTaskPlannerData } from "../website/src/features/toolboxes/storage/toolboxStorage.js";
|
|
|
|
test("colon text import keeps urls intact after the first separator", () => {
|
|
assert.deepEqual(parseColonImportLines("Build:https://example.com/build?class=rogue\nPotion:10"), [
|
|
{ label: "Build", value: "https://example.com/build?class=rogue" },
|
|
{ label: "Potion", value: "10" }
|
|
]);
|
|
});
|
|
|
|
test("colon text import accepts labels without value", () => {
|
|
assert.deepEqual(parseColonImportLines("Simple item"), [
|
|
{ label: "Simple item", value: "" }
|
|
]);
|
|
});
|
|
|
|
test("time pattern recurrence uses configured frequency instead of next remaining delay", () => {
|
|
const now = new Date(2026, 0, 1, 13, 23, 45, 0).getTime();
|
|
const target = getTimePatternTargetMs("X:24:X", now);
|
|
assert.equal(new Date(target).getHours(), 13);
|
|
assert.equal(new Date(target).getMinutes(), 24);
|
|
assert.equal(new Date(target).getSeconds(), 0);
|
|
assert.equal(getTimePatternRecurrenceMs("X:24:X"), 60 * 60 * 1000);
|
|
assert.equal(getTimePatternRecurrenceMs("X:X:30"), 60 * 1000);
|
|
});
|
|
|
|
test("table formulas evaluate arithmetic, parentheses and cell references", () => {
|
|
const cells = {
|
|
A1: "100",
|
|
A2: "20",
|
|
B1: "=A1+A2*2",
|
|
B2: "=(A1+A2)/2",
|
|
C1: "=B1-B2/3"
|
|
};
|
|
const dimensions = { rows: 10, columns: 6 };
|
|
|
|
assert.equal(evaluateTableCell("B1", cells, dimensions).display, "140");
|
|
assert.equal(evaluateTableCell("B2", cells, dimensions).display, "60");
|
|
assert.equal(evaluateTableCell("C1", cells, dimensions).display, "120");
|
|
});
|
|
|
|
test("table formulas report invalid references, division by zero and circular references", () => {
|
|
const dimensions = { rows: 10, columns: 6 };
|
|
|
|
assert.equal(evaluateTableCell("A1", { A1: "=Z99+1" }, dimensions).error, "reference");
|
|
assert.equal(evaluateTableCell("A1", { A1: "=10/0" }, dimensions).error, "division");
|
|
assert.equal(evaluateTableCell("A1", { A1: "=B1", B1: "=A1" }, dimensions).error, "cycle");
|
|
assert.deepEqual(evaluateTableCell("A1", { A1: "texte", B1: "=A1+1" }, dimensions), { value: null, display: "texte", error: "" });
|
|
assert.equal(evaluateTableCell("B1", { A1: "texte", B1: "=A1+1" }, dimensions).error, "reference");
|
|
});
|
|
|
|
test("table storage clamps dimensions and compacts non-empty cells", () => {
|
|
const normalized = normalizeTableData({
|
|
rows: 200,
|
|
columns: 40,
|
|
cells: {
|
|
A1: "Boss",
|
|
T50: "=A1+1",
|
|
U51: "Ignored",
|
|
B1: " "
|
|
},
|
|
rowLabels: {
|
|
0: "Boss final ",
|
|
1: "2",
|
|
50: "Ignored"
|
|
},
|
|
columnLabels: {
|
|
0: "Item final ",
|
|
1: "B",
|
|
20: "Ignored"
|
|
}
|
|
});
|
|
|
|
assert.equal(normalized.rows, 50);
|
|
assert.equal(normalized.columns, 20);
|
|
assert.deepEqual(normalized.cells, { A1: "Boss", T50: "=A1+1" });
|
|
assert.deepEqual(normalized.rowLabels, { 0: "Boss final " });
|
|
assert.deepEqual(normalized.columnLabels, { 0: "Item final " });
|
|
|
|
const compact = compactModuleDataForStorage("table", normalized);
|
|
assert.equal(compact.rows, 50);
|
|
assert.equal(compact.columns, 20);
|
|
assert.deepEqual(compact.cells, { A1: "Boss", T50: "=A1+1" });
|
|
assert.deepEqual(compact.rowLabels, { 0: "Boss final " });
|
|
assert.deepEqual(compact.columnLabels, { 0: "Item final " });
|
|
assert.deepEqual(compactModuleDataForStorage("table", { rows: 10, columns: 6, cells: {}, columnLabels: { 0: "Item " } }), {
|
|
cells: {},
|
|
columnLabels: { 0: "Item " }
|
|
});
|
|
assert.equal(compactModuleDataForStorage("table", { rows: 10, columns: 6, cells: {} }), null);
|
|
});
|
|
|
|
test("notepad data migrates text, sanitizes html and compacts drawings", () => {
|
|
const migrated = normalizeNotepadData({ text: "Boss\nPhase 2" });
|
|
assert.equal(migrated.text, "Boss\nPhase 2");
|
|
assert.match(migrated.html, /<p>Boss<\/p><p>Phase 2<\/p>/);
|
|
|
|
const normalized = normalizeNotepadData({
|
|
html: '<h3 onclick="bad()">Titre</h3><script>alert(1)</script><span style="color: rgb(246, 196, 83); position: fixed">Important</span><img src=x>',
|
|
updatedAt: "2026-07-29T12:00:00.000Z",
|
|
drawingMode: "permanent",
|
|
drawings: {
|
|
strokes: [
|
|
{
|
|
id: "stroke1",
|
|
color: "#f6c453",
|
|
width: 40,
|
|
points: [{ x: 1.123 }, { x: 4, y: 8 }, { x: 12.345, y: 16.789 }]
|
|
}
|
|
]
|
|
}
|
|
});
|
|
|
|
assert.match(normalized.html, /<h3>Titre<\/h3>/);
|
|
assert.match(normalized.html, /<span style="color: #f6c453">Important<\/span>/);
|
|
assert.doesNotMatch(normalized.html, /script|onclick|position|img/);
|
|
assert.equal(normalized.updatedAt, "2026-07-29T12:00:00.000Z");
|
|
assert.equal(normalized.drawings.strokes[0].width, 24);
|
|
assert.deepEqual(normalized.drawings.strokes[0].points, [{ x: 4, y: 8 }, { x: 12.35, y: 16.79 }]);
|
|
|
|
const compactTemporary = compactModuleDataForStorage("notepad", {
|
|
html: "<p>Note</p>",
|
|
text: "Note",
|
|
drawingMode: "temporary",
|
|
drawings: { strokes: normalized.drawings.strokes }
|
|
});
|
|
assert.equal(compactTemporary.drawings, undefined);
|
|
assert.equal(compactTemporary.drawingMode, undefined);
|
|
|
|
const compactPermanent = compactModuleDataForStorage("notepad", {
|
|
html: "<p>Note</p>",
|
|
text: "Note",
|
|
updatedAt: "not a date",
|
|
drawingMode: "permanent",
|
|
drawings: normalized.drawings
|
|
});
|
|
assert.equal(compactPermanent.updatedAt, undefined);
|
|
assert.equal(compactPermanent.drawingMode, "permanent");
|
|
assert.equal(compactPermanent.drawings.strokes.length, 1);
|
|
});
|
|
|
|
test("image annotation storage keeps drawing strokes as percent coordinates", () => {
|
|
const compact = compactModuleDataForStorage("imageAnnotation", {
|
|
image: "data:image/png;base64,aaa",
|
|
markers: [{ id: "marker1", x: 120, y: 50, label: "Boss" }],
|
|
drawings: {
|
|
strokes: [
|
|
{
|
|
id: "stroke1",
|
|
color: "#22d3ee",
|
|
width: 4,
|
|
points: [{ x: 10.123, y: 20.456 }, { x: 140, y: -4 }]
|
|
}
|
|
]
|
|
}
|
|
});
|
|
|
|
assert.equal(compact.markers[0].x, 100);
|
|
assert.deepEqual(compact.drawings.strokes[0].points, [{ x: 10.12, y: 20.46 }, { x: 100, y: 0 }]);
|
|
|
|
const exported = createToolboxExportPayload(
|
|
{ id: "toolbox1", name: "Map", modules: [{ id: "module1", type: "imageAnnotation" }], updatedAt: "2026-01-01T00:00:00.000Z" },
|
|
{ "toolbox1:module1": compact }
|
|
);
|
|
assert.equal(exported.modules.m1.markers[0].id, "k1");
|
|
assert.equal(exported.modules.m1.drawings.strokes[0].id, "d1");
|
|
});
|
|
|
|
test("task planner data normalizes invalid settings and relations", () => {
|
|
const normalized = normalizeTaskPlannerData({
|
|
weeklyResetDay: 9,
|
|
resetTime: "27:80",
|
|
hideCompleted: true,
|
|
categoryOrder: ["Raid", "Missing", "Farm", "Raid"],
|
|
collapsedCategories: ["Raid", "", "Raid"],
|
|
tasks: [
|
|
{ id: "task1", title: "Daily", type: "daily", checked: true, checkedAt: 1000, category: "Farm" },
|
|
{ id: "task2", title: "Weekly", type: "weekly", weeklyResetDay: 4, category: "Raid" },
|
|
{ id: "task3", title: "Invalid type", type: "later", category: "Farm" }
|
|
],
|
|
relations: [
|
|
{ id: "link1", fromTaskId: "task1", toTaskId: "task2" },
|
|
{ id: "dep1", fromTaskId: "task3", toTaskId: "task2", prerequisite: true },
|
|
{ id: "bad", fromTaskId: "task1", toTaskId: "missing" },
|
|
{ id: "self", fromTaskId: "task2", toTaskId: "task2" }
|
|
]
|
|
});
|
|
|
|
assert.equal(normalized.weeklyResetDay, 1);
|
|
assert.equal(normalized.resetTime, "00:00");
|
|
assert.equal(normalized.hideCompleted, true);
|
|
assert.deepEqual(normalized.categoryOrder, ["Raid"]);
|
|
assert.deepEqual(normalized.collapsedCategories, ["Raid"]);
|
|
assert.equal(normalized.tasks[0].category, undefined);
|
|
assert.equal(normalized.tasks[1].weeklyResetDay, 4);
|
|
assert.equal(normalized.tasks[1].category, "Raid");
|
|
assert.equal(normalized.tasks[2].type, "unique");
|
|
assert.deepEqual(normalized.relations.map((relation) => relation.id), ["link1", "dep1"]);
|
|
assert.equal(normalized.relations[0].prerequisite, false);
|
|
assert.equal(normalized.relations[1].prerequisite, true);
|
|
});
|
|
|
|
test("task planner storage compacts defaults and export remaps task relations", () => {
|
|
const compact = compactModuleDataForStorage("taskPlanner", {
|
|
weeklyResetDay: 1,
|
|
resetTime: "00:00",
|
|
hideCompleted: true,
|
|
collapsedCategories: ["Raid"],
|
|
tasks: [
|
|
{ id: "task1", title: "Daily", description: "", type: "daily", checked: false },
|
|
{ id: "task2", title: "Weekly", description: "Run", type: "weekly", checked: true, checkedAt: 2000, weeklyResetDay: 5, category: "Raid" }
|
|
],
|
|
categoryOrder: ["Raid"],
|
|
relations: [
|
|
{ id: "dep1", fromTaskId: "task1", toTaskId: "task2", prerequisite: true }
|
|
]
|
|
});
|
|
|
|
assert.equal(compact.weeklyResetDay, undefined);
|
|
assert.equal(compact.resetTime, undefined);
|
|
assert.equal(compact.hideCompleted, true);
|
|
assert.deepEqual(compact.collapsedCategories, ["Raid"]);
|
|
assert.deepEqual(compact.categoryOrder, ["Raid"]);
|
|
assert.deepEqual(compact.tasks[0], { id: "task1", title: "Daily", type: "daily" });
|
|
assert.equal(compact.tasks[1].category, "Raid");
|
|
assert.equal(compact.tasks[1].weeklyResetDay, 5);
|
|
|
|
const exported = createToolboxExportPayload(
|
|
{ id: "toolbox1", name: "Planner", modules: [{ id: "module1", type: "taskPlanner" }], updatedAt: "2026-01-01T00:00:00.000Z" },
|
|
{ "toolbox1:module1": compact }
|
|
);
|
|
const exportedPlanner = exported.modules.m1;
|
|
assert.deepEqual(exportedPlanner.tasks.map((task) => task.id), ["a1", "a2"]);
|
|
assert.equal(exportedPlanner.relations[0].fromTaskId, "a1");
|
|
assert.equal(exportedPlanner.relations[0].toTaskId, "a2");
|
|
assert.equal(exportedPlanner.relations[0].prerequisite, true);
|
|
});
|