feat: recreate Equipment Tab with equip/unequip gear management
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
|
||||
// ─── Test: EquipmentTab barrel export ──────────────────────────────────────────
|
||||
|
||||
describe('EquipmentTab module structure', () => {
|
||||
it('exports EquipmentTab from barrel index', async () => {
|
||||
const mod = await import('./EquipmentTab');
|
||||
expect(mod.EquipmentTab).toBeDefined();
|
||||
expect(typeof mod.EquipmentTab).toBe('function');
|
||||
});
|
||||
|
||||
it('EquipmentTab has correct displayName', async () => {
|
||||
const { EquipmentTab } = await import('./EquipmentTab');
|
||||
expect(EquipmentTab.displayName).toBe('EquipmentTab');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Test: Barrel export includes EquipmentTab ─────────────────────────────────
|
||||
|
||||
describe('Tab barrel export', () => {
|
||||
it('includes EquipmentTab in the tabs index', async () => {
|
||||
const mod = await import('@/components/game/tabs');
|
||||
expect(mod.EquipmentTab).toBeDefined();
|
||||
expect(typeof mod.EquipmentTab).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Test: Equipment slot definitions ──────────────────────────────────────────
|
||||
|
||||
describe('Equipment slot definitions', () => {
|
||||
it('has exactly 8 equipment slots', async () => {
|
||||
const { EQUIPMENT_SLOTS } = await import('@/lib/game/data/equipment');
|
||||
expect(EQUIPMENT_SLOTS).toHaveLength(8);
|
||||
});
|
||||
|
||||
it('all slots have display names', async () => {
|
||||
const { SLOT_NAMES, EQUIPMENT_SLOTS } = await import('@/lib/game/data/equipment');
|
||||
for (const slot of EQUIPMENT_SLOTS) {
|
||||
expect(SLOT_NAMES[slot]).toBeDefined();
|
||||
expect(SLOT_NAMES[slot].length).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('includes mainHand, offHand, head, body, hands, feet, accessory1, accessory2', async () => {
|
||||
const { EQUIPMENT_SLOTS } = await import('@/lib/game/data/equipment');
|
||||
expect(EQUIPMENT_SLOTS).toContain('mainHand');
|
||||
expect(EQUIPMENT_SLOTS).toContain('offHand');
|
||||
expect(EQUIPMENT_SLOTS).toContain('head');
|
||||
expect(EQUIPMENT_SLOTS).toContain('body');
|
||||
expect(EQUIPMENT_SLOTS).toContain('hands');
|
||||
expect(EQUIPMENT_SLOTS).toContain('feet');
|
||||
expect(EQUIPMENT_SLOTS).toContain('accessory1');
|
||||
expect(EQUIPMENT_SLOTS).toContain('accessory2');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Test: Equipment type definitions ──────────────────────────────────────────
|
||||
|
||||
describe('Equipment type definitions', () => {
|
||||
it('all equipment types have required fields', async () => {
|
||||
const { EQUIPMENT_TYPES } = await import('@/lib/game/data/equipment');
|
||||
for (const [id, type] of Object.entries(EQUIPMENT_TYPES)) {
|
||||
expect(type.id).toBe(id);
|
||||
expect(type.name).toBeTruthy();
|
||||
expect(type.category).toBeTruthy();
|
||||
expect(type.slot).toBeTruthy();
|
||||
expect(type.baseCapacity).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('accessory category types can equip in accessory slots', async () => {
|
||||
const { EQUIPMENT_TYPES, getValidSlotsForEquipmentType } = await import('@/lib/game/data/equipment');
|
||||
const accessories = Object.values(EQUIPMENT_TYPES).filter((t) => t.category === 'accessory');
|
||||
expect(accessories.length).toBeGreaterThan(0);
|
||||
for (const acc of accessories) {
|
||||
const slots = getValidSlotsForEquipmentType(acc);
|
||||
expect(slots).toContain('accessory1');
|
||||
expect(slots).toContain('accessory2');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Test: Starting equipment ──────────────────────────────────────────────────
|
||||
|
||||
describe('Starting equipment', () => {
|
||||
it('createStartingEquipment returns valid equippedInstances', async () => {
|
||||
const { createStartingEquipment } = await import('@/lib/game/crafting-slice');
|
||||
const { equippedInstances, equipmentInstances } = createStartingEquipment();
|
||||
|
||||
expect(equippedInstances.mainHand).toBeTruthy();
|
||||
expect(equippedInstances.body).toBeTruthy();
|
||||
expect(equippedInstances.feet).toBeTruthy();
|
||||
expect(equippedInstances.offHand).toBeNull();
|
||||
expect(equippedInstances.head).toBeNull();
|
||||
expect(equippedInstances.hands).toBeNull();
|
||||
expect(equippedInstances.accessory1).toBeNull();
|
||||
expect(equippedInstances.accessory2).toBeNull();
|
||||
|
||||
expect(Object.keys(equipmentInstances).length).toBe(3);
|
||||
});
|
||||
|
||||
it('starting equipment instances have valid fields', async () => {
|
||||
const { createStartingEquipment } = await import('@/lib/game/crafting-slice');
|
||||
const { equipmentInstances } = createStartingEquipment();
|
||||
|
||||
for (const instance of Object.values(equipmentInstances)) {
|
||||
expect(instance.instanceId).toBeTruthy();
|
||||
expect(instance.typeId).toBeTruthy();
|
||||
expect(instance.name).toBeTruthy();
|
||||
expect(instance.rarity).toBe('common');
|
||||
expect(instance.quality).toBe(100);
|
||||
expect(instance.usedCapacity).toBeGreaterThanOrEqual(0);
|
||||
expect(instance.totalCapacity).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Test: Equipment actions ───────────────────────────────────────────────────
|
||||
|
||||
describe('Equipment actions', () => {
|
||||
it('equipItem is a function', async () => {
|
||||
const { equipItem } = await import('@/lib/game/crafting-actions/equipment-actions');
|
||||
expect(typeof equipItem).toBe('function');
|
||||
});
|
||||
|
||||
it('unequipItem is a function', async () => {
|
||||
const { unequipItem } = await import('@/lib/game/crafting-actions/equipment-actions');
|
||||
expect(typeof unequipItem).toBe('function');
|
||||
});
|
||||
|
||||
it('deleteEquipmentInstance is a function', async () => {
|
||||
const { deleteEquipmentInstance } = await import('@/lib/game/crafting-actions/equipment-actions');
|
||||
expect(typeof deleteEquipmentInstance).toBe('function');
|
||||
});
|
||||
|
||||
it('createEquipmentInstance is a function', async () => {
|
||||
const { createEquipmentInstance } = await import('@/lib/game/crafting-actions/equipment-actions');
|
||||
expect(typeof createEquipmentInstance).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Test: Equipment effects ───────────────────────────────────────────────────
|
||||
|
||||
describe('Equipment effects computation', () => {
|
||||
it('computeEquipmentEffects returns empty for no equipment', async () => {
|
||||
const { computeEquipmentEffects } = await import('@/lib/game/effects');
|
||||
const result = computeEquipmentEffects({}, { mainHand: null, offHand: null, head: null, body: null, hands: null, feet: null, accessory1: null, accessory2: null });
|
||||
expect(Object.keys(result.bonuses)).toHaveLength(0);
|
||||
expect(Object.keys(result.multipliers)).toHaveLength(0);
|
||||
expect(result.specials.size).toBe(0);
|
||||
});
|
||||
|
||||
it('computeEquipmentEffects detects enchantment bonuses', async () => {
|
||||
const { computeEquipmentEffects } = await import('@/lib/game/effects');
|
||||
const instances = {
|
||||
test1: {
|
||||
instanceId: 'test1',
|
||||
typeId: 'basicStaff',
|
||||
name: 'Test Staff',
|
||||
enchantments: [{ effectId: 'spell_manaBolt', stacks: 1, actualCost: 50 }],
|
||||
usedCapacity: 50,
|
||||
totalCapacity: 50,
|
||||
rarity: 'common' as const,
|
||||
quality: 100,
|
||||
tags: [],
|
||||
},
|
||||
};
|
||||
const equipped = { mainHand: 'test1', offHand: null, head: null, body: null, hands: null, feet: null, accessory1: null, accessory2: null };
|
||||
const result = computeEquipmentEffects(instances, equipped);
|
||||
// Should at least not crash and return a valid structure
|
||||
expect(result).toHaveProperty('bonuses');
|
||||
expect(result).toHaveProperty('multipliers');
|
||||
expect(result).toHaveProperty('specials');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Test: File size limits ────────────────────────────────────────────────────
|
||||
|
||||
describe('File size limits (400 lines max)', () => {
|
||||
it('EquipmentTab.tsx is under 400 lines', async () => {
|
||||
const fs = await import('fs');
|
||||
const path = await import('path');
|
||||
const filePath = path.join(__dirname, 'EquipmentTab.tsx');
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const lines = content.split('\n').length;
|
||||
expect(lines).toBeLessThan(400);
|
||||
});
|
||||
|
||||
it('EquipmentSlotGrid.tsx is under 400 lines', async () => {
|
||||
const fs = await import('fs');
|
||||
const path = await import('path');
|
||||
const filePath = path.join(__dirname, 'EquipmentTab/EquipmentSlotGrid.tsx');
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const lines = content.split('\n').length;
|
||||
expect(lines).toBeLessThan(400);
|
||||
});
|
||||
|
||||
it('InventoryList.tsx is under 400 lines', async () => {
|
||||
const fs = await import('fs');
|
||||
const path = await import('path');
|
||||
const filePath = path.join(__dirname, 'EquipmentTab/InventoryList.tsx');
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const lines = content.split('\n').length;
|
||||
expect(lines).toBeLessThan(400);
|
||||
});
|
||||
|
||||
it('EquipmentEffectsSummary.tsx is under 400 lines', async () => {
|
||||
const fs = await import('fs');
|
||||
const path = await import('path');
|
||||
const filePath = path.join(__dirname, 'EquipmentTab/EquipmentEffectsSummary.tsx');
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const lines = content.split('\n').length;
|
||||
expect(lines).toBeLessThan(400);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user