import { describe, expect, it, vi } from "vitest"; import { createLogger, parseLoggingOverride } from "../src/index.js"; import type { LogEvent, LogSink } from "../src/index.js"; function sink(): { sink: LogSink; events: LogEvent[] } { const events: LogEvent[] = []; return { events, sink: { emit: (event) => events.push(event) } }; } describe("parseLoggingOverride", () => { it("accepts a global level and a namespace-filtered level", () => { expect(parseLoggingOverride("debug")).toEqual({ level: "debug", namespaces: [] }); expect(parseLoggingOverride("debug:WIDGET, API")).toEqual({ level: "debug", namespaces: ["WIDGET", "API"], }); expect(parseLoggingOverride("verbose")).toBeUndefined(); }); }); describe("createLogger", () => { it("uses environment defaults and never evaluates suppressed lazy data", () => { const destination = sink(); const expensive = vi.fn(() => ({ huge: "snapshot" })); const logger = createLogger({ environment: "production", sinks: [destination.sink] }); logger.debug("ignored", expensive); logger.error("kept", expensive); expect(expensive).toHaveBeenCalledTimes(1); expect(destination.events).toHaveLength(1); expect(destination.events[0]?.arguments).toEqual(["kept", { huge: "snapshot" }]); }); it("applies session namespace filtering to namespace descendants", () => { const destination = sink(); const storage = { getItem: () => "debug:WIDGET" }; createLogger({ environment: "production", namespace: "WIDGET:Button", sessionStorage: storage, sinks: [destination.sink], }).debug("shown"); createLogger({ environment: "production", namespace: "API", sessionStorage: storage, sinks: [destination.sink], }).debug("hidden"); expect(destination.events.map((event) => event.arguments[0])).toEqual(["shown"]); }); it("builds immutable child namespaces and reports failed assertions as errors", () => { const destination = sink(); const root = createLogger({ environment: "development", namespace: "WIDGET", sinks: [destination.sink], }); root.child("Button").assert(false, "missing label"); expect(root.namespace).toBe("WIDGET"); expect(destination.events[0]).toMatchObject({ level: "error", namespace: "WIDGET:Button", arguments: ["Assertion failed", "missing label"], }); }); it("sends production browser errors to Sentry without writing to the console", () => { const destination = sink(); const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined); const logger = createLogger({ environment: "production", runtime: "browser", sentry: destination.sink, }); logger.error("captured"); expect(destination.events).toHaveLength(1); expect(consoleError).not.toHaveBeenCalled(); consoleError.mockRestore(); }); it("keeps production Node errors on stderr as well as sending them to Sentry", () => { const destination = sink(); const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined); const logger = createLogger({ environment: "production", runtime: "node", sentry: destination.sink, }); logger.error("captured"); expect(destination.events).toHaveLength(1); expect(consoleError).toHaveBeenCalledTimes(1); consoleError.mockRestore(); }); });