refactor: use dedicated logger in storage.ts

Replaces raw `console.warn` calls with a dedicated `logger.warn` in `src/storage.ts` to improve maintainability and decouple from the global console object. Updates tests in `src/storage.test.ts` to mock the new logger module appropriately.

Co-authored-by: Horizontal42 <75383519+Horizontal42@users.noreply.github.com>
This commit is contained in:
google-labs-jules[bot] 2026-08-12 17:06:01 +00:00
parent b399b95263
commit f1a5eebc65
3 changed files with 34 additions and 24 deletions

6
src/logger.ts Normal file
View file

@ -0,0 +1,6 @@
export const logger = {
info: (...args: unknown[]) => console.info(...args),
warn: (...args: unknown[]) => console.warn(...args),
error: (...args: unknown[]) => console.error(...args),
debug: (...args: unknown[]) => console.debug(...args),
};

View file

@ -1,3 +1,5 @@
import { logger } from "./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();
}); });

View file

@ -1,3 +1,5 @@
import { logger } from "./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);
} }
} }
}); });