Add toolbox spaces and recover from stale asset bundles
All checks were successful
Deploy Sokko G / deploy (push) Successful in 18s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 18s
This commit is contained in:
parent
0959c6740b
commit
0acf5ecf21
30 changed files with 1252 additions and 179 deletions
|
|
@ -217,6 +217,24 @@ export function validateSiteContent(site) {
|
|||
"toolboxes.storageStatus.errorMessage",
|
||||
"toolboxes.storageStatus.retryLabel",
|
||||
"toolboxes.storageStatus.writeErrorTitle",
|
||||
"toolboxes.spaces.toolbarLabel",
|
||||
"toolboxes.spaces.tabsLabel",
|
||||
"toolboxes.spaces.addLabel",
|
||||
"toolboxes.spaces.createTitle",
|
||||
"toolboxes.spaces.renameTitle",
|
||||
"toolboxes.spaces.renameLabel",
|
||||
"toolboxes.spaces.deleteTitle",
|
||||
"toolboxes.spaces.deleteLabel",
|
||||
"toolboxes.spaces.deleteEmpty",
|
||||
"toolboxes.spaces.deleteWithTools",
|
||||
"toolboxes.spaces.nameLabel",
|
||||
"toolboxes.spaces.cancelLabel",
|
||||
"toolboxes.spaces.saveLabel",
|
||||
"toolboxes.spaces.reorderLabel",
|
||||
"toolboxes.spaces.moveLabel",
|
||||
"toolboxes.spaces.moveSuccess",
|
||||
"toolboxes.spaces.emptyText",
|
||||
"toolboxes.spaces.limitReached",
|
||||
"toolboxes.title",
|
||||
"toolboxes.newButton",
|
||||
"toolboxes.libraryLink",
|
||||
|
|
@ -1150,15 +1168,20 @@ export function validateLibraryData(payload) {
|
|||
moduleIds.add(module.id);
|
||||
assert.equal(typeof payload.modules[module.id], "object", `library.modules.${module.id} must be an object`);
|
||||
});
|
||||
assert.deepEqual(payload.toolbox.moduleOrder?.one, [...moduleIds], "library.toolbox.moduleOrder.one must match toolbox modules");
|
||||
assert.ok(Array.isArray(payload.toolbox.moduleOrder?.two), "library.toolbox.moduleOrder.two must be an array");
|
||||
assert.equal(payload.toolbox.moduleOrder.two.length, 2, "library.toolbox.moduleOrder.two must contain 2 columns");
|
||||
payload.toolbox.moduleOrder.two.forEach((column, index) => {
|
||||
assert.ok(Array.isArray(column), `library.toolbox.moduleOrder.two[${index}] must be an array`);
|
||||
assert.ok(Array.isArray(payload.toolbox.spaces), "library.toolbox.spaces must be an array");
|
||||
assert.equal(payload.toolbox.spaces.length, 1, "library.toolbox must declare one default space");
|
||||
const defaultSpace = payload.toolbox.spaces[0];
|
||||
assert.equal(payload.toolbox.defaultSpaceId, defaultSpace.id, "library.toolbox.defaultSpaceId must target the default space");
|
||||
assert.equal(payload.toolbox.activeSpaceId, defaultSpace.id, "library.toolbox.activeSpaceId must target the default space");
|
||||
assert.deepEqual(defaultSpace.moduleOrder?.one, [...moduleIds], "library default space order must match toolbox modules");
|
||||
assert.ok(Array.isArray(defaultSpace.moduleOrder?.two), "library default space two-column order must be an array");
|
||||
assert.equal(defaultSpace.moduleOrder.two.length, 2, "library default space must contain 2 columns");
|
||||
defaultSpace.moduleOrder.two.forEach((column, index) => {
|
||||
assert.ok(Array.isArray(column), `library default space column ${index} must be an array`);
|
||||
});
|
||||
const twoColumnIds = payload.toolbox.moduleOrder.two.flat();
|
||||
assert.deepEqual(new Set(twoColumnIds), moduleIds, "library.toolbox.moduleOrder.two must contain every module exactly once");
|
||||
assert.equal(twoColumnIds.length, moduleIds.size, "library.toolbox.moduleOrder.two must not contain duplicate modules");
|
||||
const twoColumnIds = defaultSpace.moduleOrder.two.flat();
|
||||
assert.deepEqual(new Set(twoColumnIds), moduleIds, "library default space must contain every module exactly once");
|
||||
assert.equal(twoColumnIds.length, moduleIds.size, "library default space must not contain duplicate modules");
|
||||
|
||||
["memos", "notepad", "checklist", "images", "links", "counters", "combos", "calculator", "table", "goalTree", "timer", "calendar", "taskPlanner", "equipmentPlanner", "imageAnnotation"].forEach((type) => {
|
||||
assert.ok(payload.toolbox.modules.some((module) => module.type === type), `library must include a ${type} example`);
|
||||
|
|
|
|||
|
|
@ -137,6 +137,32 @@ test("global imports started from the same render both survive", async () => {
|
|||
assert.deepEqual(queue.getCurrent(), disk);
|
||||
});
|
||||
|
||||
test("v2 imports reject more than five spaces before writing", async () => {
|
||||
let writes = 0;
|
||||
let errorMessage = "";
|
||||
const actions = useToolboxActions({
|
||||
store: {
|
||||
ready: true,
|
||||
persistToolboxSnapshot: () => { writes += 1; return Promise.resolve(true); }
|
||||
},
|
||||
notify() {},
|
||||
setConfirmModal: (value) => { errorMessage = value.message; }
|
||||
});
|
||||
const result = await actions.importToolbox({ text: async () => JSON.stringify({
|
||||
version: 2,
|
||||
toolbox: {
|
||||
id: "overflow",
|
||||
name: "Overflow",
|
||||
modules: [],
|
||||
spaces: Array.from({ length: 6 }, (_, index) => ({ id: `space-${index}`, name: `Space ${index}`, moduleOrder: { one: [], two: [[], []] } }))
|
||||
},
|
||||
modules: {}
|
||||
}) });
|
||||
assert.equal(result, null);
|
||||
assert.equal(writes, 0);
|
||||
assert.match(errorMessage, /plus de 5 espaces/);
|
||||
});
|
||||
|
||||
test("failed toolbox deletion preserves registry links and modules together", async () => {
|
||||
const initial = {
|
||||
toolboxes: [{ id: "existing", modules: [{ id: "note", type: "notepad" }] }],
|
||||
|
|
|
|||
|
|
@ -19,7 +19,12 @@ test("vite entrypoint and app shell are wired", async () => {
|
|||
assert.match(html, /<div id="app"><\/div>/);
|
||||
assert.match(html, /\/favicon\.ico/);
|
||||
assert.match(html, /\/src\/main\.jsx/);
|
||||
assert.match(html, /_asset-recovery/);
|
||||
assert.match(html, /vite:preloadError/);
|
||||
assert.match(html, /HTMLScriptElement/);
|
||||
assert.match(source, /styles\/main\.scss/);
|
||||
assert.match(source, /searchParams\.has\("_asset-recovery"\)/);
|
||||
assert.match(source, /searchParams\.delete\("_asset-recovery"\)/);
|
||||
assert.match(source, /hooks\/useGameFilters\.js/);
|
||||
assert.doesNotMatch(source, /DEFAULT_SITE_CONTENT/);
|
||||
assert.match(gameFiltersHook, /export function useGameFilters/);
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
|||
const moduleRegistry = await readFile("website/src/features/toolboxes/modules/index.jsx", "utf8");
|
||||
const moduleComponents = await readFile("website/src/features/toolboxes/modules/toolboxModuleComponents.jsx", "utf8");
|
||||
const addToolControls = await readFile("website/src/features/toolboxes/modules/AddToolControls.jsx", "utf8");
|
||||
const toolboxPages = await readFile("website/src/features/toolboxes/ToolboxPages.jsx", "utf8");
|
||||
const toolboxModules = await readFile("website/src/features/toolboxes/modules/ToolboxModules.jsx", "utf8");
|
||||
const moduleShell = await readFile("website/src/features/toolboxes/modules/ModuleShell.jsx", "utf8");
|
||||
const moduleRegistryBundle = `${moduleRegistry}\n${moduleComponents}\n${addToolControls}\n${toolboxModules}\n${moduleShell}`;
|
||||
|
|
@ -222,7 +223,8 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
|||
assert.match(moduleShell, /module-edit-button/);
|
||||
assert.match(moduleShell, /itemReorder\.startDrag/);
|
||||
assert.match(moduleRegistryBundle, /onPointerDown/);
|
||||
assert.match(toolboxModules, /useGroupedReorder/);
|
||||
assert.match(toolboxPages, /useGroupedReorder/);
|
||||
assert.match(toolboxModules, /reorder=\{reorder\}/);
|
||||
assert.match(moduleShell, /useInlineEdit/);
|
||||
assert.match(moduleShell, /getContentEditableProps/);
|
||||
assert.match(addToolControls, /tool-add-card/);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { createBalancedTeams, drawWeightedEntries, filterExclusionsForPlayers, g
|
|||
import { calculateAtLeastOneProbability, calculateDrawsForChance, calculateRngCostSummary, calculateRngRealDrawsSummary, calculateRngSummary, getAveragePositionStatus, getDurationPresetsForAverageHours, getEffectiveRate, normalizeCostString, normalizeRateString, simulateRngDraws } from "../website/src/features/toolboxes/modules/rngSimulatorUtils.js";
|
||||
import { getTimePatternRecurrenceMs, getTimePatternTargetMs } from "../website/src/features/toolboxes/modules/timerUtils.js";
|
||||
import { compactModuleDataForStorage, createToolboxExportPayload, moduleStorageKey, normalizeCalculatorData, normalizeChecklistData, normalizeCombosData, normalizeCountersData, normalizeEquipmentPlannerData, normalizeGoalTreeData, normalizeImageAnnotationData, normalizeLinksData, normalizeMemosData, normalizeModuleOrder, normalizeNotepadData, normalizeRandomizerData, normalizeRngSimulatorData, normalizeTableData, normalizeTaskPlannerData, normalizeTimerData, normalizeToolbox, normalizeUrl, summarizeEquipmentPlannerData } from "../website/src/features/toolboxes/storage/toolboxStorage.js";
|
||||
import { createToolboxSpace, deleteToolboxSpace, moveToolboxModule, reorderToolboxSpaces } from "../website/src/features/toolboxes/toolboxSpaceUtils.js";
|
||||
import { applyGroupedReorderOperation, completeGroupOrder, getBoundaryItemId, getGroupedEntries, moveGroupOrder, moveGroupOrderToEnd, moveGroupOrderToStart, moveItem, moveItemGroup } from "../website/src/hooks/useGroupedReorder.js";
|
||||
|
||||
function createTextImportContext() {
|
||||
|
|
@ -989,6 +990,48 @@ test("toolbox modules preserve only active shell display options", () => {
|
|||
assert.equal("collapsed" in toolbox.modules[1], false);
|
||||
});
|
||||
|
||||
test("legacy toolbox order becomes a persistent default space", () => {
|
||||
const toolbox = normalizeToolbox({
|
||||
id: "legacy",
|
||||
modules: [{ id: "a", type: "notepad" }, { id: "b", type: "links" }],
|
||||
moduleOrder: { one: ["b", "a"], two: [["a"], ["b"]] }
|
||||
});
|
||||
assert.equal(toolbox.spaces.length, 1);
|
||||
assert.equal(toolbox.spaces[0].name, "Défaut");
|
||||
assert.equal(toolbox.defaultSpaceId, toolbox.spaces[0].id);
|
||||
assert.equal(toolbox.activeSpaceId, toolbox.spaces[0].id);
|
||||
assert.deepEqual(toolbox.spaces[0].moduleOrder, { one: ["b", "a"], two: [["a"], ["b"]] });
|
||||
});
|
||||
|
||||
test("spaces move modules, preserve inactive orders and merge deleted spaces into default", () => {
|
||||
let toolbox = normalizeToolbox({
|
||||
id: "spaces",
|
||||
modules: ["a", "b", "c"].map((id) => ({ id, type: "counters" })),
|
||||
moduleOrder: { one: ["a", "b", "c"], two: [["a", "b"], ["c"]] }
|
||||
});
|
||||
toolbox = createToolboxSpace(toolbox, "Boss");
|
||||
const bossId = toolbox.activeSpaceId;
|
||||
toolbox = moveToolboxModule(toolbox, "b", { targetSpaceId: bossId, moduleColumns: 1 });
|
||||
assert.deepEqual(toolbox.spaces[0].moduleOrder, { one: ["a", "c"], two: [["a"], ["c"]] });
|
||||
assert.deepEqual(toolbox.spaces[1].moduleOrder, { one: ["b"], two: [["b"], []] });
|
||||
toolbox = deleteToolboxSpace(toolbox, bossId);
|
||||
assert.deepEqual(toolbox.spaces[0].moduleOrder, { one: ["a", "c", "b"], two: [["a", "b"], ["c"]] });
|
||||
});
|
||||
|
||||
test("the default space stays first when secondary spaces are reordered", () => {
|
||||
let toolbox = normalizeToolbox({ modules: [] });
|
||||
toolbox = createToolboxSpace(toolbox, "Deux");
|
||||
const secondId = toolbox.activeSpaceId;
|
||||
toolbox = createToolboxSpace(toolbox, "Trois");
|
||||
const thirdId = toolbox.activeSpaceId;
|
||||
toolbox = reorderToolboxSpaces(toolbox, thirdId, secondId, "before");
|
||||
assert.equal(toolbox.spaces[0].id, toolbox.defaultSpaceId);
|
||||
assert.deepEqual(toolbox.spaces.slice(1).map((space) => space.id), [thirdId, secondId]);
|
||||
toolbox = createToolboxSpace(createToolboxSpace(toolbox, "Quatre"), "Cinq");
|
||||
assert.equal(toolbox.spaces.length, 5);
|
||||
assert.equal(createToolboxSpace(toolbox, "Six").spaces.length, 5);
|
||||
});
|
||||
|
||||
test("toolbox export remaps module order ids", () => {
|
||||
const payload = createToolboxExportPayload(
|
||||
{
|
||||
|
|
@ -1004,11 +1047,32 @@ test("toolbox export remaps module order ids", () => {
|
|||
);
|
||||
|
||||
assert.deepEqual(payload.toolbox.modules.map((module) => module.id), ["m1", "m2", "m3"]);
|
||||
assert.deepEqual(payload.toolbox.moduleOrder, {
|
||||
assert.equal(payload.version, 2);
|
||||
assert.deepEqual(payload.toolbox.spaces[0].moduleOrder, {
|
||||
one: ["m3", "m1", "m2"],
|
||||
two: [["m2"], ["m3", "m1"]]
|
||||
});
|
||||
assert.deepEqual(normalizeToolbox(payload.toolbox).moduleOrder, payload.toolbox.moduleOrder);
|
||||
assert.deepEqual(normalizeToolbox(payload.toolbox).spaces[0].moduleOrder, payload.toolbox.spaces[0].moduleOrder);
|
||||
});
|
||||
|
||||
test("toolbox export remaps space ids and preserves the active space", () => {
|
||||
const payload = createToolboxExportPayload({
|
||||
id: "toolbox-spaces",
|
||||
name: "Spaces",
|
||||
modules: [{ id: "module1", type: "notepad" }, { id: "module2", type: "links" }],
|
||||
spaces: [
|
||||
{ id: "default", name: "Défaut", moduleOrder: { one: ["module1"], two: [["module1"], []] } },
|
||||
{ id: "boss", name: "Boss", moduleOrder: { one: ["module2"], two: [[], ["module2"]] } }
|
||||
],
|
||||
defaultSpaceId: "default",
|
||||
activeSpaceId: "boss"
|
||||
}, {});
|
||||
assert.equal(payload.toolbox.spaces.length, 2);
|
||||
assert.notEqual(payload.toolbox.defaultSpaceId, "default");
|
||||
assert.notEqual(payload.toolbox.activeSpaceId, "boss");
|
||||
assert.equal(payload.toolbox.defaultSpaceId, payload.toolbox.spaces[0].id);
|
||||
assert.equal(payload.toolbox.activeSpaceId, payload.toolbox.spaces[1].id);
|
||||
assert.deepEqual(payload.toolbox.spaces.map((space) => space.moduleOrder.one), [["m1"], ["m2"]]);
|
||||
});
|
||||
|
||||
test("toolbox export remaps randomizer multi-target exclusions", () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue