Files
synthesis/tests/crafting.test.ts
Денис Шкабатур b097ce738f feat: crafting system with chemistry engine integration (Phase 4.4)
craftFromInventory checks reagent availability, runs ReactionEngine,
consumes reagents on success, adds products. Failed attempts return
educational reasons (noble gas inertness, missing conditions, etc.).
11 new tests (185 total).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 13:27:22 +03:00

190 lines
5.2 KiB
TypeScript

/**
* 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);
});
});