Replace direct adapter/persistence imports with context-based injection (AdapterContext + useAdapters) so tests use in-memory implementations instead of vi.mock. Migrate component tests from context mocking to AllProviders with real hooks. Extract export/import logic from ActionBar into useEncounterExportImport hook. Add bestiary-cache and bestiary-index-adapter test suites. Raise adapter coverage thresholds (68→80 lines, 56→62 branches). 77 test files, 891 tests, all passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
109 lines
2.8 KiB
TypeScript
109 lines
2.8 KiB
TypeScript
import {
|
|
type Creature,
|
|
type CreatureId,
|
|
EMPTY_UNDO_REDO_STATE,
|
|
type Encounter,
|
|
type PlayerCharacter,
|
|
type UndoRedoState,
|
|
} from "@initiative/domain";
|
|
import type { Adapters } from "../../contexts/adapter-context.js";
|
|
|
|
export function createTestAdapters(options?: {
|
|
encounter?: Encounter | null;
|
|
undoRedoState?: UndoRedoState;
|
|
playerCharacters?: PlayerCharacter[];
|
|
creatures?: Map<CreatureId, Creature>;
|
|
sources?: Map<
|
|
string,
|
|
{ displayName: string; creatures: Creature[]; cachedAt: number }
|
|
>;
|
|
}): Adapters {
|
|
let storedEncounter = options?.encounter ?? null;
|
|
let storedUndoRedo = options?.undoRedoState ?? EMPTY_UNDO_REDO_STATE;
|
|
let storedPCs = options?.playerCharacters ?? [];
|
|
const sourceStore =
|
|
options?.sources ??
|
|
new Map<
|
|
string,
|
|
{ displayName: string; creatures: Creature[]; cachedAt: number }
|
|
>();
|
|
|
|
// Pre-populate sourceStore from creatures map if provided
|
|
if (options?.creatures && !options?.sources) {
|
|
// No-op: creatures are accessed directly from the map
|
|
}
|
|
|
|
const creatureMap = options?.creatures ?? new Map<CreatureId, Creature>();
|
|
|
|
return {
|
|
encounterPersistence: {
|
|
load: () => storedEncounter,
|
|
save: (e) => {
|
|
storedEncounter = e;
|
|
},
|
|
},
|
|
undoRedoPersistence: {
|
|
load: () => storedUndoRedo,
|
|
save: (state) => {
|
|
storedUndoRedo = state;
|
|
},
|
|
},
|
|
playerCharacterPersistence: {
|
|
load: () => [...storedPCs],
|
|
save: (pcs) => {
|
|
storedPCs = pcs;
|
|
},
|
|
},
|
|
bestiaryCache: {
|
|
cacheSource(sourceCode, displayName, creatures) {
|
|
sourceStore.set(sourceCode, {
|
|
displayName,
|
|
creatures,
|
|
cachedAt: Date.now(),
|
|
});
|
|
for (const c of creatures) {
|
|
creatureMap.set(c.id, c);
|
|
}
|
|
return Promise.resolve();
|
|
},
|
|
isSourceCached(sourceCode) {
|
|
return Promise.resolve(sourceStore.has(sourceCode));
|
|
},
|
|
getCachedSources() {
|
|
return Promise.resolve(
|
|
[...sourceStore.entries()].map(([sourceCode, info]) => ({
|
|
sourceCode,
|
|
displayName: info.displayName,
|
|
creatureCount: info.creatures.length,
|
|
cachedAt: info.cachedAt,
|
|
})),
|
|
);
|
|
},
|
|
clearSource(sourceCode) {
|
|
sourceStore.delete(sourceCode);
|
|
return Promise.resolve();
|
|
},
|
|
clearAll() {
|
|
sourceStore.clear();
|
|
return Promise.resolve();
|
|
},
|
|
loadAllCachedCreatures() {
|
|
return Promise.resolve(new Map(creatureMap));
|
|
},
|
|
},
|
|
bestiaryIndex: {
|
|
loadIndex: () => ({ sources: {}, creatures: [] }),
|
|
getAllSourceCodes: () => [],
|
|
getDefaultFetchUrl: (sourceCode, baseUrl) => {
|
|
const filename = `bestiary-${sourceCode.toLowerCase()}.json`;
|
|
if (baseUrl !== undefined) {
|
|
const normalized = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
return `${normalized}${filename}`;
|
|
}
|
|
return `https://example.com/${filename}`;
|
|
},
|
|
getSourceDisplayName: (sourceCode) => sourceCode,
|
|
},
|
|
};
|
|
}
|