mirror of
https://github.com/Horizontal42/summarum.git
synced 2026-11-11 01:02:01 +03:00
Refactor console.warn/error to use custom logger utility
Co-authored-by: Horizontal42 <75383519+Horizontal42@users.noreply.github.com>
This commit is contained in:
parent
b399b95263
commit
a7f9e899d8
9 changed files with 78 additions and 56 deletions
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
// Public facade: evaluates whole documents line by line and exposes the
|
// Public facade: evaluates whole documents line by line and exposes the
|
||||||
// extension API (numi.addUnit / addFunction / setVariable).
|
// extension API (numi.addUnit / addFunction / setVariable).
|
||||||
import { DateOrder, Decimal, EngineSettings, EvalError, Quantity, Unit, Value, XRefError, defaultSettings, qty } from "./types";
|
import { DateOrder, Decimal, EngineSettings, EvalError, Quantity, Unit, Value, XRefError, defaultSettings, qty } from "./types";
|
||||||
|
|
@ -144,7 +146,7 @@ export class SumEngine {
|
||||||
} else {
|
} else {
|
||||||
// Surface unexpected bugs/BigInt errors and log them
|
// Surface unexpected bugs/BigInt errors and log them
|
||||||
lineError = e instanceof Error ? e.message : String(e);
|
lineError = e instanceof Error ? e.message : String(e);
|
||||||
console.warn("evaluate failed:", e);
|
logger.warn("evaluate failed:", e);
|
||||||
}
|
}
|
||||||
value = null;
|
value = null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { logger } from "./utils/logger";
|
||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
import { makeApi, runExtensions } from "./extensions";
|
import { makeApi, runExtensions } from "./extensions";
|
||||||
import { SumEngine } from "./engine";
|
import { SumEngine } from "./engine";
|
||||||
|
|
@ -37,17 +39,17 @@ describe("extensions", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("runExtensions", () => {
|
describe("runExtensions", () => {
|
||||||
let consoleErrorSpy: any;
|
let loggerErrorSpy: any;
|
||||||
let consoleWarnSpy: any;
|
let loggerWarnSpy: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
loggerErrorSpy = vi.spyOn(logger, "error").mockImplementation(() => {});
|
||||||
consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
loggerWarnSpy = vi.spyOn(logger, "warn").mockImplementation(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
consoleErrorSpy.mockRestore();
|
loggerErrorSpy.mockRestore();
|
||||||
consoleWarnSpy.mockRestore();
|
loggerWarnSpy.mockRestore();
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
vi.doUnmock("quickjs-emscripten-core");
|
vi.doUnmock("quickjs-emscripten-core");
|
||||||
});
|
});
|
||||||
|
|
@ -65,14 +67,14 @@ describe("extensions", () => {
|
||||||
baseUnitId: "USD",
|
baseUnitId: "USD",
|
||||||
ratio: 1,
|
ratio: 1,
|
||||||
});
|
});
|
||||||
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
expect(loggerErrorSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("swallows errors and logs to console.error", async () => {
|
it("swallows errors and logs to logger.error", async () => {
|
||||||
await runExtensions(mockEngine as SumEngine, [{ name: "script1", code: "throw new Error('test error');" }]);
|
await runExtensions(mockEngine as SumEngine, [{ name: "script1", code: "throw new Error('test error');" }]);
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith("extension script1 failed:", expect.any(Error));
|
expect(loggerErrorSpy).toHaveBeenCalledWith("extension script1 failed:", expect.any(Error));
|
||||||
expect(consoleErrorSpy.mock.calls[0][1].message).toContain("test error");
|
expect(loggerErrorSpy.mock.calls[0][1].message).toContain("test error");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("continues running other scripts if one fails", async () => {
|
it("continues running other scripts if one fails", async () => {
|
||||||
|
|
@ -84,7 +86,7 @@ describe("extensions", () => {
|
||||||
|
|
||||||
expect(mockEngine.setVariable).toHaveBeenCalledWith("var1", 10);
|
expect(mockEngine.setVariable).toHaveBeenCalledWith("var1", 10);
|
||||||
expect(mockEngine.setVariable).toHaveBeenCalledWith("var3", 30);
|
expect(mockEngine.setVariable).toHaveBeenCalledWith("var3", 30);
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
expect(loggerErrorSpy).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes unit-carrying values through setVariable", async () => {
|
it("passes unit-carrying values through setVariable", async () => {
|
||||||
|
|
@ -99,7 +101,7 @@ describe("extensions", () => {
|
||||||
await runExtensions(mockEngine as SumEngine, [{ name: "bad", code: "numi.setVariable('x', 'nope');" }]);
|
await runExtensions(mockEngine as SumEngine, [{ name: "bad", code: "numi.setVariable('x', 'nope');" }]);
|
||||||
|
|
||||||
expect(mockEngine.setVariable).not.toHaveBeenCalled();
|
expect(mockEngine.setVariable).not.toHaveBeenCalled();
|
||||||
expect(consoleErrorSpy.mock.calls[0][1].message).toContain("numi.setVariable");
|
expect(loggerErrorSpy.mock.calls[0][1].message).toContain("numi.setVariable");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("registers a function callable with one array of values", async () => {
|
it("registers a function callable with one array of values", async () => {
|
||||||
|
|
@ -133,7 +135,7 @@ describe("extensions", () => {
|
||||||
{ name: "probe", code: "if (typeof window !== 'undefined' || typeof fetch !== 'undefined' || typeof console !== 'undefined') throw new Error('leak');" },
|
{ name: "probe", code: "if (typeof window !== 'undefined' || typeof fetch !== 'undefined' || typeof console !== 'undefined') throw new Error('leak');" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
expect(loggerErrorSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("interrupts an infinite loop at load time", async () => {
|
it("interrupts an infinite loop at load time", async () => {
|
||||||
|
|
@ -142,8 +144,8 @@ describe("extensions", () => {
|
||||||
{ name: "after", code: "numi.setVariable('ok', 1);" },
|
{ name: "after", code: "numi.setVariable('ok', 1);" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
expect(loggerErrorSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(consoleErrorSpy.mock.calls[0][0]).toBe("extension spin failed:");
|
expect(loggerErrorSpy.mock.calls[0][0]).toBe("extension spin failed:");
|
||||||
// the runtime survives the interrupt and keeps loading scripts
|
// the runtime survives the interrupt and keeps loading scripts
|
||||||
expect(mockEngine.setVariable).toHaveBeenCalledWith("ok", 1);
|
expect(mockEngine.setVariable).toHaveBeenCalledWith("ok", 1);
|
||||||
});
|
});
|
||||||
|
|
@ -184,12 +186,13 @@ describe("extensions", () => {
|
||||||
vi.doMock("quickjs-emscripten-core", () => ({
|
vi.doMock("quickjs-emscripten-core", () => ({
|
||||||
newQuickJSWASMModuleFromVariant: () => Promise.reject(new Error("no WebAssembly")),
|
newQuickJSWASMModuleFromVariant: () => Promise.reject(new Error("no WebAssembly")),
|
||||||
}));
|
}));
|
||||||
|
vi.doMock("./utils/logger", () => ({ logger: { warn: loggerWarnSpy, error: loggerErrorSpy } }));
|
||||||
const { runExtensions: run } = await import("./extensions");
|
const { runExtensions: run } = await import("./extensions");
|
||||||
|
|
||||||
await run(mockEngine as SumEngine, [{ name: "s", code: "numi.setVariable('x', 1);" }]);
|
await run(mockEngine as SumEngine, [{ name: "s", code: "numi.setVariable('x', 1);" }]);
|
||||||
|
|
||||||
expect(mockEngine.setVariable).not.toHaveBeenCalled();
|
expect(mockEngine.setVariable).not.toHaveBeenCalled();
|
||||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
expect(loggerWarnSpy).toHaveBeenCalledWith(
|
||||||
"extensions disabled: QuickJS sandbox unavailable:",
|
"extensions disabled: QuickJS sandbox unavailable:",
|
||||||
expect.any(Error),
|
expect.any(Error),
|
||||||
);
|
);
|
||||||
|
|
@ -197,8 +200,8 @@ describe("extensions", () => {
|
||||||
|
|
||||||
it("does not load the sandbox when there are no scripts", async () => {
|
it("does not load the sandbox when there are no scripts", async () => {
|
||||||
await runExtensions(mockEngine as SumEngine, []);
|
await runExtensions(mockEngine as SumEngine, []);
|
||||||
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
expect(loggerWarnSpy).not.toHaveBeenCalled();
|
||||||
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
expect(loggerErrorSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { logger } from "./utils/logger";
|
||||||
|
|
||||||
// JS extensions runtime: numi.setVariable, numi.addUnit, numi.addFunction.
|
// JS extensions runtime: numi.setVariable, numi.addUnit, numi.addFunction.
|
||||||
// Scripts run inside a QuickJS WASM sandbox — no DOM, no Tauri IPC, no host globals.
|
// Scripts run inside a QuickJS WASM sandbox — no DOM, no Tauri IPC, no host globals.
|
||||||
import {
|
import {
|
||||||
|
|
@ -75,7 +77,7 @@ export async function runExtensions(
|
||||||
try {
|
try {
|
||||||
await load(engine, scripts);
|
await load(engine, scripts);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("extensions disabled: QuickJS sandbox unavailable:", e);
|
logger.warn("extensions disabled: QuickJS sandbox unavailable:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,7 +202,7 @@ function evaluateScripts(
|
||||||
}
|
}
|
||||||
res.value.dispose();
|
res.value.dispose();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`extension ${s.name} failed:`, e);
|
logger.error(`extension ${s.name} failed:`, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
src/main.ts
10
src/main.ts
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { logger } from "./utils/logger";
|
||||||
|
|
||||||
/** App bootstrap: engine + editor + documents + settings + Tauri glue. */
|
/** App bootstrap: engine + editor + documents + settings + Tauri glue. */
|
||||||
import "./ui/app.css";
|
import "./ui/app.css";
|
||||||
import { SumEngine } from "./engine";
|
import { SumEngine } from "./engine";
|
||||||
|
|
@ -433,7 +435,7 @@ function bindDataDirSettings(): void {
|
||||||
try {
|
try {
|
||||||
await migrateDataDir(settings.dataDir, picked, strategy);
|
await migrateDataDir(settings.dataDir, picked, strategy);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("migrate failed", e);
|
logger.warn("migrate failed", e);
|
||||||
toast(t("folderError"));
|
toast(t("folderError"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -534,7 +536,7 @@ async function registerHotkey(old: string | null, combo: string): Promise<boolea
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("hotkey registration failed", e);
|
logger.warn("hotkey registration failed", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -546,14 +548,14 @@ async function applyAutostart(enabled: boolean): Promise<void> {
|
||||||
if (enabled) await auto.enable();
|
if (enabled) await auto.enable();
|
||||||
else await auto.disable();
|
else await auto.disable();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("autostart failed", e);
|
logger.warn("autostart failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyAlwaysOnTop(enabled: boolean): Promise<void> {
|
async function applyAlwaysOnTop(enabled: boolean): Promise<void> {
|
||||||
if (!isTauri()) return;
|
if (!isTauri()) return;
|
||||||
const { getCurrentWindow } = await import("@tauri-apps/api/window");
|
const { getCurrentWindow } = await import("@tauri-apps/api/window");
|
||||||
await getCurrentWindow().setAlwaysOnTop(enabled).catch((e) => console.warn("always-on-top failed", e));
|
await getCurrentWindow().setAlwaysOnTop(enabled).catch((e) => logger.warn("always-on-top failed", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshRates(force = false): Promise<void> {
|
async function refreshRates(force = false): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { logger } from "./utils/logger";
|
||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
import { isTauri, loadSettings, saveSettings, defaultSettingsData, loadAppData, saveAppData, flushAppData, setDataDir, runBackups, backupDeletedSheet, openBackupsFolder, chooseFolder, dataDirHasDocuments, migrateDataDir, fetchRates, fetchHistoricalRates } from './storage';
|
import { isTauri, loadSettings, saveSettings, defaultSettingsData, loadAppData, saveAppData, flushAppData, setDataDir, runBackups, backupDeletedSheet, openBackupsFolder, chooseFolder, dataDirHasDocuments, migrateDataDir, fetchRates, fetchHistoricalRates } from './storage';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
|
@ -14,7 +16,7 @@ describe('storage', () => {
|
||||||
getItem: vi.fn(),
|
getItem: vi.fn(),
|
||||||
setItem: vi.fn(),
|
setItem: vi.fn(),
|
||||||
});
|
});
|
||||||
vi.stubGlobal('console', { warn: vi.fn() });
|
vi.spyOn(logger, 'warn').mockImplementation(() => {});
|
||||||
vi.stubGlobal('fetch', vi.fn());
|
vi.stubGlobal('fetch', vi.fn());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -54,7 +56,7 @@ describe('storage', () => {
|
||||||
it('falls back to defaultSettingsData if localStorage throws', async () => {
|
it('falls back to defaultSettingsData if localStorage throws', async () => {
|
||||||
vi.mocked(localStorage.getItem).mockImplementation(() => { throw new Error('localStorage error'); });
|
vi.mocked(localStorage.getItem).mockImplementation(() => { throw new Error('localStorage error'); });
|
||||||
const settings = await loadSettings();
|
const settings = await loadSettings();
|
||||||
expect(console.warn).toHaveBeenCalledWith('loadSettings failed', expect.any(Error));
|
expect(logger.warn).toHaveBeenCalledWith('loadSettings failed', expect.any(Error));
|
||||||
expect(settings).toEqual(defaultSettingsData);
|
expect(settings).toEqual(defaultSettingsData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -62,7 +64,7 @@ describe('storage', () => {
|
||||||
vi.stubGlobal('window', { __TAURI_INTERNALS__: {} });
|
vi.stubGlobal('window', { __TAURI_INTERNALS__: {} });
|
||||||
vi.mocked(invoke).mockRejectedValue(new Error('invoke error'));
|
vi.mocked(invoke).mockRejectedValue(new Error('invoke error'));
|
||||||
const settings = await loadSettings();
|
const settings = await loadSettings();
|
||||||
expect(console.warn).toHaveBeenCalledWith('loadSettings failed', expect.any(Error));
|
expect(logger.warn).toHaveBeenCalledWith('loadSettings failed', expect.any(Error));
|
||||||
expect(settings).toEqual(defaultSettingsData);
|
expect(settings).toEqual(defaultSettingsData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -75,7 +77,7 @@ describe('storage', () => {
|
||||||
it('falls back to defaultSettingsData if JSON is invalid', async () => {
|
it('falls back to defaultSettingsData if JSON is invalid', async () => {
|
||||||
vi.mocked(localStorage.getItem).mockReturnValue("invalid json");
|
vi.mocked(localStorage.getItem).mockReturnValue("invalid json");
|
||||||
const settings = await loadSettings();
|
const settings = await loadSettings();
|
||||||
expect(console.warn).toHaveBeenCalledWith('loadSettings failed', expect.any(Error));
|
expect(logger.warn).toHaveBeenCalledWith('loadSettings failed', expect.any(Error));
|
||||||
expect(settings).toEqual(defaultSettingsData);
|
expect(settings).toEqual(defaultSettingsData);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -124,7 +126,7 @@ describe('storage', () => {
|
||||||
it('returns null if localStorage throws', async () => {
|
it('returns null if localStorage throws', async () => {
|
||||||
vi.mocked(localStorage.getItem).mockImplementation(() => { throw new Error('localStorage error'); });
|
vi.mocked(localStorage.getItem).mockImplementation(() => { throw new Error('localStorage error'); });
|
||||||
const data = await loadAppData('');
|
const data = await loadAppData('');
|
||||||
expect(console.warn).toHaveBeenCalledWith('loadAppData failed', expect.any(Error));
|
expect(logger.warn).toHaveBeenCalledWith('loadAppData failed', expect.any(Error));
|
||||||
expect(data).toBeNull();
|
expect(data).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -132,7 +134,7 @@ describe('storage', () => {
|
||||||
vi.stubGlobal('window', { __TAURI_INTERNALS__: {} });
|
vi.stubGlobal('window', { __TAURI_INTERNALS__: {} });
|
||||||
vi.mocked(invoke).mockRejectedValue(new Error('invoke error'));
|
vi.mocked(invoke).mockRejectedValue(new Error('invoke error'));
|
||||||
const data = await loadAppData('');
|
const data = await loadAppData('');
|
||||||
expect(console.warn).toHaveBeenCalledWith('loadAppData failed', expect.any(Error));
|
expect(logger.warn).toHaveBeenCalledWith('loadAppData failed', expect.any(Error));
|
||||||
expect(data).toBeNull();
|
expect(data).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -145,7 +147,7 @@ describe('storage', () => {
|
||||||
it('returns null if JSON is invalid', async () => {
|
it('returns null if JSON is invalid', async () => {
|
||||||
vi.mocked(localStorage.getItem).mockReturnValue("invalid json");
|
vi.mocked(localStorage.getItem).mockReturnValue("invalid json");
|
||||||
const data = await loadAppData('');
|
const data = await loadAppData('');
|
||||||
expect(console.warn).toHaveBeenCalledWith('loadAppData failed', expect.any(Error));
|
expect(logger.warn).toHaveBeenCalledWith('loadAppData failed', expect.any(Error));
|
||||||
expect(data).toBeNull();
|
expect(data).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -223,7 +225,7 @@ describe('storage', () => {
|
||||||
|
|
||||||
await flushAppData();
|
await flushAppData();
|
||||||
|
|
||||||
expect(console.warn).toHaveBeenCalledWith('saveAppData failed', expect.any(Error));
|
expect(logger.warn).toHaveBeenCalledWith('saveAppData failed', expect.any(Error));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles invoke throw gracefully in flushAppData', async () => {
|
it('handles invoke throw gracefully in flushAppData', async () => {
|
||||||
|
|
@ -234,7 +236,7 @@ describe('storage', () => {
|
||||||
|
|
||||||
await flushAppData();
|
await flushAppData();
|
||||||
|
|
||||||
expect(console.warn).toHaveBeenCalledWith('saveAppData failed', expect.any(Error));
|
expect(logger.warn).toHaveBeenCalledWith('saveAppData failed', expect.any(Error));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -255,7 +257,7 @@ describe('storage', () => {
|
||||||
vi.stubGlobal('window', { __TAURI_INTERNALS__: {} });
|
vi.stubGlobal('window', { __TAURI_INTERNALS__: {} });
|
||||||
vi.mocked(invoke).mockRejectedValue(new Error('invoke error'));
|
vi.mocked(invoke).mockRejectedValue(new Error('invoke error'));
|
||||||
await runBackups('dir', 10);
|
await runBackups('dir', 10);
|
||||||
expect(console.warn).toHaveBeenCalledWith('runBackups failed', expect.any(Error));
|
expect(logger.warn).toHaveBeenCalledWith('runBackups failed', expect.any(Error));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -281,7 +283,7 @@ describe('storage', () => {
|
||||||
vi.stubGlobal('window', { __TAURI_INTERNALS__: {} });
|
vi.stubGlobal('window', { __TAURI_INTERNALS__: {} });
|
||||||
vi.mocked(invoke).mockRejectedValue(new Error('invoke error'));
|
vi.mocked(invoke).mockRejectedValue(new Error('invoke error'));
|
||||||
await backupDeletedSheet('dir', 'title', 'contents');
|
await backupDeletedSheet('dir', 'title', 'contents');
|
||||||
expect(console.warn).toHaveBeenCalledWith('backupDeletedSheet failed', expect.any(Error));
|
expect(logger.warn).toHaveBeenCalledWith('backupDeletedSheet failed', expect.any(Error));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -369,7 +371,7 @@ describe('storage', () => {
|
||||||
it('returns null if fetch fails', async () => {
|
it('returns null if fetch fails', async () => {
|
||||||
vi.mocked(fetch).mockRejectedValue(new Error('Network error'));
|
vi.mocked(fetch).mockRejectedValue(new Error('Network error'));
|
||||||
const result = await fetchRates();
|
const result = await fetchRates();
|
||||||
expect(console.warn).toHaveBeenCalledWith('fetchRates failed', expect.any(Error));
|
expect(logger.warn).toHaveBeenCalledWith('fetchRates failed', expect.any(Error));
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -404,7 +406,7 @@ describe('storage', () => {
|
||||||
it('returns null if fetch fails', async () => {
|
it('returns null if fetch fails', async () => {
|
||||||
vi.mocked(fetch).mockRejectedValue(new Error('Network error'));
|
vi.mocked(fetch).mockRejectedValue(new Error('Network error'));
|
||||||
const result = await fetchHistoricalRates('2024-01-01');
|
const result = await fetchHistoricalRates('2024-01-01');
|
||||||
expect(console.warn).toHaveBeenCalledWith('fetchHistoricalRates failed', '2024-01-01', expect.any(Error));
|
expect(logger.warn).toHaveBeenCalledWith('fetchHistoricalRates failed', '2024-01-01', expect.any(Error));
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { logger } from "./utils/logger";
|
||||||
|
|
||||||
// Storage: Tauri commands in the app, localStorage fallback for `vite dev`.
|
// Storage: Tauri commands in the app, localStorage fallback for `vite dev`.
|
||||||
|
|
||||||
export interface DocMeta {
|
export interface DocMeta {
|
||||||
|
|
@ -79,7 +81,7 @@ export async function loadSettings(): Promise<SettingsData> {
|
||||||
: localStorage.getItem("summarum.settings");
|
: localStorage.getItem("summarum.settings");
|
||||||
if (raw) return { ...defaultSettingsData, ...JSON.parse(raw) };
|
if (raw) return { ...defaultSettingsData, ...JSON.parse(raw) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("loadSettings failed", e);
|
logger.warn("loadSettings failed", e);
|
||||||
}
|
}
|
||||||
return { ...defaultSettingsData };
|
return { ...defaultSettingsData };
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +101,7 @@ export async function loadAppData(dataDir: string): Promise<AppData | null> {
|
||||||
: localStorage.getItem("summarum.documents");
|
: localStorage.getItem("summarum.documents");
|
||||||
if (raw) return JSON.parse(raw);
|
if (raw) return JSON.parse(raw);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("loadAppData failed", e);
|
logger.warn("loadAppData failed", e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -132,7 +134,7 @@ export async function flushAppData(): Promise<void> {
|
||||||
if (isTauri()) await invoke("save_documents", { dir: dirArg(currentDataDir), contents: raw });
|
if (isTauri()) await invoke("save_documents", { dir: dirArg(currentDataDir), contents: raw });
|
||||||
else localStorage.setItem("summarum.documents", raw);
|
else localStorage.setItem("summarum.documents", raw);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("saveAppData failed", e);
|
logger.warn("saveAppData failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,7 +152,7 @@ export async function runBackups(dataDir: string, retentionDays: number): Promis
|
||||||
try {
|
try {
|
||||||
await invoke("run_backups", { dir: dirArg(dataDir), retentionDays });
|
await invoke("run_backups", { dir: dirArg(dataDir), retentionDays });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("runBackups failed", e);
|
logger.warn("runBackups failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,7 +161,7 @@ export async function backupDeletedSheet(dataDir: string, title: string, content
|
||||||
try {
|
try {
|
||||||
await invoke("backup_deleted_sheet", { dir: dirArg(dataDir), title, contents });
|
await invoke("backup_deleted_sheet", { dir: dirArg(dataDir), title, contents });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("backupDeletedSheet failed", e);
|
logger.warn("backupDeletedSheet failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,7 +207,7 @@ export async function fetchRates(force = false): Promise<RatesPayload | null> {
|
||||||
return { date: json.time_last_update_utc, rates: json.rates, fetchedAt: Math.floor(Date.now() / 1000) };
|
return { date: json.time_last_update_utc, rates: json.rates, fetchedAt: Math.floor(Date.now() / 1000) };
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("fetchRates failed", e);
|
logger.warn("fetchRates failed", e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -227,7 +229,7 @@ export async function fetchHistoricalRatesBatch(dates: string[]): Promise<Record
|
||||||
);
|
);
|
||||||
return results;
|
return results;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("fetchHistoricalRatesBatch failed", e);
|
logger.warn("fetchHistoricalRatesBatch failed", e);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -246,7 +248,7 @@ export async function fetchHistoricalRates(date: string): Promise<Record<string,
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("fetchHistoricalRates failed", date, e);
|
logger.warn("fetchHistoricalRates failed", date, e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -265,7 +267,7 @@ export async function fetchMarketData(symbols: string[]): Promise<Record<string,
|
||||||
try {
|
try {
|
||||||
return await invoke<Record<string, number>>("fetch_market_data", { symbols });
|
return await invoke<Record<string, number>>("fetch_market_data", { symbols });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("fetchMarketData failed", e);
|
logger.warn("fetchMarketData failed", e);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -277,7 +279,7 @@ export async function loadExtensionScripts(): Promise<{ name: string; code: stri
|
||||||
try {
|
try {
|
||||||
return await invoke<{ name: string; code: string }[]>("load_extensions");
|
return await invoke<{ name: string; code: string }[]>("load_extensions");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("loadExtensions failed", e);
|
logger.warn("loadExtensions failed", e);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -314,7 +316,7 @@ export async function onFileDrop(cb: (content: string) => void): Promise<void> {
|
||||||
const content = await invoke<string>("read_text_file", { path });
|
const content = await invoke<string>("read_text_file", { path });
|
||||||
cb(content);
|
cb(content);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("file drop rejected", path, e);
|
logger.warn("file drop rejected", path, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { logger } from "./utils/logger";
|
||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
import { checkForUpdate } from './updater';
|
import { checkForUpdate } from './updater';
|
||||||
import * as storage from './storage';
|
import * as storage from './storage';
|
||||||
|
|
@ -22,8 +24,8 @@ vi.mock('@tauri-apps/plugin-process', () => ({
|
||||||
describe('checkForUpdate', () => {
|
describe('checkForUpdate', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.resetAllMocks();
|
vi.resetAllMocks();
|
||||||
vi.spyOn(console, 'info').mockImplementation(() => {});
|
vi.spyOn(logger, 'info').mockImplementation(() => {});
|
||||||
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
vi.spyOn(logger, 'warn').mockImplementation(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if not in Tauri', async () => {
|
it('should return null if not in Tauri', async () => {
|
||||||
|
|
@ -39,7 +41,7 @@ describe('checkForUpdate', () => {
|
||||||
|
|
||||||
const result = await checkForUpdate();
|
const result = await checkForUpdate();
|
||||||
expect(result).toBe('error');
|
expect(result).toBe('error');
|
||||||
expect(console.warn).toHaveBeenCalledWith('update check failed', expect.any(Error));
|
expect(logger.warn).toHaveBeenCalledWith('update check failed', expect.any(Error));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if no update is available', async () => {
|
it('should return null if no update is available', async () => {
|
||||||
|
|
@ -52,7 +54,7 @@ describe('checkForUpdate', () => {
|
||||||
|
|
||||||
const result = await checkForUpdate();
|
const result = await checkForUpdate();
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
expect(console.info).toHaveBeenCalledWith("no update: running 1.0.0, that's the latest published release");
|
expect(logger.info).toHaveBeenCalledWith("no update: running 1.0.0, that's the latest published release");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an update object if an update is available', async () => {
|
it('should return an update object if an update is available', async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { logger } from "./utils/logger";
|
||||||
|
|
||||||
import { isTauri } from "./storage";
|
import { isTauri } from "./storage";
|
||||||
|
|
||||||
interface AvailableUpdate {
|
interface AvailableUpdate {
|
||||||
|
|
@ -19,10 +21,10 @@ export async function checkForUpdate(): Promise<AvailableUpdate | null | "error"
|
||||||
// visible in devtools console — the fastest way to tell "already on
|
// visible in devtools console — the fastest way to tell "already on
|
||||||
// the latest published release" apart from "the check silently failed"
|
// the latest published release" apart from "the check silently failed"
|
||||||
const { getVersion } = await import("@tauri-apps/api/app");
|
const { getVersion } = await import("@tauri-apps/api/app");
|
||||||
console.info(`no update: running ${await getVersion()}, that's the latest published release`);
|
logger.info(`no update: running ${await getVersion()}, that's the latest published release`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
console.info(`update available: ${update.currentVersion} -> ${update.version}`);
|
logger.info(`update available: ${update.currentVersion} -> ${update.version}`);
|
||||||
return {
|
return {
|
||||||
version: update.version,
|
version: update.version,
|
||||||
async install() {
|
async install() {
|
||||||
|
|
@ -32,7 +34,7 @@ export async function checkForUpdate(): Promise<AvailableUpdate | null | "error"
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("update check failed", e);
|
logger.warn("update check failed", e);
|
||||||
return "error";
|
return "error";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
src/utils/logger.ts
Normal file
5
src/utils/logger.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export const logger = {
|
||||||
|
info: (...args: unknown[]) => console.info(...args),
|
||||||
|
warn: (...args: unknown[]) => console.warn(...args),
|
||||||
|
error: (...args: unknown[]) => console.error(...args),
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue