fix(SEC-WEB-33+35): Fix Mermaid error display + useWorkspaceId error logging
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
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>
This commit is contained in:
@@ -232,6 +232,11 @@ export function useWorkspaceId(): string | null {
|
||||
const stored = localStorage.getItem(WORKSPACE_KEY);
|
||||
if (stored) {
|
||||
setWorkspaceId(stored);
|
||||
} else {
|
||||
console.warn(
|
||||
`useWorkspaceId: No workspace ID found in localStorage (key: "${WORKSPACE_KEY}"). ` +
|
||||
"This may indicate no workspace has been selected yet."
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load workspace ID from localStorage:", error);
|
||||
|
||||
109
apps/web/src/lib/hooks/useWorkspaceId.test.ts
Normal file
109
apps/web/src/lib/hooks/useWorkspaceId.test.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user