Newer
Older
rpg / v2 / packages / engine / src / save / __tests__ / saveManager.test.ts
import { describe, it, expect } from 'vitest';
import { SaveManager, type StorageLike } from '../saveManager';

/** Хранилище-заглушка (как localStorage, без DOM). */
function makeStorage(): StorageLike & { map: Map<string, string> } {
    const map = new Map<string, string>();
    return {
        map,
        getItem: (k) => map.get(k) ?? null,
        setItem: (k, v) => void map.set(k, v),
        removeItem: (k) => void map.delete(k),
        key: (i) => [...map.keys()][i] ?? null,
        get length() { return map.size; },
    };
}

describe('SaveManager', () => {
    it('сохраняет и загружает по слотам', () => {
        const sm = new SaveManager(makeStorage());
        expect(sm.has('a')).toBe(false);
        sm.save('a', { pos: [1, 2, 3] });
        expect(sm.has('a')).toBe(true);
        expect(sm.load('a')).toEqual({ pos: [1, 2, 3] });
        expect(sm.load('b')).toBeNull();
    });

    it('битый JSON → null', () => {
        const storage = makeStorage();
        const sm = new SaveManager(storage);
        storage.setItem('save:x', '{oops');
        expect(sm.load('x')).toBeNull();
    });

    it('listSlots и delete', () => {
        const sm = new SaveManager(makeStorage());
        sm.save('a', 1);
        sm.save('b', 2);
        expect(sm.listSlots()).toEqual(['a', 'b']);
        sm.delete('a');
        expect(sm.listSlots()).toEqual(['b']);
        expect(sm.has('a')).toBe(false);
    });

    it('мета пишется в параллельный ключ и читается без сейва', () => {
        const storage = makeStorage();
        const sm = new SaveManager(storage);
        sm.save('slot', { hp: 5 }, { title: 'Луга', savedAt: 123, version: 1 });
        expect(storage.map.has('save:meta:slot')).toBe(true);
        expect(sm.slotMeta('slot')).toEqual({ title: 'Луга', savedAt: 123, version: 1 });
        // мета-ключи не попадают в список слотов
        expect(sm.listSlots()).toEqual(['slot']);
    });

    it('сейв без меты и битая мета → slotMeta null, сейв читается', () => {
        const storage = makeStorage();
        const sm = new SaveManager(storage);
        sm.save('old', 7);
        expect(sm.slotMeta('old')).toBeNull();
        storage.setItem('save:meta:bad', '{oops');
        expect(sm.slotMeta('bad')).toBeNull();
        storage.setItem('save:bad', '3');
        expect(sm.load('bad')).toBe(3);
    });

    it('delete чистит и сейв, и мету; кастомный префикс', () => {
        const sm = new SaveManager(makeStorage(), 'auto:');
        sm.save('s', 1, { title: 'Т' });
        expect(sm.listSlots()).toEqual(['s']);
        sm.delete('s');
        expect(sm.listSlots()).toEqual([]);
        expect(sm.slotMeta('s')).toBeNull();
        expect(sm.has('s')).toBe(false);
    });

    it('list возвращает слоты с метой', () => {
        const sm = new SaveManager(makeStorage());
        sm.save('a', 1, { title: 'А' });
        sm.save('b', 2);
        expect(sm.list()).toEqual([
            { slot: 'a', meta: { title: 'А' } },
            { slot: 'b', meta: null },
        ]);
    });
});