mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 23:00:03 +00:00
fix(studio): drain pending edits before reload (#2989)
* fix(studio): drain pending edits before reload * fix(studio): address drain review feedback (#2989) - prioritize conflicts and clear recovered DOM queue errors - cover delayed blur effects and missing drain branches - document stacked consumers and extend write-token retention * test(studio): satisfy drain audit gate (#2989) - share the editor-save hook harness across drain regressions - extract settled failure inspection from the drain loop
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
flushStudioPendingEdits,
|
||||
trackStudioPendingEdit,
|
||||
} from "./studioPendingEdits";
|
||||
import { StudioFileConflictError } from "./studioSaveDiagnostics";
|
||||
|
||||
describe("studio pending edit flush", () => {
|
||||
it("waits for mounted panels to persist pending local edits", async () => {
|
||||
@@ -12,13 +13,119 @@ describe("studio pending edit flush", () => {
|
||||
const remove = addStudioPendingEditFlushListener(persist);
|
||||
|
||||
try {
|
||||
await flushStudioPendingEdits();
|
||||
await expect(flushStudioPendingEdits()).resolves.toEqual({ status: "clean" });
|
||||
expect(persist).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
remove();
|
||||
}
|
||||
});
|
||||
|
||||
it("commits the focused debounced field before draining pending work", async () => {
|
||||
const input = document.createElement("textarea");
|
||||
document.body.append(input);
|
||||
const persist = vi.fn(async () => undefined);
|
||||
input.addEventListener("blur", () => {
|
||||
trackStudioPendingEdit(persist());
|
||||
});
|
||||
input.focus();
|
||||
|
||||
await expect(flushStudioPendingEdits()).resolves.toEqual({ status: "clean" });
|
||||
|
||||
expect(document.activeElement).not.toBe(input);
|
||||
expect(persist).toHaveBeenCalledOnce();
|
||||
input.remove();
|
||||
});
|
||||
|
||||
it("waits for a post-blur effect to register its pending edit listener", async () => {
|
||||
const input = document.createElement("textarea");
|
||||
document.body.append(input);
|
||||
const persist = vi.fn(async () => undefined);
|
||||
let removeListener: (() => void) | undefined;
|
||||
let registrationDone: Promise<void> | undefined;
|
||||
input.addEventListener("blur", () => {
|
||||
registrationDone = new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
removeListener = addStudioPendingEditFlushListener(persist);
|
||||
resolve();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
input.focus();
|
||||
|
||||
try {
|
||||
await expect(flushStudioPendingEdits()).resolves.toEqual({ status: "clean" });
|
||||
|
||||
expect(persist).toHaveBeenCalledOnce();
|
||||
} finally {
|
||||
await registrationDone;
|
||||
removeListener?.();
|
||||
input.remove();
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves a pending edit failure instead of reporting a clean drain", async () => {
|
||||
const failure = new Error("field save failed");
|
||||
const remove = addStudioPendingEditFlushListener(async () => {
|
||||
throw failure;
|
||||
});
|
||||
|
||||
try {
|
||||
await expect(flushStudioPendingEdits()).resolves.toEqual({
|
||||
status: "failed",
|
||||
error: failure,
|
||||
});
|
||||
} finally {
|
||||
remove();
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps the full typed conflict payload for the external-change decision", async () => {
|
||||
const conflict = new StudioFileConflictError({
|
||||
filePath: "index.html",
|
||||
currentVersion: "v2",
|
||||
currentContent: "external",
|
||||
attemptedContent: "studio",
|
||||
});
|
||||
const remove = addStudioPendingEditFlushListener(async () => {
|
||||
throw conflict;
|
||||
});
|
||||
|
||||
try {
|
||||
await expect(flushStudioPendingEdits()).resolves.toEqual({
|
||||
status: "conflict",
|
||||
error: conflict,
|
||||
});
|
||||
} finally {
|
||||
remove();
|
||||
}
|
||||
});
|
||||
|
||||
it("prioritizes a conflict when pending edits fail with mixed errors", async () => {
|
||||
const failure = new Error("field save failed");
|
||||
const conflict = new StudioFileConflictError({
|
||||
filePath: "index.html",
|
||||
currentVersion: "v2",
|
||||
currentContent: "external",
|
||||
attemptedContent: "studio",
|
||||
});
|
||||
const removeFailure = addStudioPendingEditFlushListener(async () => {
|
||||
throw failure;
|
||||
});
|
||||
const removeConflict = addStudioPendingEditFlushListener(async () => {
|
||||
throw conflict;
|
||||
});
|
||||
|
||||
try {
|
||||
await expect(flushStudioPendingEdits()).resolves.toEqual({
|
||||
status: "conflict",
|
||||
error: conflict,
|
||||
});
|
||||
} finally {
|
||||
removeFailure();
|
||||
removeConflict();
|
||||
}
|
||||
});
|
||||
|
||||
it("waits for edits already started by unmounted panels", async () => {
|
||||
const steps: string[] = [];
|
||||
let resolvePersist!: () => void;
|
||||
|
||||
Reference in New Issue
Block a user