mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-10-23 03:27:27 +09:00
fix: guard old publish polls and recovered failures
This commit is contained in:
1 parent
62f42bff9d
commit
51573cd848
2 files changed
+130
-12
No files matched your search
@@ -12,6 +12,7 @@ import { PublishValidationResults } from "./publish-validation-results";
|
||||
const {
|
||||
usePublishProgress,
|
||||
waitForPublishStatus,
|
||||
waitForOwnedPublishStatus,
|
||||
refreshUntilSuccess,
|
||||
useManualValidationFindings,
|
||||
getRefreshedProject,
|
||||
@@ -357,6 +358,45 @@ test("publish polling stops after navigating to another project", async () => {
|
||||
expect($project.get()).toBe(newProject);
|
||||
});
|
||||
|
||||
test("an old poll cannot update a newly mounted copy of the same project", async () => {
|
||||
const firstProject = { id: "project" } as Project;
|
||||
$project.set(firstProject);
|
||||
let progress: ReturnType<typeof usePublishProgress> | undefined;
|
||||
const Harness = () => {
|
||||
progress = usePublishProgress("project");
|
||||
return null;
|
||||
};
|
||||
container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
act(() => root?.render(<Harness />));
|
||||
|
||||
let finishQuery: (result: {
|
||||
success: true;
|
||||
project: Project;
|
||||
}) => void = () => {};
|
||||
const query = new Promise<{ success: true; project: Project }>((resolve) => {
|
||||
finishQuery = resolve;
|
||||
});
|
||||
const polling = waitForOwnedPublishStatus({
|
||||
projectId: "project",
|
||||
isCurrentProject: progress!.isCurrentProject,
|
||||
query: () => query,
|
||||
});
|
||||
|
||||
act(() => root?.unmount());
|
||||
$project.set({ id: "another-project" } as Project);
|
||||
const reopenedProject = { id: "project", domain: "new" } as Project;
|
||||
$project.set(reopenedProject);
|
||||
finishQuery({
|
||||
success: true,
|
||||
project: { id: "project", domain: "old" } as Project,
|
||||
});
|
||||
|
||||
await expect(polling).resolves.toBeUndefined();
|
||||
expect($project.get()).toBe(reopenedProject);
|
||||
});
|
||||
|
||||
test("publish completion does not show a toast while the Publish panel is open", () => {
|
||||
const successToast = vi.spyOn(toast, "success").mockImplementation(() => "");
|
||||
const errorToast = vi.spyOn(toast, "error").mockImplementation(() => "");
|
||||
@@ -790,6 +830,59 @@ test("a newer build clears a recovered transport error", async () => {
|
||||
expect(container.querySelector("output")?.textContent).toBe("");
|
||||
});
|
||||
|
||||
test("a recovered failed build replaces a lost response error", async () => {
|
||||
$project.set({
|
||||
id: "project",
|
||||
latestBuildVirtual: { buildId: "old" },
|
||||
} as Project);
|
||||
$publishDialog.set("publish");
|
||||
|
||||
const Harness = () => {
|
||||
const progress = usePublishProgress("project");
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() =>
|
||||
progress.start(() =>
|
||||
Promise.reject(new Error("Network unavailable"))
|
||||
)
|
||||
}
|
||||
>
|
||||
Publish
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
progress.reconcileRefreshedProject({
|
||||
latestBuildVirtual: {
|
||||
buildId: "new",
|
||||
createdAt: new Date().toISOString(),
|
||||
publishStatus: "FAILED",
|
||||
},
|
||||
} as Project)
|
||||
}
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
<output>{progress.publishError ?? progress.requestError}</output>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
act(() => root?.render(<Harness />));
|
||||
const [publish, refresh] = container.querySelectorAll("button");
|
||||
await act(async () => publish.click());
|
||||
expect(container.querySelector("output")?.textContent).toBe(
|
||||
"Network unavailable"
|
||||
);
|
||||
act(() => refresh.click());
|
||||
expect(container.querySelector("output")?.textContent).toContain(
|
||||
"Publish failed"
|
||||
);
|
||||
});
|
||||
|
||||
test("publish diagnostics remain visible after the panel remounts", () => {
|
||||
$project.set({ id: "project" } as Project);
|
||||
|
||||
|
||||
@@ -590,6 +590,7 @@ const usePublishProgress = (projectId: Project["id"]) => {
|
||||
const [now, setNow] = useState(Date.now);
|
||||
const isMounted = useRef(false);
|
||||
const requestBaselineBuildId = useRef<string>();
|
||||
const hasStartedPublishRequest = useRef(false);
|
||||
const refreshGeneration = useRef(0);
|
||||
const recoveredPoll = useRef<{
|
||||
generation: number;
|
||||
@@ -648,17 +649,24 @@ const usePublishProgress = (projectId: Project["id"]) => {
|
||||
}, []);
|
||||
|
||||
const reconcileRefreshedProject = useCallback((project: Project) => {
|
||||
if (
|
||||
project.latestBuildVirtual != null &&
|
||||
getPublishStatusAndText(project.latestBuildVirtual).status !== "FAILED"
|
||||
) {
|
||||
const build = project.latestBuildVirtual;
|
||||
if (build == null) {
|
||||
return;
|
||||
}
|
||||
const { status, statusText } = getPublishStatusAndText(build);
|
||||
const isNewBuild =
|
||||
build.buildId !== undefined &&
|
||||
build.buildId !== requestBaselineBuildId.current;
|
||||
if (status === "FAILED") {
|
||||
if (hasStartedPublishRequest.current && isNewBuild) {
|
||||
setRequestError(undefined);
|
||||
setPublishErrorState({ value: statusText, source: "status" });
|
||||
}
|
||||
} else {
|
||||
setPublishErrorState((current) =>
|
||||
current?.source === "status" ? undefined : current
|
||||
);
|
||||
if (
|
||||
project.latestBuildVirtual.buildId !== undefined &&
|
||||
project.latestBuildVirtual.buildId !== requestBaselineBuildId.current
|
||||
) {
|
||||
if (isNewBuild) {
|
||||
setRequestError(undefined);
|
||||
}
|
||||
}
|
||||
@@ -713,6 +721,7 @@ const usePublishProgress = (projectId: Project["id"]) => {
|
||||
|
||||
const start = (publish: () => Promise<void>) => {
|
||||
const startedAt = Date.now();
|
||||
hasStartedPublishRequest.current = true;
|
||||
requestBaselineBuildId.current =
|
||||
$project.get()?.latestBuildVirtual?.buildId;
|
||||
setStartedAt(startedAt);
|
||||
@@ -796,6 +805,21 @@ const waitForPublishStatus = async (
|
||||
}
|
||||
};
|
||||
|
||||
const waitForOwnedPublishStatus = ({
|
||||
projectId,
|
||||
isCurrentProject,
|
||||
query,
|
||||
}: {
|
||||
projectId: Project["id"];
|
||||
isCurrentProject: () => boolean;
|
||||
query?: () => ReturnType<typeof nativeClient.domain.project.query>;
|
||||
}) =>
|
||||
waitForPublishStatus(
|
||||
() =>
|
||||
getRefreshedProject({ projectId, isCurrent: isCurrentProject, query }),
|
||||
isCurrentProject
|
||||
);
|
||||
|
||||
const refreshUntilSuccess = async (
|
||||
refresh: (showError: boolean) => Promise<Project | undefined>,
|
||||
isOpen: () => boolean,
|
||||
@@ -1100,10 +1124,10 @@ const Publish = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await waitForPublishStatus(
|
||||
() => getRefreshedProject({ projectId: project.id }),
|
||||
() => $project.get()?.id === project.id
|
||||
);
|
||||
const result = await waitForOwnedPublishStatus({
|
||||
projectId: project.id,
|
||||
isCurrentProject,
|
||||
});
|
||||
reportPublishResult({
|
||||
result,
|
||||
timesLeft,
|
||||
@@ -2111,6 +2135,7 @@ export const __testing__ = {
|
||||
useManualValidationFindings,
|
||||
restoreValidatedDomains,
|
||||
waitForPublishStatus,
|
||||
waitForOwnedPublishStatus,
|
||||
refreshUntilSuccess,
|
||||
getRefreshedProject,
|
||||
reportPublishResult,
|
||||
|
||||
Reference in new issue
Block a user