test(canvas): add pure-function coverage for AuditTrailPanel + MemoryInspectorPanel #822
63
canvas/src/components/__tests__/AuditTrailPanel.test.ts
Normal file
63
canvas/src/components/__tests__/AuditTrailPanel.test.ts
Normal file
@ -0,0 +1,63 @@
|
||||
// @vitest-environment jsdom
|
||||
/**
|
||||
* Unit tests for formatAuditRelativeTime — pure date formatter from AuditTrailPanel.
|
||||
*/
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { formatAuditRelativeTime } from "../AuditTrailPanel";
|
||||
|
||||
describe("formatAuditRelativeTime", () => {
|
||||
it('returns "just now" for timestamps within the last minute', () => {
|
||||
const now = 1_700_000_000_000;
|
||||
const thirtySecAgo = new Date(now - 30_000).toISOString();
|
||||
expect(formatAuditRelativeTime(thirtySecAgo, now)).toBe("just now");
|
||||
});
|
||||
|
||||
it('returns "Xm ago" for timestamps within the last hour', () => {
|
||||
const now = 1_700_000_000_000;
|
||||
const fiveMinAgo = new Date(now - 5 * 60_000).toISOString();
|
||||
expect(formatAuditRelativeTime(fiveMinAgo, now)).toBe("5m ago");
|
||||
});
|
||||
|
||||
it('returns "Xh ago" for timestamps within the last day', () => {
|
||||
const now = 1_700_000_000_000;
|
||||
const threeHoursAgo = new Date(now - 3 * 3_600_000).toISOString();
|
||||
expect(formatAuditRelativeTime(threeHoursAgo, now)).toBe("3h ago");
|
||||
});
|
||||
|
||||
it("returns locale date string for timestamps older than 24h", () => {
|
||||
const now = 1_700_000_000_000;
|
||||
const twoDaysAgo = new Date(now - 2 * 86_400_000).toISOString();
|
||||
const result = formatAuditRelativeTime(twoDaysAgo, now);
|
||||
// Should be a date string (not "Xh ago" or "Xm ago")
|
||||
expect(result).not.toMatch(/m ago|h ago|just now/);
|
||||
expect(result).toBe(new Date(twoDaysAgo).toLocaleDateString());
|
||||
});
|
||||
|
||||
it("handles the boundary between minute and hour correctly", () => {
|
||||
const now = 1_700_000_000_000;
|
||||
const exactlyOneHourAgo = new Date(now - 3_600_000).toISOString();
|
||||
expect(formatAuditRelativeTime(exactlyOneHourAgo, now)).toBe("1h ago");
|
||||
});
|
||||
|
||||
it("handles the boundary between hour and day correctly", () => {
|
||||
const now = 1_700_000_000_000;
|
||||
// 23h ago is < 24h so it shows "23h ago"; exactly 24h falls through to date string
|
||||
const twentyThreeHoursAgo = new Date(now - 23 * 3_600_000).toISOString();
|
||||
expect(formatAuditRelativeTime(twentyThreeHoursAgo, now)).toBe("23h ago");
|
||||
});
|
||||
|
||||
it("returns locale date string for exactly 24h ago (boundary)", () => {
|
||||
const now = 1_700_000_000_000;
|
||||
const exactlyOneDayAgo = new Date(now - 86_400_000).toISOString();
|
||||
const result = formatAuditRelativeTime(exactlyOneDayAgo, now);
|
||||
// diff is exactly 86_400_000, which is NOT < 86_400_000, so it falls through
|
||||
expect(result).toBe(new Date(exactlyOneDayAgo).toLocaleDateString());
|
||||
});
|
||||
|
||||
it("future timestamps return 'just now' (negative diff < 60_000)", () => {
|
||||
const now = 1_700_000_000_000;
|
||||
const future = new Date(now + 60_000).toISOString();
|
||||
// Negative diff passes diff < 60_000, returning "just now"
|
||||
expect(formatAuditRelativeTime(future, now)).toBe("just now");
|
||||
});
|
||||
});
|
||||
90
canvas/src/components/__tests__/MemoryInspectorPanel.test.ts
Normal file
90
canvas/src/components/__tests__/MemoryInspectorPanel.test.ts
Normal file
@ -0,0 +1,90 @@
|
||||
// @vitest-environment jsdom
|
||||
/**
|
||||
* Unit tests for pure helpers from MemoryInspectorPanel:
|
||||
* isPluginUnavailableError, formatRelativeTime, formatTTL
|
||||
*
|
||||
* These are the three exported non-component functions. The component
|
||||
* itself (MemoryInspectorPanel) requires full API + store mocking and
|
||||
* is exercised by the existing MemoryTab.test.tsx.
|
||||
*/
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { isPluginUnavailableError, formatTTL } from "../MemoryInspectorPanel";
|
||||
|
||||
// formatRelativeTime is not exported — tested via the component in MemoryTab.test.tsx
|
||||
|
||||
describe("isPluginUnavailableError", () => {
|
||||
it("returns true when Error message contains MEMORY_PLUGIN_URL", () => {
|
||||
const err = new Error("memory: could not resolve MEMORY_PLUGIN_URL — plugin not configured");
|
||||
expect(isPluginUnavailableError(err)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for Error containing MEMORY_PLUGIN_URL", () => {
|
||||
expect(isPluginUnavailableError(new Error("MEMORY_PLUGIN_URL is not set"))).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for unrelated error messages", () => {
|
||||
expect(isPluginUnavailableError(new Error("workspace not found"))).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for null", () => {
|
||||
expect(isPluginUnavailableError(null)).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for undefined", () => {
|
||||
expect(isPluginUnavailableError(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for plain objects without message", () => {
|
||||
expect(isPluginUnavailableError({ code: 503 })).toBe(false);
|
||||
});
|
||||
|
||||
it("is case-sensitive (MEMORY_PLUGIN_URL must match exactly)", () => {
|
||||
const lowerErr = new Error("memory_plugin_url missing");
|
||||
const upperErr = new Error("MEMORY_PLUGIN_URL missing");
|
||||
expect(isPluginUnavailableError(lowerErr)).toBe(false);
|
||||
expect(isPluginUnavailableError(upperErr)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatTTL", () => {
|
||||
it("returns '' for null", () => {
|
||||
expect(formatTTL(null)).toBe("");
|
||||
});
|
||||
|
||||
it("returns '' for undefined", () => {
|
||||
expect(formatTTL(undefined)).toBe("");
|
||||
});
|
||||
|
||||
it('returns "expired" when expiresAt is in the past', () => {
|
||||
const past = new Date(Date.now() - 60_000).toISOString();
|
||||
expect(formatTTL(past)).toBe("expired");
|
||||
});
|
||||
|
||||
it('returns "Xs" for less than a minute', () => {
|
||||
const soon = new Date(Date.now() + 30_000).toISOString();
|
||||
expect(formatTTL(soon)).toBe("30s");
|
||||
});
|
||||
|
||||
it('returns "Xm" for less than an hour', () => {
|
||||
const soon = new Date(Date.now() + 5 * 60_000).toISOString();
|
||||
expect(formatTTL(soon)).toBe("5m");
|
||||
});
|
||||
|
||||
it('returns "Xh" for less than a day', () => {
|
||||
const soon = new Date(Date.now() + 3 * 3_600_000).toISOString();
|
||||
expect(formatTTL(soon)).toBe("3h");
|
||||
});
|
||||
|
||||
it('returns "Xd" for more than a day', () => {
|
||||
const soon = new Date(Date.now() + 2 * 86_400_000).toISOString();
|
||||
expect(formatTTL(soon)).toBe("2d");
|
||||
});
|
||||
|
||||
it("returns '' for invalid date string", () => {
|
||||
expect(formatTTL("not-a-date")).toBe("");
|
||||
});
|
||||
|
||||
it("returns '' for empty string", () => {
|
||||
expect(formatTTL("")).toBe("");
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user