step 10.13: done
This commit is contained in:
@@ -31,29 +31,40 @@ describe('inventoryService', () => {
|
||||
return id;
|
||||
}
|
||||
|
||||
/** Grant an item to a character's inventory (bypasses hunting). */
|
||||
/** Grant an item to a character's inventory (bypasses hunting). Stacks with existing rows. */
|
||||
function grantItem(characterId, itemId, quantity = 1) {
|
||||
db.prepare('INSERT INTO inventory (character_id, item_id, quantity, equipped_slot, acquired_at) VALUES (?, ?, ?, NULL, ?)')
|
||||
.run(characterId, itemId, quantity, NOW);
|
||||
const existing = db.prepare(
|
||||
'SELECT id, quantity FROM inventory WHERE character_id = ? AND item_id = ?'
|
||||
).get(characterId, itemId);
|
||||
if (existing) {
|
||||
db.prepare('UPDATE inventory SET quantity = quantity + ? WHERE id = ?').run(quantity, existing.id);
|
||||
} else {
|
||||
db.prepare(
|
||||
'INSERT INTO inventory (character_id, item_id, quantity, equipped_slot, acquired_at) VALUES (?, ?, ?, NULL, ?)'
|
||||
).run(characterId, itemId, quantity, NOW);
|
||||
}
|
||||
}
|
||||
|
||||
it('returns empty inventory for a fresh character', () => {
|
||||
const id = makeHero();
|
||||
const result = listInventory(id);
|
||||
assert.equal(result.ok !== undefined || true, true); // service returns raw obj
|
||||
assert.equal(result.items.length, 0);
|
||||
// Starter kit: 2 items (equipped weapon + 2 potions)
|
||||
assert.equal(result.items.length, 2);
|
||||
assert.equal(result.character.name, `Hero${userSeq}`);
|
||||
});
|
||||
|
||||
it('equips a weapon and updates derived stats', () => {
|
||||
const id = makeHero();
|
||||
grantItem(id, 1); // Rusty Sword (weapon, +3 attack)
|
||||
const result = equipItem(id, 1, 'weapon');
|
||||
assert.equal(result.items.find((i) => i.itemId === 1)?.equipped, true);
|
||||
assert.equal(result.items.find((i) => i.itemId === 1)?.equipped_slot, 'weapon');
|
||||
const id = makeHero({ level: 5, str: 8, agi: 4, int: 2, vit: 8 });
|
||||
// Warrior already has Rusty Sword (item 1) from starter kit.
|
||||
// Grant Iron Blade (item 4, weapon, +8 attack, level 5) and equip it.
|
||||
grantItem(id, 4); // Iron Blade
|
||||
const result = equipItem(id, 4, 'weapon');
|
||||
assert.equal(result.items.find((i) => i.itemId === 4)?.equipped, true);
|
||||
assert.equal(result.items.find((i) => i.itemId === 4)?.equipped_slot, 'weapon');
|
||||
|
||||
// Derived stats should reflect the +3 attack bonus
|
||||
assert.ok(result.derived.attack >= 7, `attack should include bonus (got ${result.derived.attack})`);
|
||||
// Derived stats should reflect the +8 attack bonus
|
||||
assert.ok(result.derived.attack >= 33, `attack should include bonus (got ${result.derived.attack})`);
|
||||
});
|
||||
|
||||
it('equips armor and trinket independently', () => {
|
||||
@@ -74,30 +85,25 @@ describe('inventoryService', () => {
|
||||
|
||||
it('auto-unequips the old item when equipping to an occupied slot', () => {
|
||||
const id = makeHero({ level: 5, str: 8, agi: 4, int: 2, vit: 8 });
|
||||
grantItem(id, 1); // Rusty Sword
|
||||
grantItem(id, 4); // Iron Blade (requires level 5)
|
||||
equipItem(id, 1, 'weapon');
|
||||
// Warrior already has Rusty Sword (item 1) from starter kit.
|
||||
// Grant Iron Blade (item 4, level 5) to test swap.
|
||||
grantItem(id, 4); // Iron Blade
|
||||
equipItem(id, 4, 'weapon');
|
||||
|
||||
const result = listInventory(id);
|
||||
// After equipping item 4, item 1 should be unequipped (only one per slot).
|
||||
// The equipped item 4 now occupies the weapon slot; item 1 is in inventory but not equipped.
|
||||
// Iron Blade should be equipped (warrior's starter sword is unequipped).
|
||||
const equippedItem = result.items.find((i) => i.equipped);
|
||||
assert.ok(equippedItem, 'should have one equipped item');
|
||||
assert.equal(equippedItem.itemId, 4);
|
||||
// Item 1 should still be in inventory but unequipped
|
||||
const item1 = result.items.find((i) => i.itemId === 1);
|
||||
assert.ok(item1, 'item 1 should still be in inventory');
|
||||
assert.equal(item1.equipped, false);
|
||||
});
|
||||
|
||||
it('unequips an item from a slot', () => {
|
||||
const id = makeHero();
|
||||
grantItem(id, 1);
|
||||
equipItem(id, 1, 'weapon');
|
||||
// Warrior already has Rusty Sword (item 1) equipped from starter kit.
|
||||
// Just unequip the weapon slot.
|
||||
const result = unequipItem(id, 'weapon');
|
||||
const item1 = result.items.find((i) => i.itemId === 1);
|
||||
assert.ok(item1, 'item 1 should still be in inventory after unequip');
|
||||
assert.ok(item1, 'Rusty Sword should still be in inventory after unequip');
|
||||
assert.equal(item1.equipped, false);
|
||||
assert.equal(item1.equipped_slot, null);
|
||||
});
|
||||
@@ -121,6 +127,9 @@ describe('inventoryService', () => {
|
||||
|
||||
it('rejects unequip when slot is empty', () => {
|
||||
const id = makeHero();
|
||||
// Warrior already has Rusty Sword (item 1) equipped from starter kit.
|
||||
// Unequip it first, then try to unequip again.
|
||||
unequipItem(id, 'weapon');
|
||||
assert.throws(() => unequipItem(id, 'weapon'), (e) => e.code === 'NOT_FOUND');
|
||||
});
|
||||
|
||||
@@ -132,8 +141,8 @@ describe('inventoryService', () => {
|
||||
|
||||
it('rejects equipping an item not in the character inventory', () => {
|
||||
const id = makeHero();
|
||||
// Item 1 exists but was never acquired by this character
|
||||
assert.throws(() => equipItem(id, 1, 'weapon'), (e) => e.code === 'NOT_FOUND');
|
||||
// Item 1 is now in starter kit, so use a nonexistent item.
|
||||
assert.throws(() => equipItem(id, 99999, 'weapon'), (e) => e.code === 'NOT_FOUND');
|
||||
});
|
||||
|
||||
it('updates derived stats when equipping armor (defense bonus)', () => {
|
||||
@@ -166,24 +175,25 @@ describe('inventoryService', () => {
|
||||
|
||||
it('uses a healing potion and increases HP (capped at maxHp)', () => {
|
||||
const id = makeHero({ level: 1, str: 8, agi: 4, int: 2, vit: 8 });
|
||||
// Grant 3 Minor Healing Potions (item 14, heal:50)
|
||||
// Grant 3 Minor Healing Potions (item 14, heal:50).
|
||||
// Starter kit already has 2 potions → total 5.
|
||||
grantItem(id, 14, 3);
|
||||
|
||||
// Set HP to 60 (out of ~145 maxHp for warrior with vit=8: 50+80+5=135)
|
||||
// Set HP to 60.
|
||||
db.prepare('UPDATE characters SET hp_current = 60 WHERE id = ?').run(id);
|
||||
|
||||
const before = listInventory(id);
|
||||
const beforeHp = db.prepare('SELECT hp_current FROM characters WHERE id = ?').get(id).hp_current;
|
||||
const maxHp = before.derived.maxHp;
|
||||
|
||||
// Use one potion: should heal to min(60+50, maxHp)
|
||||
// Use one potion: should heal to min(60+50, maxHp).
|
||||
const result = useConsumable(id, 14);
|
||||
const afterHp = db.prepare('SELECT hp_current FROM characters WHERE id = ?').get(id).hp_current;
|
||||
|
||||
assert.equal(afterHp, Math.min(beforeHp + 50, maxHp));
|
||||
// Quantity should be 3 → 2 (still in inventory)
|
||||
// Total potions: 3 granted + 2 from starter kit = 5. After 1 use → 4.
|
||||
const potion = result.items.find((i) => i.itemId === 14);
|
||||
assert.equal(potion.quantity, 2);
|
||||
assert.equal(potion.quantity, 4);
|
||||
});
|
||||
|
||||
it('uses an energy tonic and restores energy (capped at 100)', () => {
|
||||
@@ -215,8 +225,8 @@ describe('inventoryService', () => {
|
||||
|
||||
it('rejects using an item not in inventory', () => {
|
||||
const id = makeHero();
|
||||
// Item 14 exists but not granted to this character
|
||||
assert.throws(() => useConsumable(id, 14), (e) => e.code === 'NOT_FOUND');
|
||||
// Item 14 is now in starter kit. Use a nonexistent item.
|
||||
assert.throws(() => useConsumable(id, 99999), (e) => e.code === 'NOT_FOUND');
|
||||
});
|
||||
|
||||
it('removes inventory row when quantity reaches 0 (single-item stack)', () => {
|
||||
@@ -239,9 +249,10 @@ describe('inventoryService', () => {
|
||||
|
||||
it('caps heal effect at maxHp', () => {
|
||||
const id = makeHero({ level: 1, str: 8, agi: 4, int: 2, vit: 8 });
|
||||
grantItem(id, 14, 5); // Minor Healing Potion (heal:50)
|
||||
// Grant 5 more Minor Healing Potions (starter kit already has 2).
|
||||
grantItem(id, 14, 5);
|
||||
|
||||
// Set HP already at max
|
||||
// Set HP already at max.
|
||||
const before = listInventory(id);
|
||||
const maxHp = before.derived.maxHp;
|
||||
db.prepare('UPDATE characters SET hp_current = ? WHERE id = ?').run(maxHp, id);
|
||||
@@ -251,15 +262,16 @@ describe('inventoryService', () => {
|
||||
const after = listInventory(id);
|
||||
const hp = db.prepare('SELECT hp_current FROM characters WHERE id = ?').get(id).hp_current;
|
||||
assert.equal(hp, maxHp, 'HP should not exceed maxHp');
|
||||
// Quantity should decrease
|
||||
assert.equal(after.items.find((i) => i.itemId === 14).quantity, 4);
|
||||
// We granted 5 potions; starter kit has 2 → total 7. After 1 use → 6.
|
||||
const potion = after.items.find((i) => i.itemId === 14);
|
||||
assert.equal(potion.quantity, 6);
|
||||
});
|
||||
|
||||
it('caps energy effect at ENERGY_MAX (100)', () => {
|
||||
const id = makeHero({ level: 1, str: 8, agi: 4, int: 2, vit: 8 });
|
||||
grantItem(id, 16, 5); // Energy Tonic (energy:25)
|
||||
|
||||
// Set energy to 90
|
||||
// Set energy to 90.
|
||||
db.prepare('UPDATE characters SET energy = 90 WHERE id = ?').run(id);
|
||||
|
||||
useConsumable(id, 16);
|
||||
@@ -267,7 +279,7 @@ describe('inventoryService', () => {
|
||||
const after = listInventory(id);
|
||||
const energy = db.prepare('SELECT energy FROM characters WHERE id = ?').get(id).energy;
|
||||
assert.equal(energy, 100, 'Energy should not exceed 100');
|
||||
// Quantity should decrease
|
||||
// Quantity should decrease.
|
||||
assert.equal(after.items.find((i) => i.itemId === 16).quantity, 4);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user