Skip to main content
Glama
encounter-presets.test.ts10.6 kB
import { describe, it, expect } from 'vitest'; import { ENCOUNTER_PRESETS, getEncounterPreset, listEncounterPresets, getEncountersByDifficulty, getEncountersByTag, getEncountersForLevel, scaleEncounter, getRandomEncounter, } from '../../src/data/encounter-presets'; import { getCreaturePreset, expandCreatureTemplate } from '../../src/data/creature-presets'; describe('encounter-presets', () => { describe('ENCOUNTER_PRESETS', () => { it('should have multiple presets defined', () => { const presetCount = Object.keys(ENCOUNTER_PRESETS).length; expect(presetCount).toBeGreaterThan(15); }); it('all presets should have required fields', () => { for (const [id, preset] of Object.entries(ENCOUNTER_PRESETS)) { expect(preset.id).toBe(id); expect(preset.name).toBeTruthy(); expect(preset.description).toBeTruthy(); expect(['easy', 'medium', 'hard', 'deadly']).toContain(preset.difficulty); expect(preset.recommendedLevel.min).toBeGreaterThanOrEqual(1); expect(preset.recommendedLevel.max).toBeLessThanOrEqual(20); expect(preset.recommendedLevel.min).toBeLessThanOrEqual(preset.recommendedLevel.max); expect(preset.participants.length).toBeGreaterThan(0); expect(preset.tags.length).toBeGreaterThan(0); } }); it('all participant templates should reference valid creature presets', () => { for (const preset of Object.values(ENCOUNTER_PRESETS)) { for (const participant of preset.participants) { const expanded = expandCreatureTemplate(participant.template); expect(expanded, `Invalid template: ${participant.template} in ${preset.id}`).not.toBeNull(); } } }); it('all participant positions should be valid format', () => { const positionRegex = /^\d+,\d+$/; for (const preset of Object.values(ENCOUNTER_PRESETS)) { for (const participant of preset.participants) { expect( participant.position, `Invalid position format in ${preset.id}` ).toMatch(positionRegex); } } }); it('party positions should be valid format when present', () => { const positionRegex = /^\d+,\d+$/; for (const preset of Object.values(ENCOUNTER_PRESETS)) { if (preset.partyPositions) { for (const pos of preset.partyPositions) { expect(pos, `Invalid party position in ${preset.id}`).toMatch(positionRegex); } } } }); }); describe('getEncounterPreset', () => { it('should return preset by exact ID', () => { const preset = getEncounterPreset('goblin_ambush'); expect(preset).not.toBeNull(); expect(preset?.name).toBe('Goblin Ambush'); }); it('should normalize spaces and hyphens', () => { const preset1 = getEncounterPreset('goblin-ambush'); const preset2 = getEncounterPreset('goblin ambush'); expect(preset1).not.toBeNull(); expect(preset2).not.toBeNull(); expect(preset1?.id).toBe(preset2?.id); }); it('should return null for unknown preset', () => { const preset = getEncounterPreset('nonexistent_encounter'); expect(preset).toBeNull(); }); }); describe('listEncounterPresets', () => { it('should return array of preset IDs', () => { const presets = listEncounterPresets(); expect(presets).toContain('goblin_ambush'); expect(presets).toContain('tavern_brawl'); expect(presets).toContain('dragon_wyrmling_lair'); }); }); describe('getEncountersByDifficulty', () => { it('should filter by easy difficulty', () => { const easy = getEncountersByDifficulty('easy'); expect(easy.length).toBeGreaterThan(0); expect(easy.every(e => e.difficulty === 'easy')).toBe(true); }); it('should filter by deadly difficulty', () => { const deadly = getEncountersByDifficulty('deadly'); expect(deadly.length).toBeGreaterThan(0); expect(deadly.every(e => e.difficulty === 'deadly')).toBe(true); }); }); describe('getEncountersByTag', () => { it('should filter by goblin tag', () => { const goblin = getEncountersByTag('goblin'); expect(goblin.length).toBeGreaterThan(0); expect(goblin.every(e => e.tags.includes('goblin'))).toBe(true); }); it('should filter by undead tag', () => { const undead = getEncountersByTag('undead'); expect(undead.length).toBeGreaterThan(0); }); it('should be case insensitive', () => { const upper = getEncountersByTag('GOBLIN'); const lower = getEncountersByTag('goblin'); expect(upper.length).toBe(lower.length); }); }); describe('getEncountersForLevel', () => { it('should return encounters for level 1', () => { const level1 = getEncountersForLevel(1); expect(level1.length).toBeGreaterThan(0); expect(level1.every(e => e.recommendedLevel.min <= 1 && e.recommendedLevel.max >= 1)).toBe(true); }); it('should return encounters for level 5', () => { const level5 = getEncountersForLevel(5); expect(level5.length).toBeGreaterThan(0); }); it('should return fewer encounters for very high levels', () => { const level15 = getEncountersForLevel(15); const level1 = getEncountersForLevel(1); // Most presets are for lower levels expect(level15.length).toBeLessThanOrEqual(level1.length); }); }); describe('scaleEncounter', () => { it('should not modify original preset', () => { const original = getEncounterPreset('goblin_ambush')!; const originalCount = original.participants.length; const scaled = scaleEncounter(original, 3, 6); expect(original.participants.length).toBe(originalCount); expect(scaled).not.toBe(original); }); it('should add minions for larger parties', () => { const preset = getEncounterPreset('goblin_ambush')!; const scaled = scaleEncounter(preset, 3, 6); // 6 players instead of 4 expect(scaled.participants.length).toBeGreaterThan(preset.participants.length); }); it('should not add minions for 4-player party', () => { const preset = getEncounterPreset('goblin_ambush')!; const scaled = scaleEncounter(preset, 3, 4); expect(scaled.participants.length).toBe(preset.participants.length); }); it('should not scale boss creatures', () => { const preset = getEncounterPreset('goblin_lair')!; const bossCount = preset.participants.filter(p => p.template.includes('boss')).length; const scaled = scaleEncounter(preset, 3, 8); const scaledBossCount = scaled.participants.filter(p => p.template.includes('boss')).length; expect(scaledBossCount).toBe(bossCount); }); }); describe('getRandomEncounter', () => { it('should return a preset when no options provided', () => { const encounter = getRandomEncounter(); expect(encounter).not.toBeNull(); }); it('should filter by difficulty', () => { const encounter = getRandomEncounter({ difficulty: 'easy' }); expect(encounter).not.toBeNull(); expect(encounter?.difficulty).toBe('easy'); }); it('should filter by level', () => { const encounter = getRandomEncounter({ level: 1 }); expect(encounter).not.toBeNull(); if (encounter) { expect(encounter.recommendedLevel.min).toBeLessThanOrEqual(1); expect(encounter.recommendedLevel.max).toBeGreaterThanOrEqual(1); } }); it('should filter by tag', () => { const encounter = getRandomEncounter({ tags: ['undead'] }); expect(encounter).not.toBeNull(); expect(encounter?.tags.some(t => t.toLowerCase() === 'undead')).toBe(true); }); it('should return null when no matches', () => { const encounter = getRandomEncounter({ difficulty: 'deadly', level: 20, tags: ['nonexistent-tag-12345'] }); expect(encounter).toBeNull(); }); }); describe('specific encounters', () => { it('goblin_ambush should have archers in back', () => { const preset = getEncounterPreset('goblin_ambush')!; const archers = preset.participants.filter(p => p.template.includes('archer')); const warriors = preset.participants.filter(p => p.template.includes('warrior')); expect(archers.length).toBeGreaterThan(0); expect(warriors.length).toBeGreaterThan(0); // Archers should have lower Y (further back) const archerY = archers.map(a => parseInt(a.position.split(',')[1])); const warriorY = warriors.map(w => parseInt(w.position.split(',')[1])); expect(Math.min(...archerY)).toBeLessThan(Math.max(...warriorY)); }); it('tavern_brawl should have tables as obstacles', () => { const preset = getEncounterPreset('tavern_brawl')!; expect(preset.terrain?.obstacles?.length).toBeGreaterThan(0); expect(preset.tags).toContain('tavern'); }); it('troll_bridge should have water terrain', () => { const preset = getEncounterPreset('troll_bridge')!; expect(preset.terrain?.water?.length).toBeGreaterThan(0); expect(preset.participants.length).toBe(1); // Solo troll }); it('dragon_wyrmling_lair should be deadly difficulty', () => { const preset = getEncounterPreset('dragon_wyrmling_lair')!; expect(preset.difficulty).toBe('deadly'); expect(preset.tags).toContain('dragon'); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Mnehmos/rpg-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server