Files
stack/apps/web/src/lib/hooks/useWorkspaceId.test.ts
Jason Woltje 12fa093f58
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
fix(SEC-WEB-33+35): Fix Mermaid error display + useWorkspaceId error logging
SEC-WEB-33: Replace raw diagram source and detailed error messages in
MermaidViewer error UI with a generic "Diagram rendering failed" message.
Detailed errors are logged to console.error for debugging only.

SEC-WEB-35: Add console.warn in useWorkspaceId when no workspace ID is
found in localStorage, making it easier to distinguish "no workspace
selected" from silent hook failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 18:16:07 -06:00

110 lines
3.3 KiB
TypeScript

/**
* useWorkspaceId Hook Tests
* Tests for SEC-WEB-35: warning logging when workspace ID is not found
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook } from "@testing-library/react";
import { useWorkspaceId } from "./useLayout";
interface MockLocalStorage {
getItem: ReturnType<typeof vi.fn>;
setItem: ReturnType<typeof vi.fn>;
removeItem: ReturnType<typeof vi.fn>;
clear: ReturnType<typeof vi.fn>;
readonly length: number;
key: ReturnType<typeof vi.fn>;
}
// Mock localStorage
const localStorageMock = ((): MockLocalStorage => {
let store: Record<string, string> = {};
return {
getItem: vi.fn((key: string): string | null => store[key] ?? null),
setItem: vi.fn((key: string, value: string): void => {
store[key] = value;
}),
removeItem: vi.fn((key: string): void => {
store = Object.fromEntries(Object.entries(store).filter(([k]) => k !== key));
}),
clear: vi.fn((): void => {
store = {};
}),
get length(): number {
return Object.keys(store).length;
},
key: vi.fn((_index: number): string | null => null),
};
})();
Object.defineProperty(window, "localStorage", {
value: localStorageMock,
writable: true,
});
describe("useWorkspaceId", (): void => {
let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
beforeEach((): void => {
vi.clearAllMocks();
localStorageMock.clear();
consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => undefined);
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
});
afterEach((): void => {
consoleWarnSpy.mockRestore();
consoleErrorSpy.mockRestore();
vi.resetAllMocks();
});
it("should return workspace ID when stored in localStorage", (): void => {
localStorageMock.setItem("mosaic-workspace-id", "ws-123");
const { result } = renderHook(() => useWorkspaceId());
expect(result.current).toBe("ws-123");
expect(consoleWarnSpy).not.toHaveBeenCalled();
});
it("should return null and log warning when no workspace ID in localStorage", (): void => {
const { result } = renderHook(() => useWorkspaceId());
expect(result.current).toBeNull();
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining("No workspace ID found in localStorage")
);
});
it("should include the storage key in the warning message", (): void => {
renderHook(() => useWorkspaceId());
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining("mosaic-workspace-id"));
});
it("should log console.error when localStorage throws", (): void => {
localStorageMock.getItem.mockImplementation(() => {
throw new Error("localStorage is disabled");
});
const { result } = renderHook(() => useWorkspaceId());
expect(result.current).toBeNull();
expect(consoleErrorSpy).toHaveBeenCalledWith(
"Failed to load workspace ID from localStorage:",
expect.any(Error)
);
});
it("should not log warning when workspace ID exists", (): void => {
localStorageMock.setItem("mosaic-workspace-id", "ws-abc-def");
renderHook(() => useWorkspaceId());
expect(consoleWarnSpy).not.toHaveBeenCalled();
expect(consoleErrorSpy).not.toHaveBeenCalled();
});
});