import { describe, it, expect, beforeEach } from 'vitest'; import { EncounterService } from '../lib/db/encounterService'; import { db } from '../lib/db'; import { EncounterType } from '../lib/db/schema'; import type { PicletInstance } from '../lib/db/schema'; describe('EncounterService', () => { beforeEach(async () => { // Clear all data before each test await db.picletInstances.clear(); await db.encounters.clear(); await db.gameState.clear(); }); describe('generateEncounters', () => { it('should return empty array when no piclets are discovered', async () => { // Arrange - ensure database is empty const picletCount = await db.picletInstances.count(); expect(picletCount).toBe(0); // Act const encounters = await EncounterService.generateEncounters(); // Assert expect(encounters).toHaveLength(0); // Verify encounters table is also empty const dbEncounters = await db.encounters.toArray(); expect(dbEncounters).toHaveLength(0); }); it('should return only "Your First Piclet!" when piclets are discovered but not caught', async () => { // Arrange - add a discovered piclet (uncaught) const testPiclet: Omit = { typeId: 'test-piclet', nickname: 'Test Piclet', primaryType: 'beast' as any, currentHp: 100, maxHp: 100, level: 5, xp: 0, attack: 50, defense: 50, fieldAttack: 40, fieldDefense: 40, speed: 50, baseHp: 100, baseAttack: 50, baseDefense: 50, baseFieldAttack: 40, baseFieldDefense: 40, baseSpeed: 50, moves: [], nature: 'hardy', specialAbility: { name: 'Test', description: 'Test', trigger: 'onBattleStart', effects: [] }, specialAbilityUnlockLevel: 1, isInRoster: false, caught: false, bst: 300, tier: 'low', role: 'balanced', variance: 0, imageUrl: 'https://test.com/piclet.png', imageCaption: 'test caption', concept: 'test', description: 'A test piclet', imagePrompt: 'test prompt' }; await db.picletInstances.add(testPiclet); // Act const encounters = await EncounterService.generateEncounters(); // Assert expect(encounters).toHaveLength(1); expect(encounters[0].type).toBe(EncounterType.WILD_PICLET); expect(encounters[0].title).toBe('Your First Piclet!'); expect(encounters[0].description).toBe('A friendly piclet appears! This one seems easy to catch.'); expect(encounters[0].enemyLevel).toBe(5); }); it('should return shop, health center, and wild encounters when player has caught piclets', async () => { // Arrange - add a caught piclet const testPiclet: Omit = { typeId: 'test-001', nickname: 'Testy', primaryType: 'beast' as any, level: 5, xp: 0, currentHp: 20, maxHp: 20, attack: 10, defense: 10, fieldAttack: 10, fieldDefense: 10, speed: 10, baseHp: 20, baseAttack: 10, baseDefense: 10, baseFieldAttack: 10, baseFieldDefense: 10, baseSpeed: 10, moves: [], nature: 'hardy', isInRoster: true, rosterPosition: 0, caughtAt: new Date(), bst: 60, tier: 'common', role: 'balanced', variance: 1, imageUrl: 'https://test.com/piclet.png', imageCaption: 'Test', concept: 'test', imagePrompt: 'test' }; await db.picletInstances.add(testPiclet); // Add some discovered monsters so wild encounters can be generated const testMonster = { name: 'Test Monster', imageUrl: 'https://test.com/monster.png', imageCaption: 'A test monster', concept: 'test concept', imagePrompt: 'test prompt', createdAt: new Date() }; await db.monsters.add(testMonster); // Act const encounters = await EncounterService.generateEncounters(); // Assert expect(encounters.length).toBeGreaterThanOrEqual(4); // At least shop, health center, and 2 wild // Check for required encounter types const encounterTypes = encounters.map(e => e.type); expect(encounterTypes).toContain(EncounterType.SHOP); expect(encounterTypes).toContain(EncounterType.HEALTH_CENTER); // Count wild encounters const wildCount = encounters.filter(e => e.type === EncounterType.WILD_PICLET).length; expect(wildCount).toBeGreaterThanOrEqual(2); expect(wildCount).toBeLessThanOrEqual(3); // Verify shop encounter details const shopEncounter = encounters.find(e => e.type === EncounterType.SHOP); expect(shopEncounter?.title).toBe('Piclet Shop'); // Verify health center encounter details const healthEncounter = encounters.find(e => e.type === EncounterType.HEALTH_CENTER); expect(healthEncounter?.title).toBe('Health Center'); }); it('should not include shop/health center with first catch encounter', async () => { // Arrange - add only a discovered piclet, no caught piclets await db.monsters.add({ name: 'Discovered Piclet', imageUrl: 'https://test.com/discovered.png', concept: 'discovered', imagePrompt: 'discovered prompt', imageCaption: 'discovered caption', createdAt: new Date() }); // Ensure no caught piclets const caughtCount = await db.picletInstances.count(); expect(caughtCount).toBe(0); // Act const encounters = await EncounterService.generateEncounters(); // Assert - should only have the first catch encounter expect(encounters).toHaveLength(1); expect(encounters[0].title).toBe('Your First Piclet!'); // Should NOT have shop or health center const hasShop = encounters.some(e => e.type === EncounterType.SHOP); const hasHealthCenter = encounters.some(e => e.type === EncounterType.HEALTH_CENTER); expect(hasShop).toBe(false); expect(hasHealthCenter).toBe(false); }); }); describe('shouldRefreshEncounters', () => { it('should return true after 2 hours', async () => { // Arrange - create game state with old refresh time const twoHoursAgo = new Date(Date.now() - (2.5 * 60 * 60 * 1000)); await db.gameState.add({ lastEncounterRefresh: twoHoursAgo, lastPlayed: new Date(), progressPoints: 0, trainersDefeated: 0, picletsCapured: 0, battlesLost: 0 }); // Act const shouldRefresh = await EncounterService.shouldRefreshEncounters(); // Assert expect(shouldRefresh).toBe(true); }); it('should return false within 2 hours', async () => { // Arrange - create game state with recent refresh time const oneHourAgo = new Date(Date.now() - (1 * 60 * 60 * 1000)); await db.gameState.add({ lastEncounterRefresh: oneHourAgo, lastPlayed: new Date(), progressPoints: 0, trainersDefeated: 0, picletsCapured: 0, battlesLost: 0 }); // Act const shouldRefresh = await EncounterService.shouldRefreshEncounters(); // Assert expect(shouldRefresh).toBe(false); }); }); });