/** * Crafting System Tests — Phase 4.4 * * Tests: crafting from inventory, reagent consumption, * product addition, failure reasons, condition checks. */ import { describe, it, expect } from 'vitest'; import { Inventory } from '../src/player/inventory'; import { craftFromInventory, type CraftInput } from '../src/player/crafting'; describe('craftFromInventory — success', () => { it('crafts NaCl from Na + Cl', () => { const inv = new Inventory(); inv.addItem('Na', 3); inv.addItem('Cl', 2); const inputs: CraftInput[] = [ { id: 'Na', count: 1 }, { id: 'Cl', count: 1 }, ]; const result = craftFromInventory(inv, inputs); expect(result.success).toBe(true); expect(result.products).toContainEqual({ id: 'NaCl', count: 1 }); // Reagents consumed expect(inv.getCount('Na')).toBe(2); expect(inv.getCount('Cl')).toBe(1); // Product added expect(inv.getCount('NaCl')).toBe(1); }); it('returns reaction data on success', () => { const inv = new Inventory(); inv.addItem('Na', 1); inv.addItem('Cl', 1); const result = craftFromInventory(inv, [ { id: 'Na', count: 1 }, { id: 'Cl', count: 1 }, ]); expect(result.success).toBe(true); expect(result.reaction).toBeDefined(); expect(result.reaction!.id).toBe('synth_nacl'); }); it('reports consumed reactants', () => { const inv = new Inventory(); inv.addItem('Na', 5); inv.addItem('Cl', 5); const inputs: CraftInput[] = [ { id: 'Na', count: 1 }, { id: 'Cl', count: 1 }, ]; const result = craftFromInventory(inv, inputs); expect(result.consumedReactants).toEqual(inputs); }); }); describe('craftFromInventory — insufficient materials', () => { it('fails when missing reagent', () => { const inv = new Inventory(); inv.addItem('Na', 1); // No Cl in inventory const result = craftFromInventory(inv, [ { id: 'Na', count: 1 }, { id: 'Cl', count: 1 }, ]); expect(result.success).toBe(false); expect(result.failureReason).toContain('Cl'); // Na not consumed expect(inv.getCount('Na')).toBe(1); }); it('fails when not enough of a reagent', () => { const inv = new Inventory(); inv.addItem('Na', 1); inv.addItem('Cl', 1); const result = craftFromInventory(inv, [ { id: 'Na', count: 5 }, // need 5, have 1 { id: 'Cl', count: 1 }, ]); expect(result.success).toBe(false); expect(result.failureReason).toContain('Na'); }); }); describe('craftFromInventory — unknown reaction', () => { it('fails with educational reason for no known reaction', () => { const inv = new Inventory(5000); // large capacity for heavy elements inv.addItem('Fe', 5); inv.addItem('Au', 5); const result = craftFromInventory(inv, [ { id: 'Fe', count: 1 }, { id: 'Au', count: 1 }, ]); expect(result.success).toBe(false); expect(result.failureReason).toBeDefined(); expect(result.failureReason!.length).toBeGreaterThan(10); // educational reason // Reagents not consumed expect(inv.getCount('Fe')).toBe(5); expect(inv.getCount('Au')).toBe(5); }); it('explains noble gas inertness', () => { const inv = new Inventory(); inv.addItem('He', 5); inv.addItem('O', 5); const result = craftFromInventory(inv, [ { id: 'He', count: 1 }, { id: 'O', count: 1 }, ]); expect(result.success).toBe(false); expect(result.failureReason).toContain('noble gas'); }); }); describe('craftFromInventory — conditions', () => { it('fails when temperature requirement not met', () => { const inv = new Inventory(); inv.addItem('Fe', 5); inv.addItem('S', 5); // Fe + S → FeS requires minTemp: 500 const result = craftFromInventory(inv, [ { id: 'Fe', count: 1 }, { id: 'S', count: 1 }, ]); expect(result.success).toBe(false); expect(result.failureReason).toContain('temperature'); // Reagents not consumed expect(inv.getCount('Fe')).toBe(5); }); it('succeeds when conditions are met', () => { const inv = new Inventory(); inv.addItem('Fe', 5); inv.addItem('S', 5); const result = craftFromInventory(inv, [ { id: 'Fe', count: 1 }, { id: 'S', count: 1 }, ], { minTemp: 500 }); expect(result.success).toBe(true); expect(result.products).toContainEqual({ id: 'FeS', count: 1 }); expect(inv.getCount('Fe')).toBe(4); expect(inv.getCount('S')).toBe(4); }); }); describe('craftFromInventory — edge cases', () => { it('handles empty reactants list', () => { const inv = new Inventory(); inv.addItem('Na', 5); const result = craftFromInventory(inv, []); expect(result.success).toBe(false); }); it('handles inventory full for products', () => { const inv = new Inventory(100, 2); // Only 2 slots inv.addItem('Na', 1); inv.addItem('Cl', 1); // After crafting: Na consumed (slot freed), Cl consumed (slot freed), NaCl added // This should work since slots are freed before products are added const result = craftFromInventory(inv, [ { id: 'Na', count: 1 }, { id: 'Cl', count: 1 }, ]); expect(result.success).toBe(true); expect(inv.getCount('NaCl')).toBe(1); }); });