fix(oracle-r4): validate selectedStepId across live refreshes
Oracle r4 'Watch Out For' caught a real edge case: if a backend graph changes shape while the scenarioId stays the same, the prior selectedStepId could survive the merge and point at a step that no longer exists. Two new vitest regressions in state/store.test.ts now pin the contract: - refreshLive() resets selectedStepId to the scenario's defaultStepId when the prior step no longer exists in the merged catalog - refreshLive() preserves a still-valid selectedStepId runLiveFetch() now derives stepStillThere from the merged scenario's own steps (not the old store) and falls back to defaultStepId when stale. Same single-set call as before; no extra renders. Confidence: high Scope-risk: narrow Not-tested: real backend definition with step IDs that disappear mid-session (covered by stub + assertion above)
This commit is contained in:
parent
2e0e4c08e8
commit
49639a0857
@ -74,4 +74,28 @@ describe("store live-mode + refresh", () => {
|
||||
await useApp.getState().setMode("snapshot");
|
||||
expect(useApp.getState().toasts.length).toBe(before);
|
||||
});
|
||||
|
||||
it("refreshLive() resets selectedStepId to defaultStepId when the prior step no longer exists in the merged catalog", async () => {
|
||||
stubFetch();
|
||||
await useApp.getState().setMode("live");
|
||||
const scenario = useApp.getState().scenarios[0];
|
||||
if (!scenario) throw new Error("no scenario");
|
||||
useApp.setState({ scenarioId: scenario.id, selectedStepId: "definitely-not-a-real-step-id" });
|
||||
await useApp.getState().refreshLive();
|
||||
const after = useApp.getState();
|
||||
const sc = after.scenarios.find((s) => s.id === after.scenarioId);
|
||||
expect(sc).toBeDefined();
|
||||
expect(sc!.steps.some((st) => st.id === after.selectedStepId)).toBe(true);
|
||||
});
|
||||
|
||||
it("refreshLive() preserves a still-valid selectedStepId", async () => {
|
||||
stubFetch();
|
||||
await useApp.getState().setMode("live");
|
||||
const scenario = useApp.getState().scenarios[0];
|
||||
if (!scenario) throw new Error("no scenario");
|
||||
const validStep = scenario.steps[scenario.steps.length - 1];
|
||||
useApp.setState({ scenarioId: scenario.id, selectedStepId: validStep.id });
|
||||
await useApp.getState().refreshLive();
|
||||
expect(useApp.getState().selectedStepId).toBe(validStep.id);
|
||||
});
|
||||
});
|
||||
|
||||
@ -84,14 +84,17 @@ async function runLiveFetch(
|
||||
const merged = [...scenarios, ...syntheticScenarios];
|
||||
const first = merged[0];
|
||||
const currentId = get().scenarioId;
|
||||
const currentStepId = get().selectedStepId;
|
||||
const stillThere = merged.find((s) => s.id === currentId);
|
||||
const stepStillThere = stillThere?.steps.some((st) => st.id === currentStepId);
|
||||
const nextScenario = stillThere ?? first;
|
||||
set({
|
||||
scenarios: merged,
|
||||
liveTotals: { workItems: workItems.length, distinctDefs },
|
||||
liveFetchedAt: Date.now(),
|
||||
liveLoading: false,
|
||||
scenarioId: stillThere ? currentId : first?.id ?? currentId,
|
||||
selectedStepId: stillThere ? get().selectedStepId : first?.defaultStepId ?? null,
|
||||
scenarioId: nextScenario?.id ?? currentId,
|
||||
selectedStepId: stepStillThere ? currentStepId : nextScenario?.defaultStepId ?? null,
|
||||
});
|
||||
get().pushToast(
|
||||
"ok",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user