Persist filter and auto-redact preferences to localStorage
Category toggles and the auto-redact checkbox now survive page reloads. On mount, useFilters reads from localStorage keys session-viewer:enabledCategories and session-viewer:autoRedact, falling back to defaults when storage is empty, corrupted, or contains invalid category names. Each state change writes back to localStorage in a useEffect. Tests cover round-trip persistence, invalid data recovery, corrupted JSON fallback, and the boolean coercion for auto-redact. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
129
tests/unit/filter-persistence.test.ts
Normal file
129
tests/unit/filter-persistence.test.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import type { MessageCategory } from "../../src/shared/types.js";
|
||||
import { ALL_CATEGORIES, DEFAULT_HIDDEN_CATEGORIES } from "../../src/shared/types.js";
|
||||
|
||||
// Test the localStorage persistence logic used by useFilters
|
||||
const STORAGE_KEY_CATEGORIES = "session-viewer:enabledCategories";
|
||||
const STORAGE_KEY_AUTOREDACT = "session-viewer:autoRedact";
|
||||
|
||||
function loadEnabledCategories(): Set<MessageCategory> {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY_CATEGORIES);
|
||||
if (stored) {
|
||||
const arr = JSON.parse(stored) as string[];
|
||||
const valid = arr.filter((c) =>
|
||||
ALL_CATEGORIES.includes(c as MessageCategory)
|
||||
) as MessageCategory[];
|
||||
if (valid.length > 0) return new Set(valid);
|
||||
}
|
||||
} catch {
|
||||
// Fall through to default
|
||||
}
|
||||
const set = new Set(ALL_CATEGORIES);
|
||||
for (const cat of DEFAULT_HIDDEN_CATEGORIES) {
|
||||
set.delete(cat);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
function loadAutoRedact(): boolean {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY_AUTOREDACT);
|
||||
if (stored !== null) return stored === "true";
|
||||
} catch {
|
||||
// Fall through to default
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mock localStorage
|
||||
const store: Record<string, string> = {};
|
||||
const localStorageMock = {
|
||||
getItem: vi.fn((key: string) => store[key] ?? null),
|
||||
setItem: vi.fn((key: string, value: string) => { store[key] = value; }),
|
||||
removeItem: vi.fn((key: string) => { delete store[key]; }),
|
||||
clear: vi.fn(() => { for (const key in store) delete store[key]; }),
|
||||
get length() { return Object.keys(store).length; },
|
||||
key: vi.fn((i: number) => Object.keys(store)[i] ?? null),
|
||||
};
|
||||
|
||||
Object.defineProperty(globalThis, "localStorage", { value: localStorageMock });
|
||||
|
||||
describe("filter persistence", () => {
|
||||
beforeEach(() => {
|
||||
localStorageMock.clear();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("loadEnabledCategories", () => {
|
||||
it("returns default categories when localStorage is empty", () => {
|
||||
const result = loadEnabledCategories();
|
||||
const expected = new Set(ALL_CATEGORIES);
|
||||
for (const cat of DEFAULT_HIDDEN_CATEGORIES) {
|
||||
expected.delete(cat);
|
||||
}
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it("restores categories from localStorage", () => {
|
||||
const saved: MessageCategory[] = ["user_message", "assistant_text"];
|
||||
store[STORAGE_KEY_CATEGORIES] = JSON.stringify(saved);
|
||||
|
||||
const result = loadEnabledCategories();
|
||||
expect(result).toEqual(new Set(saved));
|
||||
});
|
||||
|
||||
it("filters out invalid category values from localStorage", () => {
|
||||
const saved = ["user_message", "invalid_category", "thinking"];
|
||||
store[STORAGE_KEY_CATEGORIES] = JSON.stringify(saved);
|
||||
|
||||
const result = loadEnabledCategories();
|
||||
expect(result.has("user_message")).toBe(true);
|
||||
expect(result.has("thinking")).toBe(true);
|
||||
expect(result.size).toBe(2);
|
||||
});
|
||||
|
||||
it("falls back to defaults on corrupted localStorage data", () => {
|
||||
store[STORAGE_KEY_CATEGORIES] = "not valid json";
|
||||
|
||||
const result = loadEnabledCategories();
|
||||
const expected = new Set(ALL_CATEGORIES);
|
||||
for (const cat of DEFAULT_HIDDEN_CATEGORIES) {
|
||||
expected.delete(cat);
|
||||
}
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it("falls back to defaults when stored array is all invalid", () => {
|
||||
store[STORAGE_KEY_CATEGORIES] = JSON.stringify(["fake1", "fake2"]);
|
||||
|
||||
const result = loadEnabledCategories();
|
||||
const expected = new Set(ALL_CATEGORIES);
|
||||
for (const cat of DEFAULT_HIDDEN_CATEGORIES) {
|
||||
expected.delete(cat);
|
||||
}
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("loadAutoRedact", () => {
|
||||
it("returns false when localStorage is empty", () => {
|
||||
expect(loadAutoRedact()).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true when stored as 'true'", () => {
|
||||
store[STORAGE_KEY_AUTOREDACT] = "true";
|
||||
expect(loadAutoRedact()).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false when stored as 'false'", () => {
|
||||
store[STORAGE_KEY_AUTOREDACT] = "false";
|
||||
expect(loadAutoRedact()).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for any non-'true' string", () => {
|
||||
store[STORAGE_KEY_AUTOREDACT] = "yes";
|
||||
expect(loadAutoRedact()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user