/** * Inventory System Tests — Phase 4.2 * * Weight-based inventory with element stacking, * mass limits, and slot limits. */ import { describe, it, expect } from 'vitest'; import { Inventory, getItemMass } from '../src/player/inventory'; // === Item Mass Lookup === describe('getItemMass', () => { it('returns atomic mass for elements', () => { expect(getItemMass('H')).toBeCloseTo(1.008); expect(getItemMass('Na')).toBeCloseTo(22.99); expect(getItemMass('Fe')).toBeCloseTo(55.845); expect(getItemMass('O')).toBeCloseTo(15.999); }); it('returns molecular mass for compounds', () => { expect(getItemMass('NaCl')).toBeCloseTo(58.44); expect(getItemMass('H2O')).toBeCloseTo(18.015); expect(getItemMass('CO2')).toBeCloseTo(44.01); }); it('returns 0 for unknown items', () => { expect(getItemMass('UNKNOWN')).toBe(0); expect(getItemMass('')).toBe(0); }); }); // === Inventory Core === describe('Inventory — creation', () => { it('starts empty', () => { const inv = new Inventory(); expect(inv.isEmpty()).toBe(true); expect(inv.getItems()).toEqual([]); expect(inv.getTotalWeight()).toBe(0); expect(inv.slotCount).toBe(0); }); it('respects custom limits', () => { const inv = new Inventory(100, 5); expect(inv.maxWeight).toBe(100); expect(inv.maxSlots).toBe(5); }); }); describe('Inventory — adding items', () => { it('adds elements', () => { const inv = new Inventory(); const added = inv.addItem('H', 3); expect(added).toBe(3); expect(inv.getCount('H')).toBe(3); expect(inv.isEmpty()).toBe(false); expect(inv.slotCount).toBe(1); }); it('adds compounds', () => { const inv = new Inventory(); const added = inv.addItem('NaCl', 1); expect(added).toBe(1); expect(inv.getCount('NaCl')).toBe(1); }); it('stacks same elements', () => { const inv = new Inventory(); inv.addItem('Na', 2); inv.addItem('Na', 3); expect(inv.getCount('Na')).toBe(5); expect(inv.slotCount).toBe(1); }); it('stacking does not consume new slot', () => { const inv = new Inventory(10000, 3); inv.addItem('H', 1); inv.addItem('O', 1); inv.addItem('Na', 1); const added = inv.addItem('H', 5); expect(added).toBe(5); expect(inv.getCount('H')).toBe(6); expect(inv.slotCount).toBe(3); }); it('returns 0 for unknown items', () => { const inv = new Inventory(); expect(inv.addItem('UNKNOWN', 1)).toBe(0); expect(inv.isEmpty()).toBe(true); }); it('returns 0 for zero/negative count', () => { const inv = new Inventory(); expect(inv.addItem('H', 0)).toBe(0); expect(inv.addItem('H', -1)).toBe(0); }); }); describe('Inventory — removing items', () => { it('removes items', () => { const inv = new Inventory(); inv.addItem('H', 5); const removed = inv.removeItem('H', 3); expect(removed).toBe(3); expect(inv.getCount('H')).toBe(2); }); it('cleans up slot when count reaches zero', () => { const inv = new Inventory(); inv.addItem('H', 3); inv.removeItem('H', 3); expect(inv.getCount('H')).toBe(0); expect(inv.slotCount).toBe(0); expect(inv.isEmpty()).toBe(true); }); it('removes only what is available', () => { const inv = new Inventory(); inv.addItem('H', 2); const removed = inv.removeItem('H', 5); expect(removed).toBe(2); expect(inv.getCount('H')).toBe(0); }); it('returns 0 for non-existent item', () => { const inv = new Inventory(); expect(inv.removeItem('H', 1)).toBe(0); }); it('returns 0 for zero/negative count', () => { const inv = new Inventory(); inv.addItem('H', 5); expect(inv.removeItem('H', 0)).toBe(0); expect(inv.removeItem('H', -1)).toBe(0); }); }); describe('Inventory — weight system', () => { it('calculates total weight from atomic masses', () => { const inv = new Inventory(); inv.addItem('Na', 2); // 22.99 * 2 = 45.98 expect(inv.getTotalWeight()).toBeCloseTo(22.99 * 2); }); it('calculates mixed element + compound weight', () => { const inv = new Inventory(); inv.addItem('Na', 1); // 22.99 inv.addItem('NaCl', 1); // 58.44 expect(inv.getTotalWeight()).toBeCloseTo(22.99 + 58.44); }); it('limits additions by weight', () => { const inv = new Inventory(100); // 100 AMU limit // Na mass = 22.99 → max 4 fit (4 * 22.99 = 91.96) const added = inv.addItem('Na', 10); expect(added).toBe(4); expect(inv.getCount('Na')).toBe(4); expect(inv.getTotalWeight()).toBeCloseTo(22.99 * 4); }); it('weight frees up after removal', () => { const inv = new Inventory(100); inv.addItem('Na', 4); // 91.96 inv.removeItem('Na', 2); // now 45.98 used, ~54 free const added = inv.addItem('Na', 3); // 2 more fit (45.98 + 2*22.99 = 91.96) expect(added).toBe(2); expect(inv.getCount('Na')).toBe(4); }); it('getItemWeight returns stack weight', () => { const inv = new Inventory(); inv.addItem('Na', 3); expect(inv.getItemWeight('Na')).toBeCloseTo(22.99 * 3); }); it('getItemWeight returns 0 for absent item', () => { const inv = new Inventory(); expect(inv.getItemWeight('H')).toBe(0); }); }); describe('Inventory — slot limits', () => { it('respects max slots', () => { const inv = new Inventory(10000, 3); inv.addItem('H', 1); inv.addItem('O', 1); inv.addItem('Na', 1); const added = inv.addItem('Fe', 1); // 4th slot expect(added).toBe(0); expect(inv.slotCount).toBe(3); expect(inv.hasItem('Fe')).toBe(false); }); it('frees slot after full removal', () => { const inv = new Inventory(10000, 2); inv.addItem('H', 1); inv.addItem('O', 1); expect(inv.addItem('Na', 1)).toBe(0); // full inv.removeItem('O', 1); // free a slot expect(inv.addItem('Na', 1)).toBe(1); // now fits expect(inv.slotCount).toBe(2); }); }); describe('Inventory — queries', () => { it('hasItem checks against count', () => { const inv = new Inventory(); inv.addItem('H', 3); expect(inv.hasItem('H', 1)).toBe(true); expect(inv.hasItem('H', 3)).toBe(true); expect(inv.hasItem('H', 4)).toBe(false); expect(inv.hasItem('O')).toBe(false); }); it('getItems returns all items', () => { const inv = new Inventory(); inv.addItem('H', 3); inv.addItem('Na', 2); const items = inv.getItems(); expect(items).toHaveLength(2); expect(items).toContainEqual({ id: 'H', count: 3 }); expect(items).toContainEqual({ id: 'Na', count: 2 }); }); it('getCount returns 0 for absent item', () => { const inv = new Inventory(); expect(inv.getCount('H')).toBe(0); }); }); describe('Inventory — clear', () => { it('removes everything', () => { const inv = new Inventory(); inv.addItem('H', 3); inv.addItem('Na', 2); inv.addItem('NaCl', 1); inv.clear(); expect(inv.isEmpty()).toBe(true); expect(inv.getTotalWeight()).toBe(0); expect(inv.slotCount).toBe(0); expect(inv.getItems()).toEqual([]); }); });