Add equipment planner toolbox module
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
This commit is contained in:
parent
ac33a55c8b
commit
d53d4160af
33 changed files with 3522 additions and 20 deletions
|
|
@ -4,7 +4,7 @@ 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, normalizeChecklistData, normalizeCombosData, normalizeNotepadData, normalizeTableData, normalizeTaskPlannerData } from "../website/src/features/toolboxes/storage/toolboxStorage.js";
|
||||
import { compactModuleDataForStorage, createToolboxExportPayload, normalizeChecklistData, normalizeCombosData, normalizeEquipmentPlannerData, normalizeNotepadData, normalizeTableData, normalizeTaskPlannerData, 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";
|
||||
|
||||
test("colon text import keeps urls intact after the first separator", () => {
|
||||
|
|
@ -96,6 +96,228 @@ test("table storage clamps dimensions and compacts non-empty cells", () => {
|
|||
assert.equal(compactModuleDataForStorage("table", { rows: 10, columns: 6, cells: {} }), null);
|
||||
});
|
||||
|
||||
test("equipment planner storage normalizes sockets, materials and links", () => {
|
||||
const normalized = normalizeEquipmentPlannerData({
|
||||
types: [
|
||||
{ id: "type1", title: "Armes" },
|
||||
{ id: "empty", title: "Vide" }
|
||||
],
|
||||
equipments: [
|
||||
{
|
||||
id: "eq1",
|
||||
typeId: "type1",
|
||||
name: "Épée",
|
||||
active: false,
|
||||
characteristics: [
|
||||
{ id: "trait1", category: "Stats", name: "Force", value: "12" },
|
||||
{ id: "ignored", category: "Stats", name: "", value: 4 }
|
||||
],
|
||||
materials: [
|
||||
{ id: "mat1", name: "Minerai", qty: 0 }
|
||||
],
|
||||
socketItems: [
|
||||
{
|
||||
id: "socket1",
|
||||
name: "Materia feu",
|
||||
shape: "triangle",
|
||||
color: "invalid",
|
||||
bonuses: [
|
||||
{ id: "bonus1", category: "Stats", name: "Force", value: "3" },
|
||||
{ id: "bonus2", category: "Compétences", name: "", value: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "socket2",
|
||||
name: "Materia soin",
|
||||
shape: "ball",
|
||||
color: "cyan",
|
||||
bonuses: [
|
||||
{ id: "bonus3", category: "Sorts", name: "Soin", value: "" }
|
||||
]
|
||||
}
|
||||
],
|
||||
socketLinks: [
|
||||
{ id: "link1", fromSocketItemId: "socket1", toSocketItemId: "socket2" },
|
||||
{ id: "duplicate", fromSocketItemId: "socket2", toSocketItemId: "socket1" },
|
||||
{ id: "invalid", fromSocketItemId: "socket1", toSocketItemId: "missing" }
|
||||
],
|
||||
categoryOrder: ["Compétences", "Stats", "Missing"],
|
||||
collapsedCategories: ["Stats", "Missing"]
|
||||
}
|
||||
],
|
||||
typeOrder: ["empty", "type1"]
|
||||
});
|
||||
|
||||
assert.deepEqual(normalized.types.map((type) => type.id), ["type1"]);
|
||||
assert.equal(normalized.equipments[0].active, false);
|
||||
assert.equal(normalized.equipments[0].materials[0].qty, 1);
|
||||
assert.equal(normalized.equipments[0].characteristics.length, 1);
|
||||
assert.equal(normalized.equipments[0].socketItems[0].shape, "jewel");
|
||||
assert.equal(normalized.equipments[0].socketItems[0].color, "yellow");
|
||||
assert.equal(normalized.equipments[0].socketItems[0].bonuses.length, 1);
|
||||
assert.equal(normalized.equipments[0].socketLinks.length, 1);
|
||||
assert.deepEqual(normalized.equipments[0].categoryOrder, ["Stats", "Sorts"]);
|
||||
assert.deepEqual(normalized.equipments[0].collapsedCategories, ["Stats"]);
|
||||
|
||||
const compact = compactModuleDataForStorage("equipmentPlanner", normalized);
|
||||
assert.equal(compact.equipments[0].active, false);
|
||||
assert.equal(compact.equipments[0].socketItems[0].shape, "jewel");
|
||||
assert.equal(compact.equipments[0].socketLinks.length, 1);
|
||||
assert.equal(compactModuleDataForStorage("equipmentPlanner", { types: [{ id: "empty", title: "Vide" }], equipments: [] }), null);
|
||||
});
|
||||
|
||||
test("equipment planner summary only uses active equipments", () => {
|
||||
const summary = summarizeEquipmentPlannerData({
|
||||
types: [{ id: "type1", title: "Build" }],
|
||||
equipments: [
|
||||
{
|
||||
id: "eq1",
|
||||
typeId: "type1",
|
||||
name: "Casque",
|
||||
active: true,
|
||||
characteristics: [{ id: "trait1", category: "Stats", name: "Force", value: 10 }],
|
||||
socketItems: [{
|
||||
id: "socket1",
|
||||
name: "Joyau attaque",
|
||||
shape: "ball",
|
||||
color: "yellow",
|
||||
bonuses: [
|
||||
{ id: "bonus1", category: "Stats", name: "Force", value: 2 },
|
||||
{ id: "bonus2", category: "Affixes", name: "Brutalité", value: "" }
|
||||
]
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: "eq2",
|
||||
typeId: "type1",
|
||||
name: "Armure",
|
||||
active: false,
|
||||
characteristics: [{ id: "trait2", category: "Stats", name: "Force", value: 99 }]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
assert.equal(summary.activeEquipmentCount, 1);
|
||||
assert.deepEqual(summary.totals, [{
|
||||
name: "Force",
|
||||
icon: "sword",
|
||||
sourceKind: "mixed",
|
||||
contributions: [
|
||||
{ id: "trait1", sourceKind: "direct", equipmentIcon: "shield", equipmentName: "Casque", sourceName: "Casque", value: 10 },
|
||||
{ id: "bonus1", sourceKind: "socket", equipmentIcon: "shield", equipmentName: "Casque", sourceName: "Casque · Joyau attaque", value: 2 }
|
||||
],
|
||||
value: 12
|
||||
}]);
|
||||
assert.deepEqual(summary.equipmentTotals, []);
|
||||
assert.deepEqual(summary.socketTotals, []);
|
||||
assert.deepEqual(summary.mixedTotals, summary.totals);
|
||||
assert.equal(summary.textBonuses.length, 1);
|
||||
assert.equal(summary.textBonuses[0].name, "Brutalité");
|
||||
});
|
||||
|
||||
test("equipment planner summary prioritizes direct stat icons over socket icons", () => {
|
||||
const summary = summarizeEquipmentPlannerData({
|
||||
types: [{ id: "type1", title: "Build" }],
|
||||
equipments: [
|
||||
{
|
||||
id: "eq1",
|
||||
typeId: "type1",
|
||||
name: "Épée",
|
||||
characteristics: [{ id: "trait1", category: "Stats", icon: "shield", name: "Attaque", value: 100 }],
|
||||
socketItems: [{
|
||||
id: "socket1",
|
||||
name: "Joyau rouge",
|
||||
shape: "jewel",
|
||||
color: "red",
|
||||
bonuses: [
|
||||
{ id: "bonus1", category: "Stats", name: "Attaque", value: 10 },
|
||||
{ id: "bonus2", category: "Compétences", name: "Attaque de feu", value: 1 }
|
||||
]
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: "eq2",
|
||||
typeId: "type1",
|
||||
name: "Fusil",
|
||||
characteristics: [
|
||||
{ id: "trait2", category: "Stats", icon: "sword", name: "Défense", value: 10 },
|
||||
{ id: "trait3", category: "Stats", icon: "sword", name: "Défense", value: 20 },
|
||||
{ id: "trait4", category: "Stats", icon: "shield", name: "Défense", value: 30 }
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
assert.deepEqual(summary.totals.find((total) => total.name === "Attaque"), {
|
||||
name: "Attaque",
|
||||
icon: "shield",
|
||||
sourceKind: "mixed",
|
||||
contributions: [
|
||||
{ id: "trait1", sourceKind: "direct", equipmentIcon: "shield", equipmentName: "Épée", sourceName: "Épée", value: 100 },
|
||||
{ id: "bonus1", sourceKind: "socket", equipmentIcon: "shield", equipmentName: "Épée", sourceName: "Épée · Joyau rouge", value: 10 }
|
||||
],
|
||||
value: 110
|
||||
});
|
||||
assert.deepEqual(summary.totals.find((total) => total.name === "Attaque de feu"), {
|
||||
name: "Attaque de feu",
|
||||
icon: "jewel",
|
||||
color: "red",
|
||||
sourceKind: "socket",
|
||||
contributions: [{ id: "bonus2", sourceKind: "socket", equipmentIcon: "shield", equipmentName: "Épée", sourceName: "Épée · Joyau rouge", value: 1 }],
|
||||
value: 1
|
||||
});
|
||||
assert.deepEqual(summary.totals.find((total) => total.name === "Défense"), {
|
||||
name: "Défense",
|
||||
icon: "sword",
|
||||
sourceKind: "direct",
|
||||
contributions: [
|
||||
{ id: "trait2", sourceKind: "direct", equipmentIcon: "shield", equipmentName: "Fusil", sourceName: "Fusil", value: 10 },
|
||||
{ id: "trait3", sourceKind: "direct", equipmentIcon: "shield", equipmentName: "Fusil", sourceName: "Fusil", value: 20 },
|
||||
{ id: "trait4", sourceKind: "direct", equipmentIcon: "shield", equipmentName: "Fusil", sourceName: "Fusil", value: 30 }
|
||||
],
|
||||
value: 60
|
||||
});
|
||||
assert.deepEqual(summary.equipmentTotals.map((total) => total.name), ["Défense"]);
|
||||
assert.deepEqual(summary.socketTotals.map((total) => total.name), ["Attaque de feu"]);
|
||||
assert.deepEqual(summary.mixedTotals.map((total) => total.name), ["Attaque"]);
|
||||
});
|
||||
|
||||
test("equipment planner export remaps nested ids and socket links", () => {
|
||||
const payload = createToolboxExportPayload(
|
||||
{
|
||||
id: "toolbox1",
|
||||
name: "Build",
|
||||
modules: [{ id: "module1", type: "equipmentPlanner" }]
|
||||
},
|
||||
{
|
||||
"toolbox1:module1": {
|
||||
types: [{ id: "type1", title: "Build" }],
|
||||
equipments: [{
|
||||
id: "eq1",
|
||||
typeId: "type1",
|
||||
name: "Casque",
|
||||
characteristics: [{ id: "trait1", name: "Force", value: 1 }],
|
||||
materials: [{ id: "mat1", name: "Minerai", qty: 2 }],
|
||||
socketItems: [
|
||||
{ id: "socket1", name: "Joyau attaque", shape: "ball", color: "yellow", bonuses: [{ id: "bonus1", name: "Attaque", value: 1 }] },
|
||||
{ id: "socket2", name: "Joyau expert", shape: "jewel", color: "cyan", bonuses: [] }
|
||||
],
|
||||
socketLinks: [{ id: "link1", fromSocketItemId: "socket1", toSocketItemId: "socket2" }]
|
||||
}],
|
||||
typeOrder: ["type1"]
|
||||
}
|
||||
}
|
||||
);
|
||||
const moduleId = payload.toolbox.modules[0].id;
|
||||
const exported = payload.modules[moduleId];
|
||||
|
||||
assert.notEqual(exported.types[0].id, "type1");
|
||||
assert.equal(exported.equipments[0].typeId, exported.types[0].id);
|
||||
assert.notEqual(exported.equipments[0].socketItems[0].id, "socket1");
|
||||
assert.equal(exported.equipments[0].socketLinks[0].fromSocketItemId, exported.equipments[0].socketItems[0].id);
|
||||
assert.equal(exported.equipments[0].socketLinks[0].toSocketItemId, exported.equipments[0].socketItems[1].id);
|
||||
});
|
||||
|
||||
test("grouped reorder helpers group flat items and complete group order", () => {
|
||||
const items = [
|
||||
{ id: "a", category: "" },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue