All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
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>
110 lines
3.3 KiB
TypeScript
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();
|
|
});
|
|
});
|