import { describe, expect, it, vi } from 'vitest';
import { Inventory, inventoryFromData } from '../Inventory';
describe('Inventory', () => {
it('добавляет и считает предметы', () => {
const inv = new Inventory();
expect(inv.empty).toBe(true);
inv.add('cloth');
inv.add('bellflower', 2);
expect(inv.count('cloth')).toBe(1);
expect(inv.count('bellflower')).toBe(2);
expect(inv.has('cloth')).toBe(true);
expect(inv.has('salt')).toBe(false);
expect(inv.empty).toBe(false);
});
it('remove вычитает и чистит пустые слоты', () => {
const inv = new Inventory();
inv.add('bellflower', 3);
expect(inv.remove('bellflower', 2)).toBe(2);
expect(inv.count('bellflower')).toBe(1);
expect(inv.remove('bellflower', 5)).toBe(1); // нельзя убрать больше, чем есть
expect(inv.count('bellflower')).toBe(0);
expect(inv.has('bellflower')).toBe(false);
expect(inv.remove('cloth')).toBe(0); // нет предмета — ничего
});
it('add с неположительным n — no-op', () => {
const inv = new Inventory();
inv.add('salt', 0);
inv.add('salt', -1);
expect(inv.empty).toBe(true);
});
it('onChange оповещает о каждом изменении, отписка работает', () => {
const inv = new Inventory();
const fn = vi.fn();
const off = inv.onChange(fn);
inv.add('cloth');
inv.add('cloth');
inv.remove('cloth');
off();
inv.add('cloth');
expect(fn).toHaveBeenCalledTimes(3);
});
it('serialize/load round-trip; load игнорирует мусор', () => {
const inv = new Inventory();
inv.add('cloth');
inv.add('bellflower', 3);
const data = inv.serialize();
const back = inventoryFromData(JSON.parse(JSON.stringify(data)));
expect(back.all).toEqual([
{ id: 'cloth', count: 1 },
{ id: 'bellflower', count: 3 }
]);
back.load({ items: { salt: 0, bad: -2, cloth: 2 } });
expect(back.count('salt')).toBe(0);
expect(back.count('bad')).toBe(0);
expect(back.count('cloth')).toBe(2);
});
it('clear сбрасывает содержимое', () => {
const inv = new Inventory();
inv.add('cloth');
inv.clear();
expect(inv.empty).toBe(true);
});
it('maxPerStack обрезает добавление и возвращает факт', () => {
const inv = new Inventory({ maxPerStack: 3 });
expect(inv.add('bellflower', 5)).toBe(3);
expect(inv.count('bellflower')).toBe(3);
expect(inv.add('bellflower', 1)).toBe(0); // стак полон
expect(inv.spaceFor('bellflower')).toBe(0);
expect(inv.spaceFor('salt')).toBe(3);
});
it('maxSlots не даёт заводить новый слот, но стек в существующий идёт', () => {
const inv = new Inventory({ maxSlots: 2 });
inv.add('cloth');
inv.add('salt');
expect(inv.add('bellflower')).toBe(0); // слотов нет
expect(inv.has('bellflower')).toBe(false);
expect(inv.add('salt', 2)).toBe(2); // свой слот не лимитируем
});
it('load клампит под лимиты', () => {
const inv = new Inventory({ maxSlots: 2, maxPerStack: 5 });
inv.load({ items: { a: 9, b: 2, c: 1 } });
expect(inv.all).toEqual([
{ id: 'a', count: 5 },
{ id: 'b', count: 2 }
]);
expect(inv.size).toBe(2);
});
it('событие несёт деталь изменения для всех kind', () => {
const inv = new Inventory();
const fn = vi.fn();
inv.onChange(fn);
inv.add('cloth', 2);
expect(fn).toHaveBeenLastCalledWith({ kind: 'add', id: 'cloth', count: 2, after: 2 });
inv.remove('cloth');
expect(fn).toHaveBeenLastCalledWith({ kind: 'remove', id: 'cloth', count: -1, after: 1 });
inv.remove('cloth', 1);
expect(fn).toHaveBeenLastCalledWith({ kind: 'remove', id: 'cloth', count: -1, after: 0 });
inv.clear(); // пусто — clear не эмитится
inv.add('salt');
inv.load({ items: {} });
expect(fn).toHaveBeenLastCalledWith({ kind: 'load', id: '', count: 0, after: 0 });
inv.add('salt');
inv.clear();
expect(fn).toHaveBeenLastCalledWith({ kind: 'clear', id: '', count: 0, after: 0 });
});
it('генерик типизирует id (компиляция)', () => {
type Item = 'a' | 'b';
const inv = new Inventory<Item>();
inv.add('a', 2);
expect(inv.count('a')).toBe(2);
});
});