mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 21:20:44 +00:00
test: require ui deferred callbacks
This commit is contained in:
@@ -133,12 +133,15 @@ function row(key: string, overrides?: Partial<GatewaySessionRow>): GatewaySessio
|
||||
}
|
||||
|
||||
function createDeferred<T>() {
|
||||
let resolve!: (value: T) => void;
|
||||
let reject!: (reason?: unknown) => void;
|
||||
let resolve: ((value: T) => void) | undefined;
|
||||
let reject: ((reason?: unknown) => void) | undefined;
|
||||
const promise = new Promise<T>((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
if (!resolve || !reject) {
|
||||
throw new Error("Expected deferred callbacks to be initialized");
|
||||
}
|
||||
return { promise, resolve, reject };
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,17 @@ vi.mock("./app-scroll.ts", () => ({
|
||||
|
||||
import { handleConnected } from "./app-lifecycle.ts";
|
||||
|
||||
function createDeferred() {
|
||||
let resolve: (() => void) | undefined;
|
||||
const promise = new Promise<void>((res) => {
|
||||
resolve = res;
|
||||
});
|
||||
if (!resolve) {
|
||||
throw new Error("Expected bootstrap deferred resolver to be initialized");
|
||||
}
|
||||
return { promise, resolve };
|
||||
}
|
||||
|
||||
function createHost() {
|
||||
return {
|
||||
basePath: "",
|
||||
@@ -77,30 +88,22 @@ describe("handleConnected", () => {
|
||||
});
|
||||
|
||||
it("waits for bootstrap load before first gateway connect", async () => {
|
||||
let resolveBootstrap!: () => void;
|
||||
loadBootstrapMock.mockReturnValueOnce(
|
||||
new Promise<void>((resolve) => {
|
||||
resolveBootstrap = resolve;
|
||||
}),
|
||||
);
|
||||
const bootstrap = createDeferred();
|
||||
loadBootstrapMock.mockReturnValueOnce(bootstrap.promise);
|
||||
connectGatewayMock.mockReset();
|
||||
const host = createHost();
|
||||
|
||||
handleConnected(host as never);
|
||||
expect(connectGatewayMock).not.toHaveBeenCalled();
|
||||
|
||||
resolveBootstrap();
|
||||
bootstrap.resolve();
|
||||
await Promise.resolve();
|
||||
expect(connectGatewayMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("skips deferred connect when disconnected before bootstrap resolves", async () => {
|
||||
let resolveBootstrap!: () => void;
|
||||
loadBootstrapMock.mockReturnValueOnce(
|
||||
new Promise<void>((resolve) => {
|
||||
resolveBootstrap = resolve;
|
||||
}),
|
||||
);
|
||||
const bootstrap = createDeferred();
|
||||
loadBootstrapMock.mockReturnValueOnce(bootstrap.promise);
|
||||
connectGatewayMock.mockReset();
|
||||
const host = createHost();
|
||||
|
||||
@@ -108,7 +111,7 @@ describe("handleConnected", () => {
|
||||
expect(connectGatewayMock).not.toHaveBeenCalled();
|
||||
|
||||
host.connectGeneration += 1;
|
||||
resolveBootstrap();
|
||||
bootstrap.resolve();
|
||||
await Promise.resolve();
|
||||
|
||||
expect(connectGatewayMock).not.toHaveBeenCalled();
|
||||
|
||||
@@ -3,6 +3,17 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
type CronRunsLoadStatus = "ok" | "error" | "skipped";
|
||||
|
||||
function createDeferred<T = void>() {
|
||||
let resolve: ((value: T | PromiseLike<T>) => void) | undefined;
|
||||
const promise = new Promise<T>((res) => {
|
||||
resolve = res;
|
||||
});
|
||||
if (!resolve) {
|
||||
throw new Error("Expected deferred resolver to be initialized");
|
||||
}
|
||||
return { promise, resolve };
|
||||
}
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
refreshChatMock: vi.fn(async () => {}),
|
||||
scheduleChatScrollMock: vi.fn(),
|
||||
@@ -196,12 +207,8 @@ describe("refreshActiveTab", () => {
|
||||
it("records tab visible timing without waiting for the tab refresh RPC", async () => {
|
||||
const host = createHost();
|
||||
host.tab = "chat";
|
||||
let resolveSessions!: () => void;
|
||||
mocks.loadSessionsMock.mockReturnValueOnce(
|
||||
new Promise<void>((resolve) => {
|
||||
resolveSessions = resolve;
|
||||
}),
|
||||
);
|
||||
const sessions = createDeferred();
|
||||
mocks.loadSessionsMock.mockReturnValueOnce(sessions.promise);
|
||||
|
||||
setTab(host as never, "sessions");
|
||||
|
||||
@@ -221,7 +228,7 @@ describe("refreshActiveTab", () => {
|
||||
);
|
||||
});
|
||||
|
||||
resolveSessions();
|
||||
sessions.resolve();
|
||||
});
|
||||
|
||||
it("does not wait for secondary overview refreshes before resolving", async () => {
|
||||
@@ -244,12 +251,8 @@ describe("refreshActiveTab", () => {
|
||||
it("does not wait for config schema before resolving config tab refresh", async () => {
|
||||
const host = createHost();
|
||||
host.tab = "config";
|
||||
let resolveSchema!: () => void;
|
||||
mocks.loadConfigSchemaMock.mockReturnValueOnce(
|
||||
new Promise<void>((resolve) => {
|
||||
resolveSchema = resolve;
|
||||
}),
|
||||
);
|
||||
const schema = createDeferred();
|
||||
mocks.loadConfigSchemaMock.mockReturnValueOnce(schema.promise);
|
||||
|
||||
const refresh = refreshActiveTab(host as never);
|
||||
const outcome = await Promise.race([
|
||||
@@ -262,7 +265,7 @@ describe("refreshActiveTab", () => {
|
||||
expect(mocks.loadConfigMock).toHaveBeenCalledOnce();
|
||||
expect(host.requestUpdate).not.toHaveBeenCalled();
|
||||
|
||||
resolveSchema();
|
||||
schema.resolve();
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(host.requestUpdate).toHaveBeenCalledOnce();
|
||||
@@ -272,18 +275,12 @@ describe("refreshActiveTab", () => {
|
||||
it("renders channels from the cheap snapshot before starting slow probes", async () => {
|
||||
const host = createHost();
|
||||
host.tab = "channels";
|
||||
let resolveSchema!: () => void;
|
||||
let resolveProbe!: () => void;
|
||||
mocks.loadConfigSchemaMock.mockReturnValueOnce(
|
||||
new Promise<void>((resolve) => {
|
||||
resolveSchema = resolve;
|
||||
}),
|
||||
);
|
||||
const schema = createDeferred();
|
||||
const channelProbe = createDeferred();
|
||||
mocks.loadConfigSchemaMock.mockReturnValueOnce(schema.promise);
|
||||
mocks.loadChannelsMock.mockImplementation(async (_host, probe) => {
|
||||
if (probe) {
|
||||
await new Promise<void>((resolve) => {
|
||||
resolveProbe = resolve;
|
||||
});
|
||||
await channelProbe.promise;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -298,8 +295,8 @@ describe("refreshActiveTab", () => {
|
||||
expect(mocks.loadConfigMock).toHaveBeenCalledOnce();
|
||||
expect(host.requestUpdate).not.toHaveBeenCalled();
|
||||
|
||||
resolveSchema();
|
||||
resolveProbe();
|
||||
schema.resolve();
|
||||
channelProbe.resolve();
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(host.requestUpdate).toHaveBeenCalledTimes(2);
|
||||
@@ -309,16 +306,12 @@ describe("refreshActiveTab", () => {
|
||||
it("records overview secondary refresh duration and aggregate status", async () => {
|
||||
const host = createHost();
|
||||
host.tab = "overview";
|
||||
let resolveUsage!: () => void;
|
||||
mocks.loadUsageMock.mockReturnValueOnce(
|
||||
new Promise<void>((resolve) => {
|
||||
resolveUsage = resolve;
|
||||
}),
|
||||
);
|
||||
const usage = createDeferred();
|
||||
mocks.loadUsageMock.mockReturnValueOnce(usage.promise);
|
||||
mocks.loadSkillsMock.mockRejectedValueOnce(new Error("skills failed"));
|
||||
|
||||
await refreshActiveTab(host as never);
|
||||
resolveUsage();
|
||||
usage.resolve();
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(host.eventLogBuffer).toEqual(
|
||||
@@ -401,16 +394,12 @@ describe("refreshActiveTab", () => {
|
||||
it("does not record stale cron run timing after leaving the cron tab", async () => {
|
||||
const host = createHost();
|
||||
host.tab = "cron";
|
||||
let resolveRuns!: () => void;
|
||||
mocks.loadCronRunsMock.mockReturnValueOnce(
|
||||
new Promise<"ok">((resolve) => {
|
||||
resolveRuns = () => resolve("ok");
|
||||
}),
|
||||
);
|
||||
const runs = createDeferred<"ok">();
|
||||
mocks.loadCronRunsMock.mockReturnValueOnce(runs.promise);
|
||||
|
||||
await refreshActiveTab(host as never);
|
||||
host.tab = "chat";
|
||||
resolveRuns();
|
||||
runs.resolve("ok");
|
||||
await Promise.resolve();
|
||||
|
||||
expect(host.eventLogBuffer).not.toEqual(
|
||||
|
||||
@@ -27,6 +27,17 @@ type HandlerMap = {
|
||||
|
||||
type MockWebSocketHandler = (ev?: { code?: number; data?: string; reason?: string }) => void;
|
||||
|
||||
function createDeferred<T>() {
|
||||
let resolve: ((value: T) => void) | undefined;
|
||||
const promise = new Promise<T>((res) => {
|
||||
resolve = res;
|
||||
});
|
||||
if (!resolve) {
|
||||
throw new Error("Expected deferred resolver to be initialized");
|
||||
}
|
||||
return { promise, resolve };
|
||||
}
|
||||
|
||||
class MockWebSocket {
|
||||
static OPEN = 1;
|
||||
|
||||
@@ -556,13 +567,8 @@ describe("GatewayBrowserClient", () => {
|
||||
|
||||
it("does not send stale connect frames on a replacement socket", async () => {
|
||||
vi.useFakeTimers();
|
||||
let resolveIdentity!: (identity: DeviceIdentity) => void;
|
||||
loadOrCreateDeviceIdentityMock.mockImplementationOnce(
|
||||
() =>
|
||||
new Promise<DeviceIdentity>((resolve) => {
|
||||
resolveIdentity = resolve;
|
||||
}),
|
||||
);
|
||||
const identity = createDeferred<DeviceIdentity>();
|
||||
loadOrCreateDeviceIdentityMock.mockImplementationOnce(() => identity.promise);
|
||||
|
||||
const client = new GatewayBrowserClient({
|
||||
url: "ws://127.0.0.1:18789",
|
||||
@@ -585,7 +591,7 @@ describe("GatewayBrowserClient", () => {
|
||||
const secondWs = getLatestWebSocket();
|
||||
expect(secondWs).not.toBe(firstWs);
|
||||
|
||||
resolveIdentity({
|
||||
identity.resolve({
|
||||
deviceId: "device-1",
|
||||
privateKey: "private-key", // pragma: allowlist secret
|
||||
publicKey: "public-key", // pragma: allowlist secret
|
||||
|
||||
Reference in New Issue
Block a user