This commit is contained in:
parent
07de342fc2
commit
35fe877d58
32 changed files with 3822 additions and 200 deletions
|
|
@ -11,8 +11,9 @@ import { createDefaultDraft, getDateSpanDays, getDraftSelection, normalizeDraftA
|
|||
import { getMonthOccurrenceSegments, getWeekGridLayout, getWeekMinutePosition, layoutConcurrentOccurrences } from "../website/src/features/toolboxes/modules/calendarLayout.js";
|
||||
import { appendChecklistItems, moveChecklistItem, moveChecklistItemToCategory, renameChecklistSection } from "../website/src/features/toolboxes/modules/checklistUtils.js";
|
||||
import { formatUpdatedAt, isHtmlEmpty, queryBlockFormat, queryCommandState } from "../website/src/features/toolboxes/modules/notepadUtils.js";
|
||||
import { createBalancedTeams, drawWeightedEntries, filterExclusionsForPlayers, getDiceShape, rollDice } from "../website/src/features/toolboxes/modules/randomizerUtils.js";
|
||||
import { getTimePatternRecurrenceMs, getTimePatternTargetMs } from "../website/src/features/toolboxes/modules/timerUtils.js";
|
||||
import { compactModuleDataForStorage, createToolboxExportPayload, normalizeCalculatorData, normalizeChecklistData, normalizeCombosData, normalizeCountersData, normalizeEquipmentPlannerData, normalizeGoalTreeData, normalizeImageAnnotationData, normalizeLinksData, normalizeMemosData, normalizeModuleOrder, normalizeNotepadData, normalizeTableData, normalizeTaskPlannerData, normalizeTimerData, normalizeToolbox, normalizeUrl, summarizeEquipmentPlannerData } from "../website/src/features/toolboxes/storage/toolboxStorage.js";
|
||||
import { compactModuleDataForStorage, createToolboxExportPayload, moduleStorageKey, normalizeCalculatorData, normalizeChecklistData, normalizeCombosData, normalizeCountersData, normalizeEquipmentPlannerData, normalizeGoalTreeData, normalizeImageAnnotationData, normalizeLinksData, normalizeMemosData, normalizeModuleOrder, normalizeNotepadData, normalizeRandomizerData, 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() {
|
||||
|
|
@ -32,6 +33,7 @@ function createTextImportContext() {
|
|||
normalizeGoalTreeData,
|
||||
normalizeEquipmentPlannerData,
|
||||
normalizeImageAnnotationData,
|
||||
normalizeRandomizerData,
|
||||
normalizeUrl
|
||||
};
|
||||
}
|
||||
|
|
@ -132,6 +134,156 @@ test("text exchange round trips simple toolbox modules and resets checklist prog
|
|||
assert.deepEqual(memosImport.data.items.map((memo) => [memo.text, memo.color]), [["Route", "#f6c453"], ["Potion", "#22d3ee"]]);
|
||||
});
|
||||
|
||||
test("randomizer helpers roll dice, draw weighted values and balance teams", () => {
|
||||
assert.equal(getDiceShape(2), "circle");
|
||||
assert.equal(getDiceShape(3), "triangle");
|
||||
assert.equal(getDiceShape(6), "square");
|
||||
assert.equal(getDiceShape(12), "hexagon");
|
||||
assert.deepEqual(rollDice(6, 3, () => 0), [1, 1, 1]);
|
||||
assert.deepEqual(rollDice(6, 2, () => 0.99), [6, 6]);
|
||||
|
||||
const wheelDraws = drawWeightedEntries([
|
||||
{ id: "a", label: "A", weight: 1 },
|
||||
{ id: "b", label: "B", weight: 3 }
|
||||
], 2, () => 0.9);
|
||||
assert.deepEqual(wheelDraws.map((entry) => entry.label), ["B", "B"]);
|
||||
|
||||
const result = createBalancedTeams([
|
||||
{ id: "p1", name: "Alice", score: 10 },
|
||||
{ id: "p2", name: "Bob", score: 8 },
|
||||
{ id: "p3", name: "Chloé", score: 7 },
|
||||
{ id: "p4", name: "Dorian", score: 5 }
|
||||
], 2, [{ id: "e1", playerAId: "p1", playerBId: "p2" }], () => 0.4);
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.teams.length, 2);
|
||||
assert.ok(result.teams.every((team) => team.players.length === 2));
|
||||
assert.ok(!result.teams.some((team) => team.players.some((player) => player.id === "p1") && team.players.some((player) => player.id === "p2")));
|
||||
|
||||
const singleTeam = createBalancedTeams([
|
||||
{ id: "p1", name: "Bulbizarre", score: 0 },
|
||||
{ id: "p2", name: "Salamèche", score: 0 },
|
||||
{ id: "p3", name: "Carapuce", score: 0 }
|
||||
], 1, [], () => 0.4);
|
||||
assert.equal(singleTeam.ok, true);
|
||||
assert.equal(singleTeam.teams.length, 1);
|
||||
assert.equal(singleTeam.teams[0].players.length, 3);
|
||||
});
|
||||
|
||||
test("randomizer storage compacts values and detects impossible teams", () => {
|
||||
const normalized = normalizeRandomizerData({
|
||||
activeTab: "teams",
|
||||
dice: { faces: 2000, rolls: 200, history: [{ id: "old", at: "2026-08-30T12:00:00.000Z", results: [1, 2, 6] }] },
|
||||
wheel: { entries: [{ id: "w1", label: "Rare", weight: 1000 }, { label: "", weight: 1 }] },
|
||||
teams: {
|
||||
teamCount: 2,
|
||||
playerSelectionMode: "count",
|
||||
playerSelectionCount: 3,
|
||||
players: [
|
||||
{ id: "p1", name: "Alice", score: 10 },
|
||||
{ id: "p2", name: "Bob", score: 8 },
|
||||
{ id: "p3", name: "Chloé", score: 7 }
|
||||
],
|
||||
exclusions: [
|
||||
{ id: "e1", playerAId: "p1", playerBId: "p2", mode: "group" },
|
||||
{ id: "e2", playerAId: "p2", playerBId: "p1" },
|
||||
{ id: "e3", playerAId: "p1", playerBId: "missing" }
|
||||
]
|
||||
}
|
||||
});
|
||||
assert.equal(normalized.dice.faces, 999);
|
||||
assert.equal(normalized.dice.rolls, 100);
|
||||
assert.deepEqual(normalized.dice.history[0].results, [1, 2, 6]);
|
||||
assert.equal(normalized.wheel.entries[0].weight, 999);
|
||||
assert.equal(normalized.teams.exclusions.length, 1);
|
||||
assert.deepEqual(normalized.teams.exclusions[0].playerBIds, ["p2"]);
|
||||
assert.equal(normalized.teams.exclusions[0].mode, "group");
|
||||
assert.equal(normalized.teams.playerSelectionMode, "count");
|
||||
assert.equal(normalized.teams.playerSelectionCount, 3);
|
||||
assert.equal(compactModuleDataForStorage("randomizer", normalizeRandomizerData({})), null);
|
||||
assert.equal(compactModuleDataForStorage("randomizer", normalized).activeTab, "teams");
|
||||
|
||||
const impossible = createBalancedTeams([
|
||||
{ id: "p1", name: "Alice", score: 10 },
|
||||
{ id: "p2", name: "Bob", score: 8 },
|
||||
{ id: "p3", name: "Chloé", score: 7 }
|
||||
], 2, [
|
||||
{ id: "e1", playerAId: "p1", playerBId: "p2" },
|
||||
{ id: "e2", playerAId: "p1", playerBId: "p3" },
|
||||
{ id: "e3", playerAId: "p2", playerBId: "p3" }
|
||||
], () => 0.2);
|
||||
assert.equal(impossible.ok, false);
|
||||
assert.equal(impossible.error, "constraints_impossible");
|
||||
|
||||
const impossibleGroup = createBalancedTeams([
|
||||
{ id: "p1", name: "Dracolosse", score: 10 },
|
||||
{ id: "p2", name: "Drattak", score: 9 },
|
||||
{ id: "p3", name: "Carchacrok", score: 8 },
|
||||
{ id: "p4", name: "Lanssorien", score: 7 }
|
||||
], 3, [
|
||||
{ id: "e1", playerAId: "p1", playerBIds: ["p2", "p3", "p4"], mode: "group" }
|
||||
], () => 0.2);
|
||||
assert.equal(impossibleGroup.ok, false);
|
||||
assert.equal(impossibleGroup.error, "constraints_impossible");
|
||||
|
||||
const scopedGroupExclusions = filterExclusionsForPlayers([
|
||||
{ id: "e1", playerAId: "p1", playerBIds: ["p2", "p3", "p4"], mode: "group" }
|
||||
], [
|
||||
{ id: "p3", name: "Carchacrok", score: 8 },
|
||||
{ id: "p4", name: "Lanssorien", score: 7 },
|
||||
{ id: "p5", name: "Corvaillus", score: 6 }
|
||||
]);
|
||||
assert.equal(scopedGroupExclusions.length, 1);
|
||||
assert.equal(scopedGroupExclusions[0].playerAId, "p3");
|
||||
assert.deepEqual(scopedGroupExclusions[0].playerBIds, ["p4"]);
|
||||
|
||||
const scopedGroupResult = createBalancedTeams([
|
||||
{ id: "p3", name: "Carchacrok", score: 8 },
|
||||
{ id: "p4", name: "Lanssorien", score: 7 },
|
||||
{ id: "p5", name: "Corvaillus", score: 6 }
|
||||
], 2, scopedGroupExclusions, () => 0.2);
|
||||
assert.equal(scopedGroupResult.ok, true);
|
||||
assert.ok(!scopedGroupResult.teams.some((team) => team.players.some((player) => player.id === "p3") && team.players.some((player) => player.id === "p4")));
|
||||
});
|
||||
|
||||
test("randomizer text exchange round trips full configuration", () => {
|
||||
const context = createTextImportContext();
|
||||
const randomizer = normalizeRandomizerData({
|
||||
activeTab: "wheel",
|
||||
dice: { faces: 6, rolls: 3 },
|
||||
wheel: {
|
||||
draws: 2,
|
||||
entries: [
|
||||
{ id: "w1", label: "Route farm", weight: 3 },
|
||||
{ id: "w2", label: "Boss rare", weight: 1 }
|
||||
]
|
||||
},
|
||||
teams: {
|
||||
teamCount: 2,
|
||||
playerSelectionMode: "count",
|
||||
playerSelectionCount: 3,
|
||||
players: [
|
||||
{ id: "p1", name: "Alice", score: 10 },
|
||||
{ id: "p2", name: "Bob", score: 8 },
|
||||
{ id: "p3", name: "Chloé", score: 7 }
|
||||
],
|
||||
exclusions: [{ id: "e1", playerAId: "p1", playerBIds: ["p2", "p3"], mode: "group" }]
|
||||
}
|
||||
});
|
||||
const imported = importModuleText("randomizer", exportModuleText("randomizer", randomizer), context);
|
||||
assert.equal(imported.ok, true);
|
||||
assert.equal(imported.data.activeTab, "wheel");
|
||||
assert.deepEqual(imported.data.wheel.entries.map((entry) => [entry.label, entry.weight]), [["Route farm", 3], ["Boss rare", 1]]);
|
||||
assert.equal(imported.data.teams.playerSelectionMode, "count");
|
||||
assert.equal(imported.data.teams.playerSelectionCount, 3);
|
||||
assert.deepEqual(imported.data.teams.players.map((player) => [player.name, player.score]), [["Alice", 10], ["Bob", 8], ["Chloé", 7]]);
|
||||
assert.equal(imported.data.teams.exclusions.length, 1);
|
||||
assert.equal(imported.data.teams.exclusions[0].mode, "group");
|
||||
assert.deepEqual(
|
||||
imported.data.teams.exclusions[0].playerBIds.map((playerId) => imported.data.teams.players.find((player) => player.id === playerId)?.name),
|
||||
["Bob", "Chloé"]
|
||||
);
|
||||
});
|
||||
|
||||
test("notepad helpers keep empty html and invalid command states stable", () => {
|
||||
assert.equal(isHtmlEmpty("<p><br></p>", ""), true);
|
||||
assert.equal(isHtmlEmpty("<p> </p>", ""), true);
|
||||
|
|
@ -673,6 +825,40 @@ test("toolbox export remaps module order ids", () => {
|
|||
assert.deepEqual(normalizeToolbox(payload.toolbox).moduleOrder, payload.toolbox.moduleOrder);
|
||||
});
|
||||
|
||||
test("toolbox export remaps randomizer multi-target exclusions", () => {
|
||||
const payload = createToolboxExportPayload(
|
||||
{
|
||||
id: "toolbox1",
|
||||
name: "Randomizer",
|
||||
modules: [{ id: "module1", type: "randomizer" }],
|
||||
moduleOrder: {
|
||||
one: ["module1"],
|
||||
two: [["module1"], []]
|
||||
}
|
||||
},
|
||||
{
|
||||
[moduleStorageKey("toolbox1", "module1")]: normalizeRandomizerData({
|
||||
teams: {
|
||||
players: [
|
||||
{ id: "p1", name: "Dracolosse" },
|
||||
{ id: "p2", name: "Drattak" },
|
||||
{ id: "p3", name: "Carchacrok" },
|
||||
{ id: "p4", name: "Lanssorien" }
|
||||
],
|
||||
exclusions: [{ id: "e1", playerAId: "p1", playerBIds: ["p2", "p3", "p4"], mode: "group" }]
|
||||
}
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
const moduleData = Object.values(payload.modules)[0];
|
||||
assert.equal(moduleData.teams.exclusions.length, 1);
|
||||
assert.equal(moduleData.teams.exclusions[0].mode, "group");
|
||||
assert.equal(moduleData.teams.exclusions[0].playerBIds.length, 3);
|
||||
assert.ok(!moduleData.teams.exclusions[0].playerBIds.includes(undefined));
|
||||
assert.ok(moduleData.teams.exclusions[0].playerBIds.every((playerId) => playerId !== "p2" && playerId !== "p3" && playerId !== "p4"));
|
||||
});
|
||||
|
||||
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