test: speed up more control UI async polling (#110530)

This commit is contained in:
Peter Steinberger
2026-07-18 08:37:57 +01:00
committed by GitHub
parent 7c1c4960c1
commit ff54749b1b
10 changed files with 117 additions and 97 deletions
+14 -13
View File
@@ -1,5 +1,6 @@
// Control UI tests cover operator question parsing and lifecycle state.
import { afterEach, describe, expect, it, vi } from "vitest";
import { waitForFast } from "../test-helpers/wait-for.ts";
import {
cancelQuestionPrompt,
createQuestionPromptState,
@@ -367,7 +368,7 @@ describe("refreshPendingQuestions", () => {
setQuestionPromptClient(state, client);
refreshPendingQuestionsWithRetry(state, client);
await vi.waitFor(() => expect(state.prompts.get("question-1")?.status).toBe("pending"));
await waitForFast(() => expect(state.prompts.get("question-1")?.status).toBe("pending"));
expect(request).toHaveBeenCalledWith("question.list", {});
expect(state.prompts.get("question-1")?.status).toBe("pending");
@@ -423,7 +424,7 @@ describe("refreshPendingQuestions", () => {
},
});
finishList({ questions: [requestedPayload()] });
await vi.waitFor(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
await waitForFast(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
expect(state.prompts.get("question-1")?.status).toBe("answered");
});
@@ -456,7 +457,7 @@ describe("refreshPendingQuestions", () => {
},
});
finishList({ questions: [] });
await vi.waitFor(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
await waitForFast(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
expect(request).toHaveBeenCalledWith("question.get", { id: "question-1" });
expect(state.prompts.get("question-1")).toMatchObject({
@@ -488,7 +489,7 @@ describe("refreshPendingQuestions", () => {
});
refreshPendingQuestionsWithRetry(state, client);
await vi.waitFor(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
await waitForFast(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
expect(request).toHaveBeenCalledWith("question.get", { id: "question-1" });
expect(state.prompts.get("question-1")).toMatchObject({
@@ -524,11 +525,11 @@ describe("refreshPendingQuestions", () => {
});
refreshPendingQuestionsWithRetry(state, client);
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(2));
await waitForFast(() => expect(request).toHaveBeenCalledTimes(2));
expect(state.prompts.get("question-1")?.status).toBe("pending");
refreshPendingQuestionsWithRetry(state, client);
await vi.waitFor(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
await waitForFast(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
expect(state.prompts.get("question-1")).toMatchObject({
status: "answered",
answeredElsewhere: true,
@@ -552,7 +553,7 @@ describe("refreshPendingQuestions", () => {
});
refreshPendingQuestionsWithRetry(state, client);
await vi.waitFor(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
await waitForFast(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
expect(state.prompts.get("question-1")).toMatchObject({
status: "answered",
@@ -590,7 +591,7 @@ describe("refreshPendingQuestions", () => {
});
refreshPendingQuestionsWithRetry(state, client);
await vi.waitFor(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
await waitForFast(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
expect(state.prompts.get("question-1")).toMatchObject({
status: "answered",
@@ -621,7 +622,7 @@ describe("refreshPendingQuestions", () => {
});
refreshPendingQuestionsWithRetry(state, client);
await vi.waitFor(() =>
await waitForFast(() =>
expect(request).toHaveBeenCalledWith("question.get", { id: "question-1" }),
);
await vi.advanceTimersByTimeAsync(1_000);
@@ -633,7 +634,7 @@ describe("refreshPendingQuestions", () => {
}),
});
await vi.waitFor(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
await waitForFast(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
expect(state.prompts.get("question-1")).toMatchObject({
status: "answered",
locallyExpired: false,
@@ -670,7 +671,7 @@ describe("refreshPendingQuestions", () => {
await vi.advanceTimersByTimeAsync(1_000);
finishList({ questions: [] });
await vi.waitFor(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
await waitForFast(() => expect(state.prompts.get("question-1")?.status).toBe("answered"));
expect(request).toHaveBeenCalledWith("question.get", { id: "question-1" });
expect(state.prompts.get("question-1")).toMatchObject({
status: "answered",
@@ -702,7 +703,7 @@ describe("refreshPendingQuestions", () => {
onChange.mockClear();
refreshPendingQuestionsWithRetry(state, client);
await vi.waitFor(() =>
await waitForFast(() =>
expect(request).toHaveBeenCalledWith("question.get", { id: "question-1" }),
);
@@ -710,6 +711,6 @@ describe("refreshPendingQuestions", () => {
expect(onChange).toHaveBeenCalled();
finishGet({ question: requestedPayload({ status: "cancelled" }) });
await vi.waitFor(() => expect(state.prompts.get("question-1")?.status).toBe("cancelled"));
await waitForFast(() => expect(state.prompts.get("question-1")?.status).toBe("cancelled"));
});
});
+9 -8
View File
@@ -3,6 +3,7 @@
import { expect, it, vi } from "vitest";
import type { AgentIdentityResult, GatewayAgentRow } from "../api/types.ts";
import { i18n, t } from "../i18n/index.ts";
import { waitForFast } from "../test-helpers/wait-for.ts";
import { AgentSelect } from "./agent-select.ts";
const AGENT_SELECT_TEST_TAG = `test-openclaw-agent-select-${crypto.randomUUID()}`;
@@ -117,7 +118,7 @@ it("fetches local avatars with the bearer credential when token auth is active",
signal: expect.any(AbortSignal),
});
await vi.waitFor(() => {
await waitForFast(() => {
expect(
element.querySelector<HTMLImageElement>("img.agent-select__avatar")?.getAttribute("src"),
).toBe("blob:agent-avatar");
@@ -152,13 +153,13 @@ it("refetches a failed local avatar after the auth credential rotates", async ()
});
try {
await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1));
await waitForFast(() => expect(fetchMock).toHaveBeenCalledTimes(1));
expect(element.querySelector("img.agent-select__avatar")).toBeNull();
element.authToken = "tok2";
await element.updateComplete;
await vi.waitFor(() => {
await waitForFast(() => {
expect(fetchMock).toHaveBeenLastCalledWith("/avatar/alpha", {
headers: { Authorization: "Bearer tok2" },
signal: expect.any(AbortSignal),
@@ -208,7 +209,7 @@ it("aborts the stale request on auth rotation without duplicating the current fe
expect(fetchMock).toHaveBeenCalledTimes(1);
element.authToken = "tok2";
await element.updateComplete;
await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(2));
await waitForFast(() => expect(fetchMock).toHaveBeenCalledTimes(2));
expect(pending[0]?.signal.aborted).toBe(true);
expect(
@@ -226,7 +227,7 @@ it("aborts the stale request on auth rotation without duplicating the current fe
ok: true,
blob: async () => new Blob(["avatar"]),
} as Response);
await vi.waitFor(() => {
await waitForFast(() => {
expect(
element.querySelector<HTMLImageElement>("img.agent-select__avatar")?.getAttribute("src"),
).toBe("blob:rotated-avatar");
@@ -272,7 +273,7 @@ it("aborts a stalled local avatar fetch after the request deadline", async () =>
await vi.advanceTimersByTimeAsync(1);
expect(fetchInit?.signal?.aborted).toBe(true);
await vi.waitFor(() => {
await waitForFast(() => {
expect(element.querySelector("img.agent-select__avatar")).toBeNull();
expect(element.querySelector(".agent-select__avatar--text")?.textContent?.trim()).toBe("A");
});
@@ -311,12 +312,12 @@ it("aborts a stalled local avatar body after the request deadline", async () =>
try {
expect(fetchMock).toHaveBeenCalledTimes(1);
await vi.waitFor(() => expect(blob).toHaveBeenCalledTimes(1));
await waitForFast(() => expect(blob).toHaveBeenCalledTimes(1));
const [, fetchInit] = fetchMock.mock.calls[0] ?? [];
await vi.advanceTimersByTimeAsync(30_000);
expect(fetchInit?.signal?.aborted).toBe(true);
await vi.waitFor(() => {
await waitForFast(() => {
expect(element.querySelector("img.agent-select__avatar")).toBeNull();
expect(element.querySelector(".agent-select__avatar--text")?.textContent?.trim()).toBe("A");
});
@@ -3,6 +3,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { i18n } from "../../i18n/index.ts";
import { createStorageMock } from "../../test-helpers/storage.ts";
import { waitForFast } from "../../test-helpers/wait-for.ts";
import type { TerminalGatewayClient } from "./terminal-connection.ts";
import { OpenClawTerminalPanel } from "./terminal-panel.ts";
import type { createIsolatedGhosttyTerminal } from "./terminal-runtime.ts";
@@ -110,7 +111,7 @@ describe("terminal panel readiness", () => {
}),
);
await vi.waitFor(() => {
await waitForFast(() => {
expect(requests).toContainEqual({
method: "terminal.attach",
params: { sessionId: "agent-terminal-1" },
@@ -139,7 +140,7 @@ describe("terminal panel readiness", () => {
document.body.append(panel);
panel.toggle();
await vi.waitFor(() => {
await waitForFast(() => {
expect(panel.renderRoot.querySelector(".tp-connecting")?.textContent).toContain(
"Connecting to session",
);
@@ -149,7 +150,7 @@ describe("terminal panel readiness", () => {
});
open.resolve(terminalOpenResult("session-1"));
await vi.waitFor(() => {
await waitForFast(() => {
expect(panel.renderRoot.querySelector(".tp-connecting")).toBeNull();
expect(panel.renderRoot.querySelector(".tabstrip-tab")?.classList.contains("is-live")).toBe(
true,
@@ -186,7 +187,7 @@ describe("terminal panel readiness", () => {
panel.handleToggleRequest(new CustomEvent("openclaw:terminal-toggle", { detail: { catalog } }));
await vi.waitFor(() => {
await waitForFast(() => {
expect(requests).toContainEqual({
method: "terminal.open",
params: { agentId: undefined, cols: 100, rows: 30, catalog },
@@ -203,7 +204,7 @@ describe("terminal panel readiness", () => {
event: "terminal.data",
payload: { sessionId: "catalog-terminal-1", seq: 5, data: "ready" },
});
await vi.waitFor(() => expect(panel.renderRoot.querySelector(".tp-connecting")).toBeNull());
await waitForFast(() => expect(panel.renderRoot.querySelector(".tp-connecting")).toBeNull());
expect(new TextDecoder().decode(controller.write.mock.calls[0]?.[0])).toBe("ready");
expect(sessionStorage.getItem("openclaw.terminal.sessions.v1")).toBe(
JSON.stringify(["catalog-terminal-1"]),
@@ -248,14 +249,16 @@ describe("terminal panel readiness", () => {
detail: { catalog: { catalogId: "anthropic", hostId: "node:mac", threadId: "thread" } },
}),
);
await vi.waitFor(() => expect(panel.renderRoot.querySelector(".tp-connecting")).not.toBeNull());
await waitForFast(() =>
expect(panel.renderRoot.querySelector(".tp-connecting")).not.toBeNull(),
);
listener?.({
event: "terminal.data",
payload: { sessionId: "catalog-terminal-1", seq: 12, data: "gap" },
});
await vi.waitFor(() => expect(panel.renderRoot.querySelector(".tp-connecting")).toBeNull());
await waitForFast(() => expect(panel.renderRoot.querySelector(".tp-connecting")).toBeNull());
expect(requests).toContainEqual({
method: "terminal.attach",
params: { sessionId: "catalog-terminal-1" },
@@ -290,7 +293,7 @@ describe("terminal panel readiness", () => {
}),
);
await vi.waitFor(() => {
await waitForFast(() => {
expect(panel.renderRoot.querySelector(".tp-error")?.textContent).toContain(
"Session did not connect within 30 seconds",
);
@@ -3,6 +3,7 @@
import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from "vitest";
import { i18n } from "../../i18n/index.ts";
import { createStorageMock } from "../../test-helpers/storage.ts";
import { waitForFast } from "../../test-helpers/wait-for.ts";
import type { TerminalGatewayClient } from "./terminal-connection.ts";
import { OpenClawTerminalPanel } from "./terminal-panel.ts";
@@ -113,7 +114,7 @@ describe("OpenClawTerminalPanel upload lifecycle", () => {
panel.available = true;
document.body.append(panel);
panel.toggle();
await vi.waitFor(() => {
await waitForFast(() => {
expect(panel.renderRoot.querySelector<HTMLButtonElement>(".tp-upload")?.disabled).toBe(false);
});
expect(panel.renderRoot.querySelector<HTMLInputElement>(".tp-file-input")?.multiple).toBe(true);
@@ -128,7 +129,7 @@ describe("OpenClawTerminalPanel upload lifecycle", () => {
});
panel.renderRoot.querySelector(".tp-viewport")?.dispatchEvent(drop);
await vi.waitFor(() => {
await waitForFast(() => {
expect(requests).toContainEqual({
method: "terminal.upload",
params: {
@@ -175,7 +176,7 @@ describe("OpenClawTerminalPanel upload lifecycle", () => {
panel.available = true;
document.body.append(panel);
panel.toggle();
await vi.waitFor(() => {
await waitForFast(() => {
expect(panel.renderRoot.querySelector<HTMLButtonElement>(".tp-upload")?.disabled).toBe(false);
});
@@ -192,7 +193,7 @@ describe("OpenClawTerminalPanel upload lifecycle", () => {
});
panel.renderRoot.querySelector(".tp-viewport")?.dispatchEvent(drop);
await vi.waitFor(() => {
await waitForFast(() => {
const progress = panel.renderRoot.querySelector(".tp-upload-progress");
expect(progress?.getAttribute("aria-valuenow")).toBe("1");
expect(progress?.getAttribute("aria-valuemax")).toBe("2");
@@ -209,7 +210,7 @@ describe("OpenClawTerminalPanel upload lifecycle", () => {
retryable: false,
}),
);
await vi.waitFor(() => {
await waitForFast(() => {
const failed = panel.renderRoot.querySelector(".tp-upload-card--failed");
expect(failed?.textContent).toContain("Upload failed");
expect(failed?.textContent).toContain("paired node went offline");
@@ -217,7 +218,7 @@ describe("OpenClawTerminalPanel upload lifecycle", () => {
});
panel.renderRoot.querySelector<HTMLButtonElement>(".tp-upload-retry")?.click();
await vi.waitFor(() => {
await waitForFast(() => {
expect(controller.terminal.paste).toHaveBeenCalledWith(
"'/tmp/openclaw upload/scan final.pdf' '/tmp/openclaw upload/notes.txt'",
);
@@ -254,7 +255,7 @@ describe("OpenClawTerminalPanel upload lifecycle", () => {
panel.available = true;
document.body.append(panel);
panel.toggle();
await vi.waitFor(() => {
await waitForFast(() => {
expect(panel.renderRoot.querySelector<HTMLButtonElement>(".tp-upload")?.disabled).toBe(false);
});
@@ -267,7 +268,7 @@ describe("OpenClawTerminalPanel upload lifecycle", () => {
},
});
panel.renderRoot.querySelector(".tp-viewport")?.dispatchEvent(drop);
await vi.waitFor(() => {
await waitForFast(() => {
expect(panel.renderRoot.querySelector(".tp-upload-card")?.textContent).toContain(
"Uploading 1 of 1",
);
@@ -315,7 +316,7 @@ describe("OpenClawTerminalPanel upload lifecycle", () => {
panel.available = true;
document.body.append(panel);
panel.toggle();
await vi.waitFor(() => {
await waitForFast(() => {
expect(panel.renderRoot.querySelector<HTMLButtonElement>(".tp-upload")?.disabled).toBe(false);
});
@@ -328,14 +329,14 @@ describe("OpenClawTerminalPanel upload lifecycle", () => {
},
});
panel.renderRoot.querySelector(".tp-viewport")?.dispatchEvent(drop);
await vi.waitFor(() => {
await waitForFast(() => {
expect(panel.renderRoot.querySelector(".tp-upload-card")?.textContent).toContain(
"Uploading 1 of 1",
);
});
panel.renderRoot.querySelector<HTMLButtonElement>(".tabstrip-tab__close")?.click();
await vi.waitFor(() => {
await waitForFast(() => {
expect(uploadSignal?.aborted).toBe(true);
expect(panel.renderRoot.querySelector(".tp-upload-card")).toBeNull();
});
+11 -10
View File
@@ -1,6 +1,7 @@
// Control UI tests cover skills behavior.
import { expectDefined } from "@openclaw/normalization-core";
import { describe, expect, it, vi } from "vitest";
import { waitForFast } from "../../test-helpers/wait-for.ts";
import {
installFromClawHub,
installSkill,
@@ -317,7 +318,7 @@ describe("loadSkills", () => {
);
const load = loadSkills(state);
await vi.waitFor(() => expect(state.skillsLoading).toBe(true));
await waitForFast(() => expect(state.skillsLoading).toBe(true));
state.connected = false;
expect(rejectStatus).toBeDefined();
rejectStatus?.(new Error("gateway disconnected"));
@@ -700,7 +701,7 @@ describe("skill mutations", () => {
const overlappingLoadAgents = vi.fn(async () => undefined);
const refresh = refreshSkills(state, loadAgents);
await vi.waitFor(() => expect(state.skillOperation).toEqual({ kind: "refresh" }));
await waitForFast(() => expect(state.skillOperation).toEqual({ kind: "refresh" }));
await refreshSkills(state, overlappingLoadAgents);
await updateSkillEnabled(state, "github", true);
@@ -727,7 +728,7 @@ describe("skill mutations", () => {
state.skillsAgentId = "alpha";
const refresh = refreshSkills(state, async () => undefined);
await vi.waitFor(() => expect(request).toHaveBeenCalledOnce());
await waitForFast(() => expect(request).toHaveBeenCalledOnce());
setSkillsAgentId(state, "beta");
expectDefined(
pending[0],
@@ -737,7 +738,7 @@ describe("skill mutations", () => {
managedSkillsDir: "/tmp/skills",
skills: [],
});
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(2));
await waitForFast(() => expect(request).toHaveBeenCalledTimes(2));
expectDefined(
pending[1],
"beta status request",
@@ -836,7 +837,7 @@ describe("skill mutations", () => {
state.skillEdits.github = "submitted-value";
const first = fixture.start(state);
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(1));
await waitForFast(() => expect(request).toHaveBeenCalledTimes(1));
expect(state.skillOperation).toEqual(fixture.expectedMutation);
await fixture.blocked(state);
@@ -875,7 +876,7 @@ describe("skill mutations", () => {
oldRequest.mockReturnValue(oldMutationResult.promise);
const oldMutation = updateSkillEnabled(state, "github", true);
await vi.waitFor(() => expect(oldRequest).toHaveBeenCalledOnce());
await waitForFast(() => expect(oldRequest).toHaveBeenCalledOnce());
const currentMutationResult = createDeferred<unknown>();
const currentRequest = vi.fn<TestRequest>((method) =>
@@ -892,7 +893,7 @@ describe("skill mutations", () => {
state.skillOperation = null;
const currentMutation = updateSkillEnabled(state, "calendar", true);
await vi.waitFor(() => expect(currentRequest).toHaveBeenCalledOnce());
await waitForFast(() => expect(currentRequest).toHaveBeenCalledOnce());
const currentOperation = state.skillOperation;
oldMutationResult.resolve({});
@@ -947,7 +948,7 @@ describe("skill mutations", () => {
expect(pendingRequests).toHaveLength(1);
expectDefined(pendingRequests[0], "skills update request").resolve({});
await vi.waitFor(() => {
await waitForFast(() => {
expect(pendingRequests).toHaveLength(2);
});
expectDefined(pendingRequests[1], "beta skills request after update").resolve({
@@ -983,7 +984,7 @@ describe("skill mutations", () => {
state.skillsAgentId = "alpha";
const mutation = updateSkillEnabled(state, "github", true);
await vi.waitFor(() => expect(request).toHaveBeenCalledOnce());
await waitForFast(() => expect(request).toHaveBeenCalledOnce());
setSkillsAgentId(state, "beta");
expect(rejectUpdate).toBeDefined();
rejectUpdate?.(new Error("stale update failed"));
@@ -1161,7 +1162,7 @@ describe("skill mutations", () => {
await Promise.resolve();
setSkillsAgentId(state, "beta");
queue.resolveNext({ message: "Installed" });
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(2));
await waitForFast(() => expect(request).toHaveBeenCalledTimes(2));
queue.resolveNext({
workspaceDir: "/tmp/beta-after-install",
managedSkillsDir: "/tmp/skills",
+12 -11
View File
@@ -3,6 +3,7 @@ import { expectDefined } from "@openclaw/normalization-core";
import { afterEach, describe, expect, it, vi } from "vitest";
import { GatewayRequestError } from "../../api/gateway.ts";
import type { GatewaySessionRow } from "../../api/types.ts";
import { waitForFast } from "../../test-helpers/wait-for.ts";
import {
addWorkboardCardComment,
archiveWorkboardCard,
@@ -268,7 +269,7 @@ describe("workboard controller", () => {
client: client as never,
sessions: [sampleSession],
});
await vi.waitFor(() => {
await waitForFast(() => {
expect(client.request).toHaveBeenCalledWith(
"workboard.cards.update",
expect.objectContaining({ id: linkedCard.id }),
@@ -1863,7 +1864,7 @@ describe("workboard controller", () => {
const refresh = loadWorkboard({ host, client: client as never, force: true });
await Promise.resolve();
const save = saveWorkboardCardDraft({ host, client: client as never });
await vi.waitFor(() => {
await waitForFast(() => {
expect(client.request).toHaveBeenCalledWith("workboard.cards.update", expect.anything());
});
refreshList.resolve({ cards: [sampleCard], statuses: ["todo", "done"] });
@@ -2121,7 +2122,7 @@ describe("workboard controller", () => {
state.draftTitle = "Unsaved edit";
const save = saveWorkboardCardDraft({ host, client: client as never });
await vi.waitFor(() => {
await waitForFast(() => {
expect(client.request).toHaveBeenCalledWith("workboard.cards.update", expect.anything());
});
stopWorkboardLifecycleRefresh(host);
@@ -2156,7 +2157,7 @@ describe("workboard controller", () => {
state.cards = [sampleCard];
const dispatch = dispatchWorkboard({ host, client: client as never });
await vi.waitFor(() => {
await waitForFast(() => {
expect(client.request).toHaveBeenCalledWith("workboard.cards.dispatch", {});
});
stopWorkboardLifecycleRefresh(host);
@@ -3302,7 +3303,7 @@ describe("workboard controller", () => {
client: client as never,
session: firstSession,
});
await vi.waitFor(() => {
await waitForFast(() => {
expect(client.request).toHaveBeenCalledWith(
"workboard.cards.create",
expect.objectContaining({ sessionKey: firstSession.key }),
@@ -3353,7 +3354,7 @@ describe("workboard controller", () => {
session: sampleSession,
});
list.resolve({ cards: [], statuses: ["todo"] });
await vi.waitFor(() => {
await waitForFast(() => {
expect(client.request).toHaveBeenCalledWith(
"workboard.cards.create",
expect.objectContaining({ sessionKey: sampleSession.key }),
@@ -3488,7 +3489,7 @@ describe("workboard controller", () => {
client: client as never,
sessions: [sampleSession],
});
await vi.waitFor(() => {
await waitForFast(() => {
expect(client.request).toHaveBeenCalledWith("workboard.cards.update", expect.anything());
});
stopWorkboardLifecycleRefresh(host);
@@ -5150,7 +5151,7 @@ describe("workboard controller", () => {
});
const sync = syncWorkboardLifecycle({ host, client: client as never, sessions: [] });
await vi.waitFor(() => {
await waitForFast(() => {
expect(client.request).toHaveBeenCalledWith("tasks.list", { limit: 500 });
});
stopWorkboardLifecycleRefresh(host);
@@ -5188,7 +5189,7 @@ describe("workboard controller", () => {
];
const syncing = syncWorkboardLifecycle({ host, client: client as never, sessions });
await vi.waitFor(() => {
await waitForFast(() => {
expect(client.request).toHaveBeenCalledWith(
"workboard.cards.update",
expect.objectContaining({ id: first.id }),
@@ -5231,7 +5232,7 @@ describe("workboard controller", () => {
});
const first = syncWorkboardLifecycle({ host, client: client as never, sessions: [] });
await vi.waitFor(() => {
await waitForFast(() => {
expect(client.request).toHaveBeenCalledWith("tasks.list", { limit: 500 });
});
const second = syncWorkboardLifecycle({ host, client: client as never, sessions: [] });
@@ -5288,7 +5289,7 @@ describe("workboard controller", () => {
sessions: [],
requestUpdate,
});
await vi.waitFor(() => {
await waitForFast(() => {
expect(client.request).toHaveBeenCalledWith("tasks.list", { limit: 500 });
});
await addWorkboardCardComment({
+17 -8
View File
@@ -1,4 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { waitForFast } from "../../test-helpers/wait-for.ts";
import { normalizeWorkboardChange } from "./change-payload.ts";
import {
configureWorkboardLiveRefresh,
@@ -61,7 +62,7 @@ describe("Workboard live refresh", () => {
configureWorkboardLiveRefresh({ host, client: client as never });
expect(handleWorkboardChanged(host, { epoch: "epoch-a", revision: 2 })).toBe(true);
await vi.waitFor(() =>
await waitForFast(() =>
expect(getWorkboardState(host).cards[0]?.title).toBe("Updated elsewhere"),
);
expect(handleWorkboardChanged(host, { epoch: "epoch-a", revision: 1 })).toBe(false);
@@ -98,12 +99,12 @@ describe("Workboard live refresh", () => {
configureWorkboardLiveRefresh({ host, client: client as never });
handleWorkboardChanged(host, { epoch: "epoch-a", revision: 1 });
await vi.waitFor(() => expect(listCalls).toBe(1));
await waitForFast(() => expect(listCalls).toBe(1));
handleWorkboardChanged(host, { epoch: "epoch-a", revision: 2 });
handleWorkboardChanged(host, { epoch: "epoch-a", revision: 3 });
firstList.resolve({ cards: [], statuses: ["todo", "done"] });
await vi.waitFor(() => expect(listCalls).toBe(2));
await waitForFast(() => expect(listCalls).toBe(2));
await Promise.resolve();
expect(listCalls).toBe(2);
});
@@ -127,7 +128,9 @@ describe("Workboard live refresh", () => {
state.draftOpen = false;
state.editingCardId = null;
resumeWorkboardLiveRefresh(host);
await vi.waitFor(() => expect(client.request).toHaveBeenCalledWith("workboard.cards.list", {}));
await waitForFast(() =>
expect(client.request).toHaveBeenCalledWith("workboard.cards.list", {}),
);
});
it("retries transient failures and treats a new epoch as authoritative", async () => {
@@ -146,7 +149,9 @@ describe("Workboard live refresh", () => {
configureWorkboardLiveRefresh({ host, client: client as never });
handleWorkboardChanged(host, { epoch: "epoch-a", revision: 9 });
await vi.waitFor(() => expect(client.request).toHaveBeenCalledWith("workboard.cards.list", {}));
await waitForFast(() =>
expect(client.request).toHaveBeenCalledWith("workboard.cards.list", {}),
);
resumeWorkboardLiveRefresh(host);
configureWorkboardLiveRefresh({ host, client: client as never });
await Promise.resolve();
@@ -160,7 +165,7 @@ describe("Workboard live refresh", () => {
).toHaveLength(2);
expect(handleWorkboardChanged(host, { epoch: "epoch-b", revision: 1 })).toBe(true);
await vi.waitFor(() =>
await waitForFast(() =>
expect(
client.request.mock.calls.filter(([method]) => method === "workboard.cards.list"),
).toHaveLength(3),
@@ -176,7 +181,9 @@ describe("Workboard live refresh", () => {
);
configureWorkboardLiveRefresh({ host, client: client as never });
handleWorkboardChanged(host, { epoch: "epoch-a", revision: 1 });
await vi.waitFor(() => expect(client.request).toHaveBeenCalledWith("workboard.cards.list", {}));
await waitForFast(() =>
expect(client.request).toHaveBeenCalledWith("workboard.cards.list", {}),
);
stopWorkboardLiveRefresh(host);
list.resolve({
@@ -210,7 +217,9 @@ describe("Workboard live refresh", () => {
);
configureWorkboardLiveRefresh({ host, client: client as never });
const loading = loadWorkboard({ host, client: client as never, force: true });
await vi.waitFor(() => expect(client.request).toHaveBeenCalledWith("workboard.cards.list", {}));
await waitForFast(() =>
expect(client.request).toHaveBeenCalledWith("workboard.cards.list", {}),
);
stopWorkboardLiveRefresh(host);
list.resolve({
@@ -4,6 +4,7 @@ import { nothing } from "lit";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { GatewayBrowserClient } from "../api/gateway.ts";
import type { ApplicationContext, ApplicationGatewaySnapshot } from "../app/context.ts";
import { waitForFast } from "../test-helpers/wait-for.ts";
import type { SessionsRouteData } from "./sessions/sessions-page.ts";
import type { SkillsRouteData } from "./skills/skills-page.ts";
import { USAGE_PAYLOAD_TTL_MS, type UsageRefreshReason } from "./usage/refresh-policy.ts";
@@ -235,7 +236,7 @@ describe("gateway source replacement across reconnect with a reused client", ()
document.body.append(page);
await page.updateComplete;
await vi.waitFor(() => expect(page.usageResult).toBe(freshResult));
await waitForFast(() => expect(page.usageResult).toBe(freshResult));
expect(request).toHaveBeenCalledWith("sessions.usage", expect.any(Object));
expect(page.usageResult).not.toBe(staleResult);
@@ -324,7 +325,7 @@ describe("gateway source replacement across reconnect with a reused client", ()
});
page.refreshRuntime.applyGatewaySnapshot(context.gateway.snapshot);
await vi.waitFor(() =>
await waitForFast(() =>
expect(request).toHaveBeenCalledWith("sessions.usage", expect.any(Object)),
);
});
@@ -385,7 +386,7 @@ describe("gateway source replacement across reconnect with a reused client", ()
visibility.mockReturnValue("visible");
document.dispatchEvent(new Event("visibilitychange"));
window.dispatchEvent(new Event("focus"));
await vi.waitFor(() => expect(page.usageLoading).toBe(false));
await waitForFast(() => expect(page.usageLoading).toBe(false));
expect(request.mock.calls.map(([method]) => method)).toEqual([
"sessions.usage",
"usage.cost",
@@ -394,7 +395,7 @@ describe("gateway source replacement across reconnect with a reused client", ()
page.usageLoading = true;
page.refreshRuntime.request("manual");
await vi.waitFor(() => expect(page.usageLoading).toBe(false));
await waitForFast(() => expect(page.usageLoading).toBe(false));
expect(request).toHaveBeenCalledTimes(6);
const failedRefresh = deferred<never>();
@@ -404,8 +405,8 @@ describe("gateway source replacement across reconnect with a reused client", ()
expect(request).toHaveBeenCalledTimes(9);
page.refreshRuntime.request("focus");
failedRefresh.reject(new Error("connection interrupted"));
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(12));
await vi.waitFor(() => expect(page.usageLoading).toBe(false));
await waitForFast(() => expect(request).toHaveBeenCalledTimes(12));
await waitForFast(() => expect(page.usageLoading).toBe(false));
});
it("preserves matching skills route data on the first bind", async () => {
const request = vi.fn();
@@ -460,7 +461,7 @@ describe("gateway source replacement across reconnect with a reused client", ()
document.body.append(page);
await page.updateComplete;
await vi.waitFor(() => expect(page.skillsReport).toBe(freshReport));
await waitForFast(() => expect(page.skillsReport).toBe(freshReport));
expect(page.skillsReport).not.toBe(staleReport);
});
@@ -542,7 +543,7 @@ describe("gateway source replacement across reconnect with a reused client", ()
page.connected = true;
const load = page.loadAgents();
await vi.waitFor(() => expect(ensureList).toHaveBeenCalledOnce());
await waitForFast(() => expect(ensureList).toHaveBeenCalledOnce());
const replacementAgents = {
defaultId: "fresh",
mainKey: "agent:fresh:main",
@@ -622,7 +623,7 @@ describe("gateway source replacement across reconnect with a reused client", ()
page.connected = true;
const load = page.loadDiagnostics();
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(4));
await waitForFast(() => expect(request).toHaveBeenCalledTimes(4));
await replaceContext(page, client);
pending.resolve({ models: [{ id: "stale" }], stale: true });
await load;
+13 -12
View File
@@ -2,6 +2,7 @@ import { render } from "lit";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { GatewayBrowserClient } from "../../api/gateway.ts";
import { getWorkspaceState } from "../../lib/workspace/index.ts";
import { waitForFast } from "../../test-helpers/wait-for.ts";
import { stopWorkspace } from "./workspace-controller.ts";
import { renderWorkspace as renderWorkspaceView } from "./workspace-view.ts";
@@ -165,7 +166,7 @@ describe("renderWorkspace", () => {
render(renderWorkspace({ host, client, connected: true }), host);
resolveWorkspace({ doc, workspaceVersion: doc.workspaceVersion });
await vi.waitFor(() => expect(state.loaded).toBe(true));
await waitForFast(() => expect(state.loaded).toBe(true));
expect(state.activeSlug).toBe("main");
} finally {
stopWorkspace(host);
@@ -226,7 +227,7 @@ describe("renderWorkspace", () => {
resolveFresh(2);
await freshResult;
await vi.waitFor(() => {
await waitForFast(() => {
render(renderWorkspace({ host, client, connected: true }), host);
expect(host.querySelector(".workspace-stat__value")?.textContent).toBe("2");
});
@@ -293,8 +294,8 @@ describe("renderWorkspace", () => {
try {
render(renderWorkspace({ host, client, connected: true }), host);
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(2));
await vi.waitFor(() => {
await waitForFast(() => expect(request).toHaveBeenCalledTimes(2));
await waitForFast(() => {
render(renderWorkspace({ host, client, connected: true }), host);
expect(
host.querySelector("[data-test-id='workspace-agent-status']")?.textContent,
@@ -303,10 +304,10 @@ describe("renderWorkspace", () => {
active = true;
render(renderWorkspace({ host, client, connected: true, sessionListRevision: 1 }), host);
await vi.waitFor(() => {
await waitForFast(() => {
expect(request.mock.calls.filter(([method]) => method === "sessions.list")).toHaveLength(2);
});
await vi.waitFor(() => {
await waitForFast(() => {
render(renderWorkspace({ host, client, connected: true, sessionListRevision: 1 }), host);
expect(
host.querySelector("[data-test-id='workspace-agent-status']")?.textContent,
@@ -371,24 +372,24 @@ describe("renderWorkspace", () => {
try {
state.workspace = workspace(1);
render(renderWorkspace({ host, client, connected: true }), host);
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(1));
await vi.waitFor(() => {
await waitForFast(() => expect(request).toHaveBeenCalledTimes(1));
await waitForFast(() => {
render(renderWorkspace({ host, client, connected: true }), host);
expect(host.querySelector("iframe")?.getAttribute("src")).toContain("old.html");
});
state.workspace = workspace(2);
render(renderWorkspace({ host, client, connected: true }), host);
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(2));
await vi.waitFor(() => {
await waitForFast(() => expect(request).toHaveBeenCalledTimes(2));
await waitForFast(() => {
render(renderWorkspace({ host, client, connected: true }), host);
expect(host.querySelector("iframe")?.getAttribute("src")).toContain("new.html");
});
render(renderWorkspace({ host, client, connected: false }), host);
render(renderWorkspace({ host, client, connected: true }), host);
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(3));
await vi.waitFor(() => {
await waitForFast(() => expect(request).toHaveBeenCalledTimes(3));
await waitForFast(() => {
render(renderWorkspace({ host, client, connected: true }), host);
expect(host.querySelector("iframe")?.getAttribute("src")).toContain(
"3333333333333333333333333333333333333333333",
+8 -7
View File
@@ -7,6 +7,7 @@ import type { RouteId } from "../../app-route-paths.ts";
import type { ApplicationContext, ApplicationGatewaySnapshot } from "../../app/context.ts";
import { i18n, t } from "../../i18n/index.ts";
import { createApplicationContextProvider } from "../../test-helpers/application-context.ts";
import { waitForFast } from "../../test-helpers/wait-for.ts";
import { USAGE_PAYLOAD_TTL_MS, type UsageRefreshReason } from "../usage/refresh-policy.ts";
import { ProfilePage } from "./profile-page.ts";
@@ -189,8 +190,8 @@ it("gates profile usage refreshes by payload age and page visibility", async ()
};
provider.append(page);
document.body.append(provider);
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(2));
await vi.waitFor(() => expect(page.loading).toBe(false));
await waitForFast(() => expect(request).toHaveBeenCalledTimes(2));
await waitForFast(() => expect(page.loading).toBe(false));
harness.emitConnected(false);
harness.emitConnected(true);
@@ -226,12 +227,12 @@ it("gates profile usage refreshes by payload age and page visibility", async ()
visibility.mockReturnValue("visible");
document.dispatchEvent(new Event("visibilitychange"));
window.dispatchEvent(new Event("focus"));
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(4));
await vi.waitFor(() => expect(page.loading).toBe(false));
await waitForFast(() => expect(request).toHaveBeenCalledTimes(4));
await waitForFast(() => expect(page.loading).toBe(false));
page.querySelector<HTMLButtonElement>(".profile-refresh")?.click();
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(6));
await vi.waitFor(() => expect(page.loading).toBe(false));
await waitForFast(() => expect(request).toHaveBeenCalledTimes(6));
await waitForFast(() => expect(page.loading).toBe(false));
let settlePoll: TimerHandler | null = null;
let settleDelayMs: number | undefined;
@@ -264,5 +265,5 @@ it("gates profile usage refreshes by payload age and page visibility", async ()
setTimeoutSpy.mockRestore();
visibility.mockReturnValue("visible");
window.dispatchEvent(new Event("focus"));
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(8));
await waitForFast(() => expect(request).toHaveBeenCalledTimes(8));
});