test: reduce repeated test setup overhead

This commit is contained in:
Peter Steinberger
2026-04-27 13:31:43 +01:00
parent 0931a1f11e
commit cae492374c
10 changed files with 115 additions and 114 deletions

View File

@@ -1,30 +1,39 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("./remote-http.js", () => ({
withRemoteHttpResponse: vi.fn(),
}));
let postJson: typeof import("./post-json.js").postJson;
let withRemoteHttpResponse: typeof import("./remote-http.js").withRemoteHttpResponse;
const { postJson } = await import("./post-json.js");
const { withRemoteHttpResponse } = await import("./remote-http.js");
const remoteHttpMock = vi.mocked(withRemoteHttpResponse);
function jsonResponse(payload: unknown, status = 200): Response {
return {
ok: status >= 200 && status < 300,
status,
json: async () => payload,
text: async () => JSON.stringify(payload),
} as Response;
}
function textResponse(body: string, status: number): Response {
return {
ok: status >= 200 && status < 300,
status,
json: async () => JSON.parse(body) as unknown,
text: async () => body,
} as Response;
}
describe("postJson", () => {
let remoteHttpMock: ReturnType<typeof vi.mocked<typeof withRemoteHttpResponse>>;
beforeAll(async () => {
({ postJson } = await import("./post-json.js"));
({ withRemoteHttpResponse } = await import("./remote-http.js"));
remoteHttpMock = vi.mocked(withRemoteHttpResponse);
});
beforeEach(() => {
vi.clearAllMocks();
});
it("parses JSON payload on successful response", async () => {
remoteHttpMock.mockImplementationOnce(async (params) => {
return await params.onResponse(
new Response(JSON.stringify({ data: [{ embedding: [1, 2] }] }), { status: 200 }),
);
return await params.onResponse(jsonResponse({ data: [{ embedding: [1, 2] }] }));
});
const result = await postJson({
@@ -40,7 +49,7 @@ describe("postJson", () => {
it("attaches status to thrown error when requested", async () => {
remoteHttpMock.mockImplementationOnce(async (params) => {
return await params.onResponse(new Response("bad gateway", { status: 502 }));
return await params.onResponse(textResponse("bad gateway", 502));
});
await expect(

View File

@@ -6,14 +6,14 @@ vi.mock("./plugin-sdk/browser-maintenance.js", () => ({
closeTrackedBrowserTabsForSessions,
}));
const { cleanupBrowserSessionsForLifecycleEnd } = await import("./browser-lifecycle-cleanup.js");
describe("cleanupBrowserSessionsForLifecycleEnd", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("normalizes session keys before closing browser sessions", async () => {
const { cleanupBrowserSessionsForLifecycleEnd } =
await import("./browser-lifecycle-cleanup.js");
const onWarn = vi.fn();
await expect(
@@ -30,8 +30,6 @@ describe("cleanupBrowserSessionsForLifecycleEnd", () => {
});
it("swallows browser cleanup failures", async () => {
const { cleanupBrowserSessionsForLifecycleEnd } =
await import("./browser-lifecycle-cleanup.js");
const onError = vi.fn();
const error = new Error("cleanup failed");
closeTrackedBrowserTabsForSessions.mockRejectedValueOnce(error);

View File

@@ -1,6 +1,6 @@
import fs from "node:fs";
import { fileURLToPath } from "node:url";
import { beforeAll, describe, expect, it } from "vitest";
import { describe, expect, it } from "vitest";
type TranslationTree = {
readonly [key: string]: string | TranslationTree | undefined;
@@ -18,7 +18,17 @@ const describeWhenUiI18nPresent = fs.existsSync(fileURLToPath(registryModuleUrl)
? describe
: describe.skip;
let registry: LocaleRegistry;
const registry =
describeWhenUiI18nPresent === describe
? ((await import("../../ui/src/i18n/lib/registry.ts")) as LocaleRegistry)
: undefined;
function getRegistry(): LocaleRegistry {
if (registry === undefined) {
throw new Error("expected UI i18n registry to be present");
}
return registry;
}
function getNestedTranslation(map: TranslationTree | null, ...path: string[]): string | undefined {
let value: string | TranslationTree | undefined = map ?? undefined;
@@ -32,12 +42,10 @@ function getNestedTranslation(map: TranslationTree | null, ...path: string[]): s
}
describeWhenUiI18nPresent("ui i18n locale registry", () => {
beforeAll(async () => {
registry = (await import("../../ui/src/i18n/lib/registry.ts")) as LocaleRegistry;
});
it("lists supported locales", () => {
expect(registry.SUPPORTED_LOCALES).toEqual([
const localeRegistry = getRegistry();
expect(localeRegistry.SUPPORTED_LOCALES).toEqual([
"en",
"zh-CN",
"zh-TW",
@@ -53,34 +61,37 @@ describeWhenUiI18nPresent("ui i18n locale registry", () => {
"pl",
"th",
]);
expect(registry.DEFAULT_LOCALE).toBe("en");
expect(localeRegistry.DEFAULT_LOCALE).toBe("en");
});
it("resolves browser locale fallbacks", () => {
expect(registry.resolveNavigatorLocale("de-DE")).toBe("de");
expect(registry.resolveNavigatorLocale("es-ES")).toBe("es");
expect(registry.resolveNavigatorLocale("es-MX")).toBe("es");
expect(registry.resolveNavigatorLocale("pt-PT")).toBe("pt-BR");
expect(registry.resolveNavigatorLocale("zh-HK")).toBe("zh-TW");
expect(registry.resolveNavigatorLocale("en-US")).toBe("en");
expect(registry.resolveNavigatorLocale("ja-JP")).toBe("ja-JP");
expect(registry.resolveNavigatorLocale("ko-KR")).toBe("ko");
expect(registry.resolveNavigatorLocale("fr-CA")).toBe("fr");
expect(registry.resolveNavigatorLocale("tr-TR")).toBe("tr");
expect(registry.resolveNavigatorLocale("uk-UA")).toBe("uk");
expect(registry.resolveNavigatorLocale("id-ID")).toBe("id");
expect(registry.resolveNavigatorLocale("pl-PL")).toBe("pl");
expect(registry.resolveNavigatorLocale("th-TH")).toBe("th");
const localeRegistry = getRegistry();
expect(localeRegistry.resolveNavigatorLocale("de-DE")).toBe("de");
expect(localeRegistry.resolveNavigatorLocale("es-ES")).toBe("es");
expect(localeRegistry.resolveNavigatorLocale("es-MX")).toBe("es");
expect(localeRegistry.resolveNavigatorLocale("pt-PT")).toBe("pt-BR");
expect(localeRegistry.resolveNavigatorLocale("zh-HK")).toBe("zh-TW");
expect(localeRegistry.resolveNavigatorLocale("en-US")).toBe("en");
expect(localeRegistry.resolveNavigatorLocale("ja-JP")).toBe("ja-JP");
expect(localeRegistry.resolveNavigatorLocale("ko-KR")).toBe("ko");
expect(localeRegistry.resolveNavigatorLocale("fr-CA")).toBe("fr");
expect(localeRegistry.resolveNavigatorLocale("tr-TR")).toBe("tr");
expect(localeRegistry.resolveNavigatorLocale("uk-UA")).toBe("uk");
expect(localeRegistry.resolveNavigatorLocale("id-ID")).toBe("id");
expect(localeRegistry.resolveNavigatorLocale("pl-PL")).toBe("pl");
expect(localeRegistry.resolveNavigatorLocale("th-TH")).toBe("th");
});
it("loads lazy locale translations from the registry", async () => {
const localeRegistry = getRegistry();
const [de, es, ptBR, zhCN, th, en] = await Promise.all([
registry.loadLazyLocaleTranslation("de"),
registry.loadLazyLocaleTranslation("es"),
registry.loadLazyLocaleTranslation("pt-BR"),
registry.loadLazyLocaleTranslation("zh-CN"),
registry.loadLazyLocaleTranslation("th"),
registry.loadLazyLocaleTranslation("en"),
localeRegistry.loadLazyLocaleTranslation("de"),
localeRegistry.loadLazyLocaleTranslation("es"),
localeRegistry.loadLazyLocaleTranslation("pt-BR"),
localeRegistry.loadLazyLocaleTranslation("zh-CN"),
localeRegistry.loadLazyLocaleTranslation("th"),
localeRegistry.loadLazyLocaleTranslation("en"),
]);
expect(getNestedTranslation(de, "common", "health")).toBe("Status");

View File

@@ -5,16 +5,18 @@ import { afterEach, describe, expect, it } from "vitest";
import { cleanupTempDirs, makeTempDir } from "../test/helpers/temp-dir.js";
const tempRoots: string[] = [];
const installerPath = path.join(process.cwd(), "scripts", "install.sh");
const installerSource = fs.readFileSync(installerPath, "utf-8");
const versionHelperStart = installerSource.indexOf("load_install_version_helpers() {");
const versionHelperEnd = installerSource.indexOf("\nis_gateway_daemon_loaded() {");
if (versionHelperStart < 0 || versionHelperEnd < 0) {
throw new Error("install.sh version helper block not found");
}
const versionHelperSource = installerSource.slice(versionHelperStart, versionHelperEnd);
function resolveInstallerVersionCases(params: { stdinCwd: string }): string[] {
const installerPath = path.join(process.cwd(), "scripts", "install.sh");
const installerSource = fs.readFileSync(installerPath, "utf-8");
const versionHelperStart = installerSource.indexOf("load_install_version_helpers() {");
const versionHelperEnd = installerSource.indexOf("\nis_gateway_daemon_loaded() {");
if (versionHelperStart < 0 || versionHelperEnd < 0) {
throw new Error("install.sh version helper block not found");
}
const versionHelperSource = installerSource.slice(versionHelperStart, versionHelperEnd);
const output = execFileSync(
"bash",
[

View File

@@ -2,7 +2,7 @@ import fsSync from "node:fs";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../../media/mime.js", () => ({
detectMime: async (opts: { filePath?: string }) => {
@@ -30,17 +30,11 @@ import {
type MemoryMultimodalSettings,
} from "./multimodal.js";
let sharedTempRoot = "";
const sharedTempRoot = fsSync.mkdtempSync(path.join(os.tmpdir(), "memory-host-sdk-tests-"));
let sharedTempId = 0;
beforeAll(() => {
sharedTempRoot = fsSync.mkdtempSync(path.join(os.tmpdir(), "memory-host-sdk-tests-"));
});
afterAll(() => {
if (sharedTempRoot) {
fsSync.rmSync(sharedTempRoot, { recursive: true, force: true });
}
fsSync.rmSync(sharedTempRoot, { recursive: true, force: true });
});
function setupTempDirLifecycle(prefix: string): () => string {

View File

@@ -1,30 +1,39 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("./remote-http.js", () => ({
withRemoteHttpResponse: vi.fn(),
}));
let postJson: typeof import("./post-json.js").postJson;
let withRemoteHttpResponse: typeof import("./remote-http.js").withRemoteHttpResponse;
const { postJson } = await import("./post-json.js");
const { withRemoteHttpResponse } = await import("./remote-http.js");
const remoteHttpMock = vi.mocked(withRemoteHttpResponse);
function jsonResponse(payload: unknown, status = 200): Response {
return {
ok: status >= 200 && status < 300,
status,
json: async () => payload,
text: async () => JSON.stringify(payload),
} as Response;
}
function textResponse(body: string, status: number): Response {
return {
ok: status >= 200 && status < 300,
status,
json: async () => JSON.parse(body) as unknown,
text: async () => body,
} as Response;
}
describe("postJson", () => {
let remoteHttpMock: ReturnType<typeof vi.mocked<typeof withRemoteHttpResponse>>;
beforeAll(async () => {
({ postJson } = await import("./post-json.js"));
({ withRemoteHttpResponse } = await import("./remote-http.js"));
remoteHttpMock = vi.mocked(withRemoteHttpResponse);
});
beforeEach(() => {
vi.clearAllMocks();
});
it("parses JSON payload on successful response", async () => {
remoteHttpMock.mockImplementationOnce(async (params) => {
return await params.onResponse(
new Response(JSON.stringify({ data: [{ embedding: [1, 2] }] }), { status: 200 }),
);
return await params.onResponse(jsonResponse({ data: [{ embedding: [1, 2] }] }));
});
const result = await postJson({
@@ -40,7 +49,7 @@ describe("postJson", () => {
it("attaches status to thrown error when requested", async () => {
remoteHttpMock.mockImplementationOnce(async (params) => {
return await params.onResponse(new Response("bad gateway", { status: 502 }));
return await params.onResponse(textResponse("bad gateway", 502));
});
await expect(

View File

@@ -1,4 +1,4 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { SecretInput } from "../config/types.secrets.js";
vi.mock("../infra/device-bootstrap.js", () => ({
@@ -8,9 +8,9 @@ vi.mock("../infra/device-bootstrap.js", () => ({
})),
}));
let encodePairingSetupCode: typeof import("./setup-code.js").encodePairingSetupCode;
let resolvePairingSetupFromConfig: typeof import("./setup-code.js").resolvePairingSetupFromConfig;
let issueDeviceBootstrapTokenMock: typeof import("../infra/device-bootstrap.js").issueDeviceBootstrapToken;
const { encodePairingSetupCode, resolvePairingSetupFromConfig } = await import("./setup-code.js");
const { issueDeviceBootstrapToken: issueDeviceBootstrapTokenMock } =
await import("../infra/device-bootstrap.js");
describe("pairing setup code", () => {
type ResolvedSetup = Awaited<ReturnType<typeof resolvePairingSetupFromConfig>>;
@@ -185,12 +185,6 @@ describe("pairing setup code", () => {
vi.stubEnv("OPENCLAW_GATEWAY_PORT", "");
});
beforeAll(async () => {
({ encodePairingSetupCode, resolvePairingSetupFromConfig } = await import("./setup-code.js"));
({ issueDeviceBootstrapToken: issueDeviceBootstrapTokenMock } =
await import("../infra/device-bootstrap.js"));
});
beforeEach(() => {
vi.mocked(issueDeviceBootstrapTokenMock).mockClear();
});

View File

@@ -2,7 +2,7 @@ import fsSync from "node:fs";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
import {
clearSkillScanCacheForTest,
isScannable,
@@ -16,17 +16,11 @@ import type { SkillScanOptions } from "./skill-scanner.js";
// Helpers
// ---------------------------------------------------------------------------
let fixtureRoot = "";
const fixtureRoot = fsSync.mkdtempSync(path.join(os.tmpdir(), "skill-scanner-test-"));
let fixtureId = 0;
beforeAll(() => {
fixtureRoot = fsSync.mkdtempSync(path.join(os.tmpdir(), "skill-scanner-test-"));
});
afterAll(() => {
if (fixtureRoot) {
fsSync.rmSync(fixtureRoot, { recursive: true, force: true });
}
fsSync.rmSync(fixtureRoot, { recursive: true, force: true });
});
function makeTmpDir(): string {

View File

@@ -2,12 +2,12 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import type { Message, Usage } from "@mariozechner/pi-ai";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { afterAll, describe, expect, it } from "vitest";
import { exportTrajectoryBundle, resolveDefaultTrajectoryExportDir } from "./export.js";
import { resolveTrajectoryPointerFilePath } from "./paths.js";
import type { TrajectoryEvent } from "./types.js";
let tempRoot = "";
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-trajectory-"));
let tempDirId = 0;
function makeTempDir(): string {
@@ -180,14 +180,8 @@ function writeToolCallSessionFile(sessionFile: string): void {
);
}
beforeAll(() => {
tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-trajectory-"));
});
afterAll(() => {
if (tempRoot) {
fs.rmSync(tempRoot, { recursive: true, force: true });
}
fs.rmSync(tempRoot, { recursive: true, force: true });
});
describe("exportTrajectoryBundle", () => {

View File

@@ -1,4 +1,4 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
const loadBundledPluginPublicSurfaceModuleSync = vi.hoisted(() => vi.fn());
const loadActivatedBundledPluginPublicSurfaceModuleSync = vi.hoisted(() => vi.fn());
@@ -31,13 +31,9 @@ vi.mock("../plugin-sdk/facade-runtime.js", () => ({
loadBundledPluginPublicSurfaceModuleSync,
}));
const tts = await import("./tts.js");
describe("tts runtime facade", () => {
let tts: typeof import("./tts.js");
beforeAll(async () => {
tts = await import("./tts.js");
});
beforeEach(() => {
loadActivatedBundledPluginPublicSurfaceModuleSync.mockReset();
loadBundledPluginPublicSurfaceModuleSync.mockReset();