test(studio): assert the superseded refresh's fetch actually aborts

Closes the coverage gap the independent review flagged: the abort
call had no test asserting on it, so a dropped abort() would have
stayed green. Also cuts two comment blocks over the repo's 4-line cap.
This commit is contained in:
Miguel Angel Simon Sierra
2026-09-18 08:17:25 -04:00
parent 8139a3fa37
commit d069763c6d
3 changed files with 63 additions and 9 deletions
@@ -258,12 +258,9 @@ export const VariablesPanel = memo(function VariablesPanel({
const { refreshKey } = useStudioPlaybackContext();
const { readProjectFile, writeProjectFile, fileTree, compositions } = useFileManagerContext();
const { domEditSelection } = useDomEditContext();
// On the master view (no activeCompPath) the panel targets the project's real
// main composition — the first composition in the tree — not a hardcoded
// index.html that may not exist. This same path is used for the persist write
// target (so an edit never lands in a phantom index.html) AND the handoff
// render command. Null only when the project has no composition yet, in which
// case sdkSession is also null and the panel is inert.
// Master view (no activeCompPath) targets the real main composition, not a
// hardcoded index.html — used for both the persist write target and the
// handoff render command. Null only when the project has no composition.
const effectiveCompPath = activeCompPath ?? resolveMasterCompositionPath(compositions);
const previewValues = usePreviewVariablesStore((s) => s.values);
const setPreviewValues = usePreviewVariablesStore((s) => s.setValues);
@@ -1,9 +1,8 @@
import { useEffect, useRef } from "react";
/**
* Opens the root composition once hydration settles on no selection; fires at most once per
* `projectId`. Relies on the caller resetting selection on an in-session project switch
* (see `useResetSelectionOnProjectSwitch`) or a stale selection reads as "already open".
* Opens the root composition once hydration settles with no selection, at most once per
* `projectId`. Caller must reset selection on a project switch (`useResetSelectionOnProjectSwitch`).
*/
export function useAutoOpenRootComposition({
projectId,
@@ -156,6 +156,64 @@ describe("useFileTree.refreshFileTree", () => {
fetchSpy.mockRestore();
});
it("aborts the superseded request's fetch when a newer refresh is issued", async () => {
let call = 0;
const signals: AbortSignal[] = [];
const fetchSpy = vi
.spyOn(globalThis, "fetch")
.mockImplementation(async (_url, init?: RequestInit) => {
call += 1;
if (call === 1) {
return new Response(
JSON.stringify({ files: ["index.html"], compositions: ["index.html"] }),
{ status: 200 },
);
}
if (init?.signal) signals.push(init.signal);
return new Promise<Response>(() => {});
});
const handleRef = { current: null as Handle | null };
const host = document.createElement("div");
document.body.append(host);
root = createRoot(host);
await act(async () => {
root?.render(
<Harness
ref={(h) => {
handleRef.current = h;
}}
projectId="project-a"
/>,
);
await Promise.resolve();
await Promise.resolve();
});
act(() => {
void handleRef.current?.refresh();
});
await act(async () => {
await Promise.resolve();
});
expect(signals).toHaveLength(1);
expect(signals[0].aborted).toBe(false);
act(() => {
void handleRef.current?.refresh();
});
await act(async () => {
await Promise.resolve();
});
expect(signals).toHaveLength(2);
expect(signals[0].aborted).toBe(true);
expect(signals[1].aborted).toBe(false);
fetchSpy.mockRestore();
});
it("keeps the prior compositions list when a refresh response omits the field", async () => {
let call = 0;
const fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(async () => {