test: silence vitest warning noise
This commit is contained in:
parent
522dda1971
commit
0218045818
@ -1,78 +1,104 @@
|
||||
import { Mock, vi } from "vitest";
|
||||
import { vi, type Mock } from "vitest";
|
||||
|
||||
export const messageCommand: Mock<(...args: unknown[]) => unknown> = vi.fn();
|
||||
export const statusCommand: Mock<(...args: unknown[]) => unknown> = vi.fn();
|
||||
export const configureCommand: Mock<(...args: unknown[]) => unknown> = vi.fn();
|
||||
export const configureCommandWithSections: Mock<(...args: unknown[]) => unknown> = vi.fn();
|
||||
export const setupCommand: Mock<(...args: unknown[]) => unknown> = vi.fn();
|
||||
export const onboardCommand: Mock<(...args: unknown[]) => unknown> = vi.fn();
|
||||
export const callGateway: Mock<(...args: unknown[]) => unknown> = vi.fn();
|
||||
export const runChannelLogin: Mock<(...args: unknown[]) => unknown> = vi.fn();
|
||||
export const runChannelLogout: Mock<(...args: unknown[]) => unknown> = vi.fn();
|
||||
export const runTui: Mock<(...args: unknown[]) => unknown> = vi.fn();
|
||||
type AnyMock = Mock<(...args: unknown[]) => unknown>;
|
||||
|
||||
export const loadAndMaybeMigrateDoctorConfig: Mock<(...args: unknown[]) => unknown> = vi.fn();
|
||||
export const ensureConfigReady: Mock<(...args: unknown[]) => unknown> = vi.fn();
|
||||
export const ensurePluginRegistryLoaded: Mock<(...args: unknown[]) => unknown> = vi.fn();
|
||||
const programMocks = vi.hoisted(() => ({
|
||||
messageCommand: vi.fn(),
|
||||
statusCommand: vi.fn(),
|
||||
configureCommand: vi.fn(),
|
||||
configureCommandWithSections: vi.fn(),
|
||||
setupCommand: vi.fn(),
|
||||
onboardCommand: vi.fn(),
|
||||
callGateway: vi.fn(),
|
||||
runChannelLogin: vi.fn(),
|
||||
runChannelLogout: vi.fn(),
|
||||
runTui: vi.fn(),
|
||||
loadAndMaybeMigrateDoctorConfig: vi.fn(),
|
||||
ensureConfigReady: vi.fn(),
|
||||
ensurePluginRegistryLoaded: vi.fn(),
|
||||
runtime: {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn(() => {
|
||||
throw new Error("exit");
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
export const runtime: {
|
||||
export const messageCommand = programMocks.messageCommand as AnyMock;
|
||||
export const statusCommand = programMocks.statusCommand as AnyMock;
|
||||
export const configureCommand = programMocks.configureCommand as AnyMock;
|
||||
export const configureCommandWithSections = programMocks.configureCommandWithSections as AnyMock;
|
||||
export const setupCommand = programMocks.setupCommand as AnyMock;
|
||||
export const onboardCommand = programMocks.onboardCommand as AnyMock;
|
||||
export const callGateway = programMocks.callGateway as AnyMock;
|
||||
export const runChannelLogin = programMocks.runChannelLogin as AnyMock;
|
||||
export const runChannelLogout = programMocks.runChannelLogout as AnyMock;
|
||||
export const runTui = programMocks.runTui as AnyMock;
|
||||
export const loadAndMaybeMigrateDoctorConfig =
|
||||
programMocks.loadAndMaybeMigrateDoctorConfig as AnyMock;
|
||||
export const ensureConfigReady = programMocks.ensureConfigReady as AnyMock;
|
||||
export const ensurePluginRegistryLoaded = programMocks.ensurePluginRegistryLoaded as AnyMock;
|
||||
|
||||
export const runtime = programMocks.runtime as {
|
||||
log: Mock<(...args: unknown[]) => void>;
|
||||
error: Mock<(...args: unknown[]) => void>;
|
||||
exit: Mock<(...args: unknown[]) => never>;
|
||||
} = {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn(() => {
|
||||
throw new Error("exit");
|
||||
}),
|
||||
};
|
||||
|
||||
export function installBaseProgramMocks() {
|
||||
vi.mock("../commands/message.js", () => ({ messageCommand }));
|
||||
vi.mock("../commands/status.js", () => ({ statusCommand }));
|
||||
vi.mock("../commands/configure.js", () => ({
|
||||
CONFIGURE_WIZARD_SECTIONS: [
|
||||
"workspace",
|
||||
"model",
|
||||
"web",
|
||||
"gateway",
|
||||
"daemon",
|
||||
"channels",
|
||||
"skills",
|
||||
"health",
|
||||
],
|
||||
configureCommand,
|
||||
configureCommandWithSections,
|
||||
configureCommandFromSectionsArg: (sections: unknown, runtime: unknown) => {
|
||||
const resolved = Array.isArray(sections) ? sections : [];
|
||||
if (resolved.length > 0) {
|
||||
return configureCommandWithSections(resolved, runtime);
|
||||
}
|
||||
return configureCommand({}, runtime);
|
||||
},
|
||||
}));
|
||||
vi.mock("../commands/setup.js", () => ({ setupCommand }));
|
||||
vi.mock("../commands/onboard.js", () => ({ onboardCommand }));
|
||||
vi.mock("../runtime.js", () => ({ defaultRuntime: runtime }));
|
||||
vi.mock("./channel-auth.js", () => ({ runChannelLogin, runChannelLogout }));
|
||||
vi.mock("../tui/tui.js", () => ({ runTui }));
|
||||
vi.mock("../gateway/call.js", () => ({
|
||||
callGateway,
|
||||
randomIdempotencyKey: () => "idem-test",
|
||||
buildGatewayConnectionDetails: () => ({
|
||||
url: "ws://127.0.0.1:1234",
|
||||
urlSource: "test",
|
||||
message: "Gateway target: ws://127.0.0.1:1234",
|
||||
}),
|
||||
}));
|
||||
vi.mock("./deps.js", () => ({ createDefaultDeps: () => ({}) }));
|
||||
}
|
||||
// Keep these mocks at top level so Vitest does not warn about hoisted nested mocks.
|
||||
vi.mock("../commands/message.js", () => ({ messageCommand: programMocks.messageCommand }));
|
||||
vi.mock("../commands/status.js", () => ({ statusCommand: programMocks.statusCommand }));
|
||||
vi.mock("../commands/configure.js", () => ({
|
||||
CONFIGURE_WIZARD_SECTIONS: [
|
||||
"workspace",
|
||||
"model",
|
||||
"web",
|
||||
"gateway",
|
||||
"daemon",
|
||||
"channels",
|
||||
"skills",
|
||||
"health",
|
||||
],
|
||||
configureCommand: programMocks.configureCommand,
|
||||
configureCommandWithSections: programMocks.configureCommandWithSections,
|
||||
configureCommandFromSectionsArg: (sections: unknown, runtime: unknown) => {
|
||||
const resolved = Array.isArray(sections) ? sections : [];
|
||||
if (resolved.length > 0) {
|
||||
return programMocks.configureCommandWithSections(resolved, runtime);
|
||||
}
|
||||
return programMocks.configureCommand({}, runtime);
|
||||
},
|
||||
}));
|
||||
vi.mock("../commands/setup.js", () => ({ setupCommand: programMocks.setupCommand }));
|
||||
vi.mock("../commands/onboard.js", () => ({ onboardCommand: programMocks.onboardCommand }));
|
||||
vi.mock("../runtime.js", () => ({ defaultRuntime: programMocks.runtime }));
|
||||
vi.mock("./channel-auth.js", () => ({
|
||||
runChannelLogin: programMocks.runChannelLogin,
|
||||
runChannelLogout: programMocks.runChannelLogout,
|
||||
}));
|
||||
vi.mock("../tui/tui.js", () => ({ runTui: programMocks.runTui }));
|
||||
vi.mock("../gateway/call.js", () => ({
|
||||
callGateway: programMocks.callGateway,
|
||||
randomIdempotencyKey: () => "idem-test",
|
||||
buildGatewayConnectionDetails: () => ({
|
||||
url: "ws://127.0.0.1:1234",
|
||||
urlSource: "test",
|
||||
message: "Gateway target: ws://127.0.0.1:1234",
|
||||
}),
|
||||
}));
|
||||
vi.mock("./deps.js", () => ({ createDefaultDeps: () => ({}) }));
|
||||
vi.mock("./plugin-registry.js", () => ({
|
||||
ensurePluginRegistryLoaded: programMocks.ensurePluginRegistryLoaded,
|
||||
}));
|
||||
vi.mock("../commands/doctor-config-flow.js", () => ({
|
||||
loadAndMaybeMigrateDoctorConfig: programMocks.loadAndMaybeMigrateDoctorConfig,
|
||||
}));
|
||||
vi.mock("./program/config-guard.js", () => ({
|
||||
ensureConfigReady: programMocks.ensureConfigReady,
|
||||
}));
|
||||
vi.mock("./preaction.js", () => ({ registerPreActionHooks: () => {} }));
|
||||
|
||||
export function installSmokeProgramMocks() {
|
||||
vi.mock("./plugin-registry.js", () => ({ ensurePluginRegistryLoaded }));
|
||||
vi.mock("../commands/doctor-config-flow.js", () => ({
|
||||
loadAndMaybeMigrateDoctorConfig,
|
||||
}));
|
||||
vi.mock("./program/config-guard.js", () => ({ ensureConfigReady }));
|
||||
vi.mock("./preaction.js", () => ({ registerPreActionHooks: () => {} }));
|
||||
}
|
||||
export function installBaseProgramMocks() {}
|
||||
|
||||
export function installSmokeProgramMocks() {}
|
||||
|
||||
@ -74,6 +74,7 @@ describe("warning filter", () => {
|
||||
|
||||
it("installs once and suppresses known warnings at emit time", async () => {
|
||||
const seenWarnings: Array<{ code?: string; name: string; message: string }> = [];
|
||||
const stderrWrites: string[] = [];
|
||||
const onWarning = (warning: Error & { code?: string }) => {
|
||||
seenWarnings.push({
|
||||
code: warning.code,
|
||||
@ -81,6 +82,12 @@ describe("warning filter", () => {
|
||||
message: warning.message,
|
||||
});
|
||||
};
|
||||
const stderrWriteSpy = vi.spyOn(process.stderr, "write").mockImplementation(((
|
||||
chunk: string | Uint8Array,
|
||||
) => {
|
||||
stderrWrites.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
|
||||
return true;
|
||||
}) as typeof process.stderr.write);
|
||||
|
||||
process.on("warning", onWarning);
|
||||
try {
|
||||
@ -135,7 +142,9 @@ describe("warning filter", () => {
|
||||
warning.code === "DEP0040" && warning.message === "The punycode module is deprecated.",
|
||||
),
|
||||
).toBeDefined();
|
||||
expect(stderrWrites.join("")).toContain("Visible warning");
|
||||
} finally {
|
||||
stderrWriteSpy.mockRestore();
|
||||
process.off("warning", onWarning);
|
||||
}
|
||||
});
|
||||
|
||||
@ -7,13 +7,13 @@ import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withEnv } from "../test-utils/env.js";
|
||||
async function importFreshPluginTestModules() {
|
||||
vi.resetModules();
|
||||
vi.unmock("node:fs");
|
||||
vi.unmock("node:fs/promises");
|
||||
vi.unmock("node:module");
|
||||
vi.unmock("./hook-runner-global.js");
|
||||
vi.unmock("./hooks.js");
|
||||
vi.unmock("./loader.js");
|
||||
vi.unmock("jiti");
|
||||
vi.doUnmock("node:fs");
|
||||
vi.doUnmock("node:fs/promises");
|
||||
vi.doUnmock("node:module");
|
||||
vi.doUnmock("./hook-runner-global.js");
|
||||
vi.doUnmock("./hooks.js");
|
||||
vi.doUnmock("./loader.js");
|
||||
vi.doUnmock("jiti");
|
||||
const [loader, hookRunnerGlobal, hooks, runtime, registry] = await Promise.all([
|
||||
import("./loader.js"),
|
||||
import("./hook-runner-global.js"),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { getSafeLocalStorage } from "../../local-storage.ts";
|
||||
import { en } from "../locales/en.ts";
|
||||
import {
|
||||
DEFAULT_LOCALE,
|
||||
@ -22,8 +23,8 @@ class I18nManager {
|
||||
}
|
||||
|
||||
private readStoredLocale(): string | null {
|
||||
const storage = globalThis.localStorage;
|
||||
if (!storage || typeof storage.getItem !== "function") {
|
||||
const storage = getSafeLocalStorage();
|
||||
if (!storage) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
@ -34,8 +35,8 @@ class I18nManager {
|
||||
}
|
||||
|
||||
private persistLocale(locale: Locale) {
|
||||
const storage = globalThis.localStorage;
|
||||
if (!storage || typeof storage.setItem !== "function") {
|
||||
const storage = getSafeLocalStorage();
|
||||
if (!storage) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
||||
@ -92,6 +92,22 @@ describe("i18n", () => {
|
||||
expect(fresh.t("common.health")).toBe("健康状况");
|
||||
});
|
||||
|
||||
it("skips node localStorage accessors that warn without a storage file", async () => {
|
||||
vi.resetModules();
|
||||
vi.unstubAllGlobals();
|
||||
vi.stubGlobal("navigator", { language: "en-US" } as Navigator);
|
||||
const warningSpy = vi.spyOn(process, "emitWarning");
|
||||
|
||||
const fresh = await import("../lib/translate.ts");
|
||||
|
||||
expect(fresh.i18n.getLocale()).toBe("en");
|
||||
expect(warningSpy).not.toHaveBeenCalledWith(
|
||||
"`--localstorage-file` was provided without a valid path",
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps the version label available in shipped locales", () => {
|
||||
expect((pt_BR.common as { version?: string }).version).toBeTruthy();
|
||||
expect((zh_CN.common as { version?: string }).version).toBeTruthy();
|
||||
|
||||
25
ui/src/local-storage.ts
Normal file
25
ui/src/local-storage.ts
Normal file
@ -0,0 +1,25 @@
|
||||
function isStorage(value: unknown): value is Storage {
|
||||
return (
|
||||
Boolean(value) &&
|
||||
typeof (value as Storage).getItem === "function" &&
|
||||
typeof (value as Storage).setItem === "function"
|
||||
);
|
||||
}
|
||||
|
||||
export function getSafeLocalStorage(): Storage | null {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(globalThis, "localStorage");
|
||||
|
||||
if (process.env.VITEST) {
|
||||
return descriptor && !descriptor.get && isStorage(descriptor.value) ? descriptor.value : null;
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
||||
try {
|
||||
return isStorage(window.localStorage) ? window.localStorage : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return descriptor && !descriptor.get && isStorage(descriptor.value) ? descriptor.value : null;
|
||||
}
|
||||
@ -4,6 +4,7 @@ import {
|
||||
parseAgentSessionKey,
|
||||
} from "../../../src/routing/session-key.js";
|
||||
import { t } from "../i18n/index.ts";
|
||||
import { getSafeLocalStorage } from "../local-storage.ts";
|
||||
import { refreshChatAvatar } from "./app-chat.ts";
|
||||
import { renderUsageTab } from "./app-render-usage-tab.ts";
|
||||
import {
|
||||
@ -181,7 +182,7 @@ type DismissedUpdateBanner = {
|
||||
|
||||
function loadDismissedUpdateBanner(): DismissedUpdateBanner | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(UPDATE_BANNER_DISMISS_KEY);
|
||||
const raw = getSafeLocalStorage()?.getItem(UPDATE_BANNER_DISMISS_KEY);
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
@ -225,7 +226,7 @@ function dismissUpdateBanner(updateAvailable: unknown) {
|
||||
dismissedAtMs: Date.now(),
|
||||
};
|
||||
try {
|
||||
localStorage.setItem(UPDATE_BANNER_DISMISS_KEY, JSON.stringify(payload));
|
||||
getSafeLocalStorage()?.setItem(UPDATE_BANNER_DISMISS_KEY, JSON.stringify(payload));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { getSafeLocalStorage } from "../../local-storage.ts";
|
||||
|
||||
const PREFIX = "openclaw:deleted:";
|
||||
|
||||
export class DeletedMessages {
|
||||
@ -30,7 +32,7 @@ export class DeletedMessages {
|
||||
|
||||
private load(): void {
|
||||
try {
|
||||
const raw = localStorage.getItem(this.key);
|
||||
const raw = getSafeLocalStorage()?.getItem(this.key);
|
||||
if (!raw) {
|
||||
return;
|
||||
}
|
||||
@ -45,7 +47,7 @@ export class DeletedMessages {
|
||||
|
||||
private save(): void {
|
||||
try {
|
||||
localStorage.setItem(this.key, JSON.stringify([...this._keys]));
|
||||
getSafeLocalStorage()?.setItem(this.key, JSON.stringify([...this._keys]));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { html, nothing } from "lit";
|
||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
||||
import { getSafeLocalStorage } from "../../local-storage.ts";
|
||||
import type { AssistantIdentity } from "../assistant-identity.ts";
|
||||
import { icons } from "../icons.ts";
|
||||
import { toSanitizedMarkdownHtml } from "../markdown.ts";
|
||||
@ -322,7 +323,7 @@ type DeleteConfirmSide = "left" | "right";
|
||||
|
||||
function shouldSkipDeleteConfirm(): boolean {
|
||||
try {
|
||||
return localStorage.getItem(SKIP_DELETE_CONFIRM_KEY) === "1";
|
||||
return getSafeLocalStorage()?.getItem(SKIP_DELETE_CONFIRM_KEY) === "1";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
@ -370,7 +371,7 @@ function renderDeleteButton(onDelete: () => void, side: DeleteConfirmSide) {
|
||||
yes.addEventListener("click", () => {
|
||||
if (check.checked) {
|
||||
try {
|
||||
localStorage.setItem(SKIP_DELETE_CONFIRM_KEY, "1");
|
||||
getSafeLocalStorage()?.setItem(SKIP_DELETE_CONFIRM_KEY, "1");
|
||||
} catch {}
|
||||
}
|
||||
popover.remove();
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { getSafeLocalStorage } from "../../local-storage.ts";
|
||||
|
||||
const PREFIX = "openclaw:pinned:";
|
||||
|
||||
export class PinnedMessages {
|
||||
@ -42,7 +44,7 @@ export class PinnedMessages {
|
||||
|
||||
private load(): void {
|
||||
try {
|
||||
const raw = localStorage.getItem(this.key);
|
||||
const raw = getSafeLocalStorage()?.getItem(this.key);
|
||||
if (!raw) {
|
||||
return;
|
||||
}
|
||||
@ -57,7 +59,7 @@ export class PinnedMessages {
|
||||
|
||||
private save(): void {
|
||||
try {
|
||||
localStorage.setItem(this.key, JSON.stringify([...this._indices]));
|
||||
getSafeLocalStorage()?.setItem(this.key, JSON.stringify([...this._indices]));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { getSafeLocalStorage } from "../../local-storage.ts";
|
||||
import type { GatewayBrowserClient } from "../gateway.ts";
|
||||
import type { SessionsUsageResult, CostUsageSummary, SessionUsageTimeSeries } from "../types.ts";
|
||||
import type { SessionLogEntry } from "../views/usage.ts";
|
||||
@ -39,14 +40,7 @@ const LEGACY_USAGE_DATE_PARAMS_INVALID_RE = /invalid sessions\.usage params/i;
|
||||
let legacyUsageDateParamsCache: Set<string> | null = null;
|
||||
|
||||
function getLocalStorage(): Storage | null {
|
||||
// Support browser runtime and node tests (when localStorage is stubbed globally).
|
||||
if (typeof window !== "undefined" && window.localStorage) {
|
||||
return window.localStorage;
|
||||
}
|
||||
if (typeof localStorage !== "undefined") {
|
||||
return localStorage;
|
||||
}
|
||||
return null;
|
||||
return getSafeLocalStorage();
|
||||
}
|
||||
|
||||
function loadLegacyUsageDateParamsCache(): Set<string> {
|
||||
|
||||
@ -5,12 +5,13 @@ import {
|
||||
storeDeviceAuthTokenInStore,
|
||||
} from "../../../src/shared/device-auth-store.js";
|
||||
import type { DeviceAuthStore } from "../../../src/shared/device-auth.js";
|
||||
import { getSafeLocalStorage } from "../local-storage.ts";
|
||||
|
||||
const STORAGE_KEY = "openclaw.device.auth.v1";
|
||||
|
||||
function readStore(): DeviceAuthStore | null {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(STORAGE_KEY);
|
||||
const raw = getSafeLocalStorage()?.getItem(STORAGE_KEY);
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
@ -32,7 +33,7 @@ function readStore(): DeviceAuthStore | null {
|
||||
|
||||
function writeStore(store: DeviceAuthStore) {
|
||||
try {
|
||||
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(store));
|
||||
getSafeLocalStorage()?.setItem(STORAGE_KEY, JSON.stringify(store));
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { getPublicKeyAsync, signAsync, utils } from "@noble/ed25519";
|
||||
import { getSafeLocalStorage } from "../local-storage.ts";
|
||||
|
||||
type StoredIdentity = {
|
||||
version: 1;
|
||||
@ -58,8 +59,9 @@ async function generateIdentity(): Promise<DeviceIdentity> {
|
||||
}
|
||||
|
||||
export async function loadOrCreateDeviceIdentity(): Promise<DeviceIdentity> {
|
||||
const storage = getSafeLocalStorage();
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
const raw = storage?.getItem(STORAGE_KEY);
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw) as StoredIdentity;
|
||||
if (
|
||||
@ -74,7 +76,7 @@ export async function loadOrCreateDeviceIdentity(): Promise<DeviceIdentity> {
|
||||
...parsed,
|
||||
deviceId: derivedId,
|
||||
};
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
|
||||
storage?.setItem(STORAGE_KEY, JSON.stringify(updated));
|
||||
return {
|
||||
deviceId: derivedId,
|
||||
publicKey: parsed.publicKey,
|
||||
@ -100,7 +102,7 @@ export async function loadOrCreateDeviceIdentity(): Promise<DeviceIdentity> {
|
||||
privateKey: identity.privateKey,
|
||||
createdAtMs: Date.now(),
|
||||
};
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
|
||||
storage?.setItem(STORAGE_KEY, JSON.stringify(stored));
|
||||
return identity;
|
||||
}
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ type PersistedUiSettings = Omit<UiSettings, "token" | "sessionKey" | "lastActive
|
||||
};
|
||||
|
||||
import { isSupportedLocale } from "../i18n/index.ts";
|
||||
import { getSafeLocalStorage } from "../local-storage.ts";
|
||||
import { inferBasePathFromPathname, normalizeBasePath } from "./navigation.ts";
|
||||
import { parseThemeSelection, type ThemeMode, type ThemeName } from "./theme.ts";
|
||||
|
||||
@ -168,6 +169,7 @@ function persistSessionToken(gatewayUrl: string, token: string) {
|
||||
|
||||
export function loadSettings(): UiSettings {
|
||||
const { pageUrl: pageDerivedUrl, effectiveUrl: defaultUrl } = deriveDefaultGatewayUrl();
|
||||
const storage = getSafeLocalStorage();
|
||||
|
||||
const defaults: UiSettings = {
|
||||
gatewayUrl: defaultUrl,
|
||||
@ -186,7 +188,7 @@ export function loadSettings(): UiSettings {
|
||||
};
|
||||
|
||||
try {
|
||||
const raw = localStorage.getItem(KEY);
|
||||
const raw = storage?.getItem(KEY);
|
||||
if (!raw) {
|
||||
return defaults;
|
||||
}
|
||||
@ -252,10 +254,11 @@ export function saveSettings(next: UiSettings) {
|
||||
|
||||
function persistSettings(next: UiSettings) {
|
||||
persistSessionToken(next.gatewayUrl, next.token);
|
||||
const storage = getSafeLocalStorage();
|
||||
const scope = normalizeGatewayTokenScope(next.gatewayUrl);
|
||||
let existingSessionsByGateway: Record<string, ScopedSessionSelection> = {};
|
||||
try {
|
||||
const raw = localStorage.getItem(KEY);
|
||||
const raw = storage?.getItem(KEY);
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw) as PersistedUiSettings;
|
||||
if (parsed.sessionsByGateway && typeof parsed.sessionsByGateway === "object") {
|
||||
@ -291,5 +294,5 @@ function persistSettings(next: UiSettings) {
|
||||
sessionsByGateway,
|
||||
...(next.locale ? { locale: next.locale } : {}),
|
||||
};
|
||||
localStorage.setItem(KEY, JSON.stringify(persisted));
|
||||
storage?.setItem(KEY, JSON.stringify(persisted));
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import { render } from "lit";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { getSafeLocalStorage } from "../../local-storage.ts";
|
||||
import { renderChatSessionSelect } from "../app-render.helpers.ts";
|
||||
import type { AppViewState } from "../app-view-state.ts";
|
||||
import type { GatewayBrowserClient } from "../gateway.ts";
|
||||
@ -482,7 +483,7 @@ describe("chat view", () => {
|
||||
|
||||
it("opens delete confirm on the left for user messages", () => {
|
||||
try {
|
||||
localStorage.removeItem("openclaw:skipDeleteConfirm");
|
||||
getSafeLocalStorage()?.removeItem("openclaw:skipDeleteConfirm");
|
||||
} catch {
|
||||
/* noop */
|
||||
}
|
||||
@ -515,7 +516,7 @@ describe("chat view", () => {
|
||||
|
||||
it("opens delete confirm on the right for assistant messages", () => {
|
||||
try {
|
||||
localStorage.removeItem("openclaw:skipDeleteConfirm");
|
||||
getSafeLocalStorage()?.removeItem("openclaw:skipDeleteConfirm");
|
||||
} catch {
|
||||
/* noop */
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user