mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-14 02:40:45 +08:00
fix(graph-ui): govern live graph refreshes
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
buildGraphStructureFingerprint,
|
||||
buildVisibleGraphRefreshToken,
|
||||
classifyGraphRefresh,
|
||||
createGraphRefreshGovernor,
|
||||
resolveVisibleGraphWorkspaceMode,
|
||||
} from "../ui/panel-graph-refresh-utils.js";
|
||||
|
||||
@@ -183,4 +186,225 @@ assert.notEqual(
|
||||
}),
|
||||
);
|
||||
|
||||
assert.equal(buildGraphStructureFingerprint(null), "empty");
|
||||
|
||||
const orderedFingerprint = buildGraphStructureFingerprint({
|
||||
nodes: [{ id: "b" }, { id: "a" }, { id: "c" }],
|
||||
edges: [
|
||||
{ from: "b", to: "c", relation: "supports" },
|
||||
{ fromId: "a", toId: "b", type: "causes" },
|
||||
],
|
||||
});
|
||||
|
||||
const reorderedFingerprint = buildGraphStructureFingerprint({
|
||||
edges: [
|
||||
{ toId: "b", fromId: "a", type: "causes" },
|
||||
{ to: "c", from: "b", relation: "supports" },
|
||||
],
|
||||
nodes: [{ id: "c" }, { id: "a" }, { id: "b" }],
|
||||
});
|
||||
|
||||
assert.equal(orderedFingerprint, reorderedFingerprint);
|
||||
assert.match(orderedFingerprint, /^nodes:3:[a-z0-9]+\|edges:2:[a-z0-9]+$/);
|
||||
|
||||
assert.equal(
|
||||
buildGraphStructureFingerprint({
|
||||
nodes: [{ id: "a" }, { id: "b" }],
|
||||
edges: [{ from: "a", to: "b", relation: "valid" }],
|
||||
}),
|
||||
buildGraphStructureFingerprint({
|
||||
nodes: [
|
||||
{ id: "a" },
|
||||
{ id: "b" },
|
||||
{ id: "archived", archived: true },
|
||||
{ id: "archivedAt", archivedAt: 123 },
|
||||
],
|
||||
edges: [
|
||||
{ from: "a", to: "b", relation: "valid" },
|
||||
{ from: "a", to: "b", relation: "invalid", invalidAt: 123 },
|
||||
{ from: "a", to: "b", relation: "expired", expiredAt: 123 },
|
||||
{ from: "a", to: "missing", relation: "missing-endpoint" },
|
||||
{ from: "archived", to: "a", relation: "archived-endpoint" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
const fingerprintA = buildGraphStructureFingerprint({
|
||||
nodes: [{ id: "a" }, { id: "b" }],
|
||||
edges: [{ from: "a", to: "b", relation: "valid" }],
|
||||
});
|
||||
const fingerprintB = buildGraphStructureFingerprint({
|
||||
nodes: [{ id: "a" }, { id: "b" }, { id: "c" }],
|
||||
edges: [{ from: "a", to: "b", relation: "valid" }],
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
classifyGraphRefresh({
|
||||
previousToken: "desktop:graph|chat|loaded|1|2|1|1",
|
||||
nextToken: "desktop:graph|chat|loaded|2|2|1|1",
|
||||
previousFingerprint: fingerprintA,
|
||||
nextFingerprint: fingerprintA,
|
||||
visibleMode: "desktop:graph",
|
||||
}),
|
||||
{
|
||||
action: "highlight-only",
|
||||
reason: "token-changed",
|
||||
tokenChanged: true,
|
||||
structureChanged: false,
|
||||
force: false,
|
||||
final: false,
|
||||
hard: false,
|
||||
},
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
classifyGraphRefresh({
|
||||
previousToken: "same",
|
||||
nextToken: "same",
|
||||
previousFingerprint: fingerprintA,
|
||||
nextFingerprint: fingerprintB,
|
||||
visibleMode: "desktop:graph",
|
||||
}).action,
|
||||
"refresh",
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
classifyGraphRefresh({
|
||||
previousToken: "same",
|
||||
nextToken: "same",
|
||||
previousFingerprint: fingerprintA,
|
||||
nextFingerprint: fingerprintA,
|
||||
visibleMode: "desktop:graph",
|
||||
}).action,
|
||||
"skip",
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
classifyGraphRefresh({
|
||||
previousToken: "same",
|
||||
nextToken: "same",
|
||||
previousFingerprint: fingerprintA,
|
||||
nextFingerprint: fingerprintB,
|
||||
hard: true,
|
||||
final: true,
|
||||
visibleMode: "desktop:graph",
|
||||
}).action,
|
||||
"hard-refresh",
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
classifyGraphRefresh({
|
||||
previousToken: "same",
|
||||
nextToken: "same",
|
||||
previousFingerprint: fingerprintA,
|
||||
nextFingerprint: fingerprintB,
|
||||
final: true,
|
||||
visibleMode: "desktop:graph",
|
||||
}).action,
|
||||
"final-refresh",
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
classifyGraphRefresh({
|
||||
previousToken: "same",
|
||||
nextToken: "hidden",
|
||||
previousFingerprint: fingerprintA,
|
||||
nextFingerprint: fingerprintB,
|
||||
hard: true,
|
||||
final: true,
|
||||
visibleMode: "hidden",
|
||||
}).action,
|
||||
"hidden",
|
||||
);
|
||||
|
||||
let currentTime = 1000;
|
||||
const governor = createGraphRefreshGovernor({
|
||||
liveThrottleMs: 240,
|
||||
extractionThrottleMs: 700,
|
||||
layoutRestartWindowMs: 5000,
|
||||
layoutRestartMax: 2,
|
||||
layoutCooldownMs: 9000,
|
||||
now: () => currentTime,
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
governor.noteRefresh({
|
||||
nextToken: "token-1",
|
||||
nextFingerprint: fingerprintA,
|
||||
force: true,
|
||||
isExtracting: true,
|
||||
visibleMode: "desktop:graph",
|
||||
}),
|
||||
{
|
||||
shouldRefresh: true,
|
||||
shouldLayout: true,
|
||||
action: "refresh",
|
||||
delayMs: 700,
|
||||
reason: "structure-changed",
|
||||
coalescedCount: 1,
|
||||
cooldownUntil: 0,
|
||||
},
|
||||
);
|
||||
|
||||
currentTime = 1100;
|
||||
assert.deepEqual(
|
||||
governor.noteRefresh({
|
||||
nextToken: "token-2",
|
||||
nextFingerprint: fingerprintA,
|
||||
isExtracting: false,
|
||||
visibleMode: "desktop:graph",
|
||||
}),
|
||||
{
|
||||
shouldRefresh: true,
|
||||
shouldLayout: false,
|
||||
action: "highlight-only",
|
||||
delayMs: 240,
|
||||
reason: "token-changed",
|
||||
coalescedCount: 2,
|
||||
cooldownUntil: 0,
|
||||
},
|
||||
);
|
||||
|
||||
const layoutGovernor = createGraphRefreshGovernor({
|
||||
layoutRestartWindowMs: 5000,
|
||||
layoutRestartMax: 2,
|
||||
layoutCooldownMs: 9000,
|
||||
now: () => currentTime,
|
||||
});
|
||||
|
||||
currentTime = 2000;
|
||||
assert.deepEqual(layoutGovernor.canStartLayout(), {
|
||||
allowed: true,
|
||||
reason: "allowed",
|
||||
cooldownUntil: 0,
|
||||
});
|
||||
|
||||
currentTime = 2500;
|
||||
assert.deepEqual(layoutGovernor.canStartLayout(), {
|
||||
allowed: true,
|
||||
reason: "allowed",
|
||||
cooldownUntil: 0,
|
||||
});
|
||||
|
||||
currentTime = 3000;
|
||||
assert.deepEqual(layoutGovernor.canStartLayout(), {
|
||||
allowed: false,
|
||||
reason: "budget-exhausted",
|
||||
cooldownUntil: 12000,
|
||||
});
|
||||
|
||||
currentTime = 4000;
|
||||
assert.deepEqual(layoutGovernor.canStartLayout(), {
|
||||
allowed: false,
|
||||
reason: "cooldown",
|
||||
cooldownUntil: 12000,
|
||||
});
|
||||
|
||||
currentTime = 12000;
|
||||
assert.deepEqual(layoutGovernor.canStartLayout(), {
|
||||
allowed: true,
|
||||
reason: "allowed",
|
||||
cooldownUntil: 12000,
|
||||
});
|
||||
|
||||
console.log("panel-graph-refresh tests passed");
|
||||
|
||||
Reference in New Issue
Block a user