Production code (all findings cleared):
- GenerationHistory: drop unused fetchProHistory(userId) param,
change let activeId to const
- InitiativeTracker: remove write-only currentSessionName state
- NotesPanel: remove unused isLoading flag
- Open5eReference: drop unused getCachedTranslation/setCachedTranslation
imports; use fetchTranslation result directly instead of re-lookup
- ai/openrouter.ts: drop unused z + Monster imports; attach cause to
rethrown AbortError
- ai/kimi.ts: drop unused z import
- api/dm/ai/generate.ts: annotate best-effort counter increment catch
- api/dm/ai/history.ts: drop unused sql import
Test files (trivial fixes; ~70 mock-related `as any` errors remain as
pre-existing tech debt across api test mocks):
- translation-client: drop unused MockBroadcastChannel _name param and
unused getCachedTranslation destructure
- open5e-types: eslint-disable for the intentional type-test aliases
- {notes,initiative}-api: remove unused mockUser2 placeholder
- {notes,initiative,translate,history}-api: let mockDb -> const mockDb
- ai-generate-api: wrap deliberate `var` mock decls in eslint-disable
with comment explaining vi.mock hoisting requirement
Verification: tsc 0 errors, vitest 339/339, lint 81 -> 70 errors
(production code fully clean; remaining errors are test-mock as-any
patterns from Waves 5-6).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
146 lines
4.3 KiB
TypeScript
146 lines
4.3 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
const mockPostMessage = vi.fn();
|
|
const mockAddEventListener = vi.fn();
|
|
|
|
function MockBroadcastChannel(this: unknown) {
|
|
return {
|
|
postMessage: mockPostMessage,
|
|
addEventListener: mockAddEventListener,
|
|
close: vi.fn(),
|
|
};
|
|
}
|
|
|
|
vi.stubGlobal("BroadcastChannel", MockBroadcastChannel as unknown as typeof BroadcastChannel);
|
|
|
|
describe("translation service", () => {
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
mockPostMessage.mockClear();
|
|
mockAddEventListener.mockClear();
|
|
});
|
|
|
|
it("stores and retrieves cached translations", async () => {
|
|
const { getCachedTranslation, setCachedTranslation, clearTranslationCache } =
|
|
await import("../src/lib/client/translation");
|
|
clearTranslationCache();
|
|
|
|
expect(getCachedTranslation("goblin", "creature")).toBeNull();
|
|
|
|
setCachedTranslation("goblin", "creature", { name: "Гоблин" });
|
|
|
|
expect(getCachedTranslation("goblin", "creature")).toEqual({ name: "Гоблин" });
|
|
expect(getCachedTranslation("dragon", "creature")).toBeNull();
|
|
});
|
|
|
|
it("broadcasts cache updates via BroadcastChannel", async () => {
|
|
const { setCachedTranslation, clearTranslationCache } = await import(
|
|
"../src/lib/client/translation"
|
|
);
|
|
clearTranslationCache();
|
|
|
|
setCachedTranslation("goblin", "creature", { name: "Гоблин" });
|
|
|
|
expect(mockPostMessage).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
slug: "goblin",
|
|
type: "creature",
|
|
data: { name: "Гоблин" },
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("fetches translation from API when not cached", async () => {
|
|
const mockFetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ translated: { name: "Гоблин" } }),
|
|
});
|
|
global.fetch = mockFetch as unknown as typeof fetch;
|
|
|
|
const { fetchTranslation, clearTranslationCache } = await import(
|
|
"../src/lib/client/translation"
|
|
);
|
|
clearTranslationCache();
|
|
|
|
const result = await fetchTranslation("goblin", "creature");
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
"/api/dm/translate?slug=goblin&type=creature",
|
|
);
|
|
expect(result).toEqual({ name: "Гоблин" });
|
|
});
|
|
|
|
it("returns cached translation without fetching", async () => {
|
|
const mockFetch = vi.fn();
|
|
global.fetch = mockFetch as unknown as typeof fetch;
|
|
|
|
const {
|
|
setCachedTranslation,
|
|
fetchTranslation,
|
|
clearTranslationCache,
|
|
} = await import("../src/lib/client/translation");
|
|
clearTranslationCache();
|
|
|
|
setCachedTranslation("goblin", "creature", { name: "Гоблин" });
|
|
|
|
const result = await fetchTranslation("goblin", "creature");
|
|
|
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
expect(result).toEqual({ name: "Гоблин" });
|
|
});
|
|
|
|
it("handles fetch errors", async () => {
|
|
const mockFetch = vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
json: async () => ({ error: "Not found" }),
|
|
});
|
|
global.fetch = mockFetch as unknown as typeof fetch;
|
|
|
|
const { fetchTranslation, clearTranslationCache } = await import(
|
|
"../src/lib/client/translation"
|
|
);
|
|
clearTranslationCache();
|
|
|
|
await expect(fetchTranslation("unknown", "creature")).rejects.toThrow(
|
|
"Not found",
|
|
);
|
|
});
|
|
|
|
it("does not use localStorage", async () => {
|
|
const {
|
|
getCachedTranslation,
|
|
setCachedTranslation,
|
|
clearTranslationCache,
|
|
} = await import("../src/lib/client/translation");
|
|
clearTranslationCache();
|
|
|
|
const setItemSpy = vi.spyOn(Storage.prototype, "setItem");
|
|
const getItemSpy = vi.spyOn(Storage.prototype, "getItem");
|
|
|
|
setCachedTranslation("goblin", "creature", { name: "Гоблин" });
|
|
getCachedTranslation("goblin", "creature");
|
|
|
|
expect(setItemSpy).not.toHaveBeenCalled();
|
|
expect(getItemSpy).not.toHaveBeenCalled();
|
|
|
|
setItemSpy.mockRestore();
|
|
getItemSpy.mockRestore();
|
|
});
|
|
|
|
it("tracks cache size", async () => {
|
|
const {
|
|
setCachedTranslation,
|
|
getTranslationCacheSize,
|
|
clearTranslationCache,
|
|
} = await import("../src/lib/client/translation");
|
|
clearTranslationCache();
|
|
|
|
expect(getTranslationCacheSize()).toBe(0);
|
|
|
|
setCachedTranslation("a", "creature", { name: "A" });
|
|
setCachedTranslation("b", "spell", { name: "B" });
|
|
|
|
expect(getTranslationCacheSize()).toBe(2);
|
|
});
|
|
});
|