Fix recursive table evaluation caching
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s
This commit is contained in:
parent
34524d2961
commit
7521d50626
3 changed files with 66 additions and 7 deletions
|
|
@ -22,3 +22,4 @@
|
|||
{"id":"int-03ed1c823da90551f51a5a0d5691e440","kind":"field_change","created_at":"2026-08-28T12:35:29.257332022Z","actor":"Shinuwa","issue_id":"sokko-g-39h","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Applied NDS event-surface to calendar week and month events, documented data-color/data-size, and validated checks."}}
|
||||
{"id":"int-88c39f90fdd5b81110acc4bdae55836c","kind":"field_change","created_at":"2026-09-10T11:49:36.768770213Z","actor":"Shinuwa","issue_id":"sokko-g-vfo","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Shared lazy React registry consumed by toolbox and library; obsolete static import assertions removed; all 17 library types exercised; npm run check and 13 E2E tests pass."}}
|
||||
{"id":"int-7ec422ecc24735e12910a965567f32fe","kind":"field_change","created_at":"2026-09-10T12:17:38.573656548Z","actor":"Shinuwa","issue_id":"sokko-g-531","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Concurrent image batches merge current data after compression; calendar selection handlers update drafts outside state updaters. Behavioral image test and reverse week/month selection E2E pass; npm run check passes."}}
|
||||
{"id":"int-1668982a1dda5d8304440ae699a32d40","kind":"field_change","created_at":"2026-09-10T12:22:11.969660039Z","actor":"Shinuwa","issue_id":"sokko-g-5u5","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Recursive cache enabled for completed evaluations. Before: 131054 reads for 16 cells; after: 50 reads for 50 cells. Regression test failed before fix and passes after; npm run check and table persistence E2E pass."}}
|
||||
|
|
|
|||
53
tests/table-evaluation.test.mjs
Normal file
53
tests/table-evaluation.test.mjs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// Rôle : vérifie le coût des références répétées et les erreurs du tableau.
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { evaluateTable, evaluateTableCell } from "../website/src/features/toolboxes/modules/tableFormulaEngine.js";
|
||||
|
||||
for (const reverse of [false, true]) {
|
||||
test(`table reuses repeated references with ${reverse ? "forward" : "backward"} dependencies`, () => {
|
||||
const count = 50;
|
||||
const raw = { [reverse ? "A50" : "A1"]: "1" };
|
||||
for (let i = 1; i < count; i++) {
|
||||
const row = reverse ? count - i : i + 1;
|
||||
const dependency = reverse ? row + 1 : row - 1;
|
||||
raw[`A${row}`] = `=A${dependency}+A${dependency}`;
|
||||
}
|
||||
let reads = 0;
|
||||
const cells = new Proxy(raw, {
|
||||
get(target, address) {
|
||||
// Stop an exponential regression before it can hang the test runner.
|
||||
if (++reads > count * 4) throw new Error("Read budget exceeded");
|
||||
return target[address];
|
||||
}
|
||||
});
|
||||
const dimensions = { rows: count, columns: 1 };
|
||||
const last = reverse ? "A1" : "A50";
|
||||
assert.equal(evaluateTableCell(last, cells, dimensions).value, 2 ** 49);
|
||||
assert.ok(reads <= count * 4);
|
||||
reads = 0;
|
||||
assert.equal(evaluateTable(cells, dimensions)[last].value, 2 ** 49);
|
||||
assert.ok(reads <= count * 4);
|
||||
raw[reverse ? "A50" : "A1"] = "2";
|
||||
reads = 0;
|
||||
assert.equal(evaluateTable(cells, dimensions)[last].value, 2 ** 50);
|
||||
});
|
||||
}
|
||||
|
||||
test("shared evaluation preserves cycle, division and invalid reference errors", () => {
|
||||
const cells = {
|
||||
A1: "=B1", B1: "=A1", C1: "=A1+1",
|
||||
A2: "=1/0", B2: "=A2", C2: "=Z99",
|
||||
A3: "text", B3: "=A3", C3: "=7+5"
|
||||
};
|
||||
const dimensions = { rows: 3, columns: 3 };
|
||||
const result = evaluateTable(cells, dimensions);
|
||||
assert.deepEqual(Object.fromEntries(Object.entries(result).map(([key, value]) => [key, value.error])), {
|
||||
A1: "cycle", B1: "cycle", C1: "cycle",
|
||||
A2: "division", B2: "reference", C2: "reference",
|
||||
A3: "", B3: "reference", C3: ""
|
||||
});
|
||||
for (const address of Object.keys(cells)) {
|
||||
assert.deepEqual(result[address], evaluateTableCell(address, cells, dimensions));
|
||||
}
|
||||
assert.equal(result.C3.value, 12);
|
||||
});
|
||||
|
|
@ -166,17 +166,22 @@ export function evaluateTableCell(address, cells, dimensions, stack = []) {
|
|||
}
|
||||
|
||||
function evaluateTableCellWithCache(address, cells, dimensions, stack, cache) {
|
||||
if (!stack.length && cache.has(address)) return cache.get(address);
|
||||
const raw = String(cells?.[address] || "").trim();
|
||||
if (!raw) return { value: null, display: "", error: "empty" };
|
||||
|
||||
if (stack.includes(address)) return { value: null, display: "#CYCLE", error: "cycle" };
|
||||
if (cache.has(address)) return cache.get(address);
|
||||
const raw = String(cells?.[address] || "").trim();
|
||||
if (!raw) {
|
||||
const evaluated = { value: null, display: "", error: "empty" };
|
||||
cache.set(address, evaluated);
|
||||
return evaluated;
|
||||
}
|
||||
|
||||
if (!raw.startsWith("=")) {
|
||||
const value = parsePlainNumber(raw);
|
||||
return value == null
|
||||
const evaluated = value == null
|
||||
? { value: null, display: raw, error: "" }
|
||||
: { value, display: formatNumber(value), error: "" };
|
||||
cache.set(address, evaluated);
|
||||
return evaluated;
|
||||
}
|
||||
|
||||
const result = evaluateFormula(raw.slice(1), (reference) => {
|
||||
|
|
@ -189,12 +194,12 @@ function evaluateTableCellWithCache(address, cells, dimensions, stack, cache) {
|
|||
if (result.error) {
|
||||
const label = result.error === "cycle" ? "#CYCLE" : result.error === "division" ? "#DIV/0" : "#ERREUR";
|
||||
const evaluated = { value: null, display: label, error: result.error };
|
||||
if (!stack.length) cache.set(address, evaluated);
|
||||
cache.set(address, evaluated);
|
||||
return evaluated;
|
||||
}
|
||||
|
||||
const evaluated = { value: result.value, display: formatNumber(result.value), error: "" };
|
||||
if (!stack.length) cache.set(address, evaluated);
|
||||
cache.set(address, evaluated);
|
||||
return evaluated;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue