Improve IndexedDB module rollback handling
All checks were successful
Deploy Sokko G / deploy (push) Successful in 16s

This commit is contained in:
Shinuwa 2026-08-25 08:55:11 +02:00
parent fe91d18624
commit 43a9e791c5
4 changed files with 162 additions and 19 deletions

View file

@ -0,0 +1,80 @@
// Rôle : teste les rollbacks optimistes par clé des données de modules IndexedDB.
import { test } from "node:test";
import assert from "node:assert/strict";
import {
applyModuleDataValue,
commitModuleDataCheckpoints,
createModuleDataCheckpoint,
createModuleDataCheckpoints,
removeModuleDataKeys,
rollbackModuleDataCheckpoints
} from "../website/src/features/toolboxes/storage/optimisticModuleData.js";
test("module data rollback restores a failed simple write", () => {
const initialData = { "tbx:mod-a": { text: "before" } };
const initialVersions = {};
const checkpoint = createModuleDataCheckpoint(initialData, initialVersions, "tbx:mod-a");
const versions = commitModuleDataCheckpoints(initialVersions, [checkpoint]);
const optimisticData = applyModuleDataValue(initialData, "tbx:mod-a", { text: "after" });
assert.deepEqual(rollbackModuleDataCheckpoints(optimisticData, versions, [checkpoint]), initialData);
});
test("module data rollback ignores a stale failure for the same module", () => {
const initialData = { "tbx:mod-a": { text: "before" } };
const firstCheckpoint = createModuleDataCheckpoint(initialData, {}, "tbx:mod-a");
const firstVersions = commitModuleDataCheckpoints({}, [firstCheckpoint]);
const firstOptimisticData = applyModuleDataValue(initialData, "tbx:mod-a", { text: "after" });
const secondCheckpoint = createModuleDataCheckpoint(firstOptimisticData, firstVersions, "tbx:mod-a");
const secondVersions = commitModuleDataCheckpoints(firstVersions, [secondCheckpoint]);
const secondOptimisticData = applyModuleDataValue(firstOptimisticData, "tbx:mod-a", { text: "latest" });
assert.strictEqual(rollbackModuleDataCheckpoints(secondOptimisticData, secondVersions, [firstCheckpoint]), secondOptimisticData);
});
test("module data rollback restores one module even when another module changed later", () => {
const initialData = {
"tbx:mod-a": { text: "before-a" },
"tbx:mod-b": { text: "before-b" }
};
const firstCheckpoint = createModuleDataCheckpoint(initialData, {}, "tbx:mod-a");
const firstVersions = commitModuleDataCheckpoints({}, [firstCheckpoint]);
const firstOptimisticData = applyModuleDataValue(initialData, "tbx:mod-a", { text: "after-a" });
const secondCheckpoint = createModuleDataCheckpoint(firstOptimisticData, firstVersions, "tbx:mod-b");
const secondVersions = commitModuleDataCheckpoints(firstVersions, [secondCheckpoint]);
const secondOptimisticData = applyModuleDataValue(firstOptimisticData, "tbx:mod-b", { text: "after-b" });
assert.deepEqual(rollbackModuleDataCheckpoints(secondOptimisticData, secondVersions, [firstCheckpoint]), {
"tbx:mod-a": { text: "before-a" },
"tbx:mod-b": { text: "after-b" }
});
});
test("module data rollback for batch removal restores only untouched keys", () => {
const initialData = {
"tbx:mod-a": { text: "before-a" },
"tbx:mod-b": { text: "before-b" },
"tbx:mod-c": { text: "before-c" }
};
const batchCheckpoints = createModuleDataCheckpoints(initialData, {}, ["tbx:mod-a", "tbx:mod-b"]);
const batchVersions = commitModuleDataCheckpoints({}, batchCheckpoints);
const removedData = removeModuleDataKeys(initialData, ["tbx:mod-a", "tbx:mod-b"]);
const laterCheckpoint = createModuleDataCheckpoint(removedData, batchVersions, "tbx:mod-b");
const laterVersions = commitModuleDataCheckpoints(batchVersions, [laterCheckpoint]);
const laterData = applyModuleDataValue(removedData, "tbx:mod-b", { text: "latest-b" });
assert.deepEqual(rollbackModuleDataCheckpoints(laterData, laterVersions, batchCheckpoints), {
"tbx:mod-a": { text: "before-a" },
"tbx:mod-b": { text: "latest-b" },
"tbx:mod-c": { text: "before-c" }
});
});
test("module data rollback keeps removal of an initially missing key stable", () => {
const initialData = { "tbx:mod-a": { text: "before-a" } };
const checkpoint = createModuleDataCheckpoint(initialData, {}, "tbx:missing");
const versions = commitModuleDataCheckpoints({}, [checkpoint]);
const optimisticData = removeModuleDataKeys(initialData, ["tbx:missing"]);
assert.deepEqual(rollbackModuleDataCheckpoints(optimisticData, versions, [checkpoint]), initialData);
});

View file

@ -23,6 +23,7 @@ test("toolbox storage, cards and pages are wired", async () => {
const calendarStorage = await readFile("website/src/features/toolboxes/storage/modules/calendar.js", "utf8");
const equipmentStorage = await readFile("website/src/features/toolboxes/storage/modules/equipmentPlanner.js", "utf8");
const indexedToolboxesHook = await readFile("website/src/features/toolboxes/storage/useIndexedToolboxes.js", "utf8");
const optimisticModuleData = await readFile("website/src/features/toolboxes/storage/optimisticModuleData.js", "utf8");
const storageQuota = await readFile("website/src/components/StorageQuota.jsx", "utf8");
const importButton = await readFile("website/src/components/ImportButton.jsx", "utf8");
const guidedTooltipTour = await readFile("website/src/components/GuidedTooltipTour.jsx", "utf8");
@ -36,6 +37,10 @@ test("toolbox storage, cards and pages are wired", async () => {
assert.match(source, /features\/toolboxes\/useToolboxActions\.js/);
assert.match(indexedToolboxesHook, /export function useIndexedToolboxes/);
assert.match(indexedToolboxesHook, /getStorageEstimate/);
assert.match(indexedToolboxesHook, /moduleDataByKey/);
assert.match(indexedToolboxesHook, /rollbackModuleDataCheckpoints/);
assert.match(optimisticModuleData, /export function createModuleDataCheckpoint/);
assert.match(optimisticModuleData, /export function rollbackModuleDataCheckpoints/);
assert.match(toolboxActions, /export function useToolboxActions/);
assert.match(toolboxActions, /importAllToolboxesPayload/);
assert.match(toolboxActions, /async function addImageFiles/);