mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-20 21:51:28 +00:00
UI: restore cron type compatibility
This commit is contained in:
@@ -1,53 +1,90 @@
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { i18n, t } from "../lib/translate.ts";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { pt_BR } from "../locales/pt-BR.ts";
|
||||
import { zh_CN } from "../locales/zh-CN.ts";
|
||||
import { zh_TW } from "../locales/zh-TW.ts";
|
||||
|
||||
type TranslateModule = typeof import("../lib/translate.ts");
|
||||
|
||||
function createStorageMock(): Storage {
|
||||
const store = new Map<string, string>();
|
||||
return {
|
||||
get length() {
|
||||
return store.size;
|
||||
},
|
||||
clear() {
|
||||
store.clear();
|
||||
},
|
||||
getItem(key: string) {
|
||||
return store.get(key) ?? null;
|
||||
},
|
||||
key(index: number) {
|
||||
return Array.from(store.keys())[index] ?? null;
|
||||
},
|
||||
removeItem(key: string) {
|
||||
store.delete(key);
|
||||
},
|
||||
setItem(key: string, value: string) {
|
||||
store.set(key, String(value));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("i18n", () => {
|
||||
let translate: TranslateModule;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
vi.stubGlobal("localStorage", createStorageMock());
|
||||
vi.stubGlobal("navigator", { language: "en-US" } as Navigator);
|
||||
translate = await import("../lib/translate.ts");
|
||||
localStorage.clear();
|
||||
// Reset to English
|
||||
await i18n.setLocale("en");
|
||||
await translate.i18n.setLocale("en");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("should return the key if translation is missing", () => {
|
||||
expect(t("non.existent.key")).toBe("non.existent.key");
|
||||
expect(translate.t("non.existent.key")).toBe("non.existent.key");
|
||||
});
|
||||
|
||||
it("should return the correct English translation", () => {
|
||||
expect(t("common.health")).toBe("Health");
|
||||
expect(translate.t("common.health")).toBe("Health");
|
||||
});
|
||||
|
||||
it("should replace parameters correctly", () => {
|
||||
expect(t("overview.stats.cronNext", { time: "10:00" })).toBe("Next wake 10:00");
|
||||
expect(translate.t("overview.stats.cronNext", { time: "10:00" })).toBe("Next wake 10:00");
|
||||
});
|
||||
|
||||
it("should fallback to English if key is missing in another locale", async () => {
|
||||
// We haven't registered other locales in the test environment yet,
|
||||
// but the logic should fallback to 'en' map which is always there.
|
||||
await i18n.setLocale("zh-CN");
|
||||
await translate.i18n.setLocale("zh-CN");
|
||||
// Since we don't mock the import, it might fail to load zh-CN,
|
||||
// but let's assume it falls back to English for now.
|
||||
expect(t("common.health")).toBeDefined();
|
||||
expect(translate.t("common.health")).toBeDefined();
|
||||
});
|
||||
|
||||
it("loads translations even when setting the same locale again", async () => {
|
||||
const internal = i18n as unknown as {
|
||||
const internal = translate.i18n as unknown as {
|
||||
locale: string;
|
||||
translations: Record<string, unknown>;
|
||||
};
|
||||
internal.locale = "zh-CN";
|
||||
delete internal.translations["zh-CN"];
|
||||
|
||||
await i18n.setLocale("zh-CN");
|
||||
expect(t("common.health")).toBe("健康状况");
|
||||
await translate.i18n.setLocale("zh-CN");
|
||||
expect(translate.t("common.health")).toBe("健康状况");
|
||||
});
|
||||
|
||||
it("loads saved non-English locale on startup", async () => {
|
||||
localStorage.setItem("openclaw.i18n.locale", "zh-CN");
|
||||
vi.resetModules();
|
||||
const fresh = await import("../lib/translate.ts?startup-locale");
|
||||
vi.stubGlobal("localStorage", createStorageMock());
|
||||
vi.stubGlobal("navigator", { language: "en-US" } as Navigator);
|
||||
localStorage.setItem("openclaw.i18n.locale", "zh-CN");
|
||||
const fresh = await import("../lib/translate.ts");
|
||||
await vi.waitFor(() => {
|
||||
expect(fresh.i18n.getLocale()).toBe("zh-CN");
|
||||
});
|
||||
@@ -56,8 +93,8 @@ describe("i18n", () => {
|
||||
});
|
||||
|
||||
it("keeps the version label available in shipped locales", () => {
|
||||
expect(pt_BR.common.version).toBeTruthy();
|
||||
expect(zh_CN.common.version).toBeTruthy();
|
||||
expect(zh_TW.common.version).toBeTruthy();
|
||||
expect((pt_BR.common as { version?: string }).version).toBeTruthy();
|
||||
expect((zh_CN.common as { version?: string }).version).toBeTruthy();
|
||||
expect((zh_TW.common as { version?: string }).version).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,7 +124,7 @@ describe("setTabFromRoute", () => {
|
||||
vi.stubGlobal("window", {
|
||||
setInterval,
|
||||
clearInterval,
|
||||
} as Window & typeof globalThis);
|
||||
} as unknown as Window & typeof globalThis);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -203,7 +203,7 @@ describe("setTabFromRoute", () => {
|
||||
setInterval,
|
||||
clearInterval,
|
||||
matchMedia,
|
||||
} as Window & typeof globalThis);
|
||||
} as unknown as Window & typeof globalThis);
|
||||
|
||||
const host = createHost("chat");
|
||||
host.theme = "knot" as unknown as ThemeName & ThemeMode;
|
||||
|
||||
@@ -482,17 +482,58 @@ export type CronStatus = {
|
||||
nextWakeAtMs?: number | null;
|
||||
};
|
||||
|
||||
export type CronJobsEnabledFilter = "all" | "enabled" | "disabled";
|
||||
export type CronJobsSortBy = "nextRunAtMs" | "updatedAtMs" | "name";
|
||||
export type CronSortDir = "asc" | "desc";
|
||||
export type CronRunsStatusFilter = "all" | "ok" | "error" | "skipped";
|
||||
export type CronRunsStatusValue = "ok" | "error" | "skipped";
|
||||
export type CronDeliveryStatus = "delivered" | "not-delivered" | "unknown" | "not-requested";
|
||||
export type CronRunScope = "job" | "all";
|
||||
|
||||
export type CronRunLogEntry = {
|
||||
ts: number;
|
||||
jobId: string;
|
||||
status: "ok" | "error" | "skipped";
|
||||
jobName?: string;
|
||||
status?: CronRunsStatusValue;
|
||||
durationMs?: number;
|
||||
error?: string;
|
||||
summary?: string;
|
||||
deliveryStatus?: CronDeliveryStatus;
|
||||
deliveryError?: string;
|
||||
delivered?: boolean;
|
||||
runAtMs?: number;
|
||||
nextRunAtMs?: number;
|
||||
model?: string;
|
||||
provider?: string;
|
||||
usage?: {
|
||||
input_tokens?: number;
|
||||
output_tokens?: number;
|
||||
total_tokens?: number;
|
||||
cache_read_tokens?: number;
|
||||
cache_write_tokens?: number;
|
||||
};
|
||||
sessionId?: string;
|
||||
sessionKey?: string;
|
||||
};
|
||||
|
||||
export type CronJobsListResult = {
|
||||
jobs?: CronJob[];
|
||||
total?: number;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
hasMore?: boolean;
|
||||
nextOffset?: number | null;
|
||||
};
|
||||
|
||||
export type CronRunsResult = {
|
||||
entries?: CronRunLogEntry[];
|
||||
total?: number;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
hasMore?: boolean;
|
||||
nextOffset?: number | null;
|
||||
};
|
||||
|
||||
export type SkillsStatusConfigCheck = {
|
||||
path: string;
|
||||
satisfied: boolean;
|
||||
|
||||
Reference in New Issue
Block a user