/** * Tests for special ability trigger system from the design document * Tests all the different trigger events and their implementations */ import { describe, it, expect, beforeEach } from 'vitest'; import { BattleEngine } from './BattleEngine'; import { PicletDefinition, Move, SpecialAbility } from './types'; import { PicletType, AttackType } from './types'; const STANDARD_STATS = { hp: 100, attack: 80, defense: 70, speed: 60 }; describe('Special Ability Trigger System - TDD Implementation', () => { describe('Damage Triggers', () => { it('should handle onDamageTaken triggers', () => { const berserkAbility: SpecialAbility = { name: "Berserker", description: "Attack increases when taking damage", triggers: [ { event: 'onDamageTaken', effects: [ { type: 'modifyStats', target: 'self', stats: { attack: 'increase' } } ] } ] }; const berserkerPiclet: PicletDefinition = { name: "Rage Beast", description: "Gets stronger when hurt", tier: 'medium', primaryType: PicletType.BEAST, baseStats: STANDARD_STATS, nature: "Brave", specialAbility: berserkAbility, movepool: [{ name: "Tackle", type: AttackType.NORMAL, power: 40, accuracy: 100, pp: 35, priority: 0, flags: [], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] }] }; expect(berserkAbility.triggers![0].event).toBe('onDamageTaken'); }); it('should handle onDamageDealt triggers', () => { const lifeStealAbility: SpecialAbility = { name: "Life Steal", description: "Heals when dealing damage", triggers: [ { event: 'onDamageDealt', effects: [ { type: 'heal', target: 'self', amount: 'small' } ] } ] }; expect(lifeStealAbility.triggers![0].event).toBe('onDamageDealt'); }); it('should handle onContactDamage triggers', () => { const toxicSkin: SpecialAbility = { name: "Toxic Skin", description: "Physical contact poisons the attacker", triggers: [ { event: 'onContactDamage', effects: [ { type: 'applyStatus', target: 'attacker', status: 'poison', chance: 50 } ] } ] }; expect(toxicSkin.triggers![0].event).toBe('onContactDamage'); expect(toxicSkin.triggers![0].effects[0].target).toBe('attacker'); }); }); describe('Status Triggers', () => { it('should handle onStatusInflicted triggers', () => { const burnBoost: SpecialAbility = { name: "Burn Boost", description: "Fire damage energizes this Piclet, increasing attack power", triggers: [ { event: 'onStatusInflicted', condition: 'ifStatus:burn', effects: [ { type: 'modifyStats', target: 'self', stats: { attack: 'greatly_increase' } } ] } ] }; expect(burnBoost.triggers![0].event).toBe('onStatusInflicted'); expect(burnBoost.triggers![0].condition).toBe('ifStatus:burn'); }); it('should handle onStatusMove triggers', () => { const statusReflect: SpecialAbility = { name: "Status Shield", description: "Reflects status moves back to user", triggers: [ { event: 'onStatusMove', effects: [ { type: 'mechanicOverride', mechanic: 'targetRedirection', value: 'reflect' } ] } ] }; expect(statusReflect.triggers![0].event).toBe('onStatusMove'); }); it('should handle onStatusMoveTargeted triggers', () => { const statusCounter: SpecialAbility = { name: "Status Counter", description: "When targeted by status moves, counter with damage", triggers: [ { event: 'onStatusMoveTargeted', effects: [ { type: 'damage', target: 'attacker', formula: 'fixed', value: 30 } ] } ] }; expect(statusCounter.triggers![0].event).toBe('onStatusMoveTargeted'); }); }); describe('Critical Hit Triggers', () => { it('should handle onCriticalHit triggers', () => { const criticalMomentum: SpecialAbility = { name: "Critical Momentum", description: "Critical hits increase speed", triggers: [ { event: 'onCriticalHit', effects: [ { type: 'modifyStats', target: 'self', stats: { speed: 'increase' } } ] } ] }; expect(criticalMomentum.triggers![0].event).toBe('onCriticalHit'); }); }); describe('HP Drain Triggers', () => { it('should handle onHPDrained triggers', () => { const drainPunish: SpecialAbility = { name: "Drain Punishment", description: "Damages opponents who try to drain HP", triggers: [ { event: 'onHPDrained', effects: [ { type: 'damage', target: 'attacker', formula: 'fixed', value: 25 } ] } ] }; expect(drainPunish.triggers![0].event).toBe('onHPDrained'); }); }); describe('KO Triggers', () => { it('should handle onKO triggers', () => { const koBoost: SpecialAbility = { name: "Victory Rush", description: "Gets stronger after knocking out an opponent", triggers: [ { event: 'onKO', effects: [ { type: 'modifyStats', target: 'self', stats: { attack: 'greatly_increase', speed: 'increase' } } ] } ] }; expect(koBoost.triggers![0].event).toBe('onKO'); }); }); describe('Switch Triggers', () => { it('should handle onSwitchIn triggers', () => { const intimidate: SpecialAbility = { name: "Intimidate", description: "Lowers opponent's attack when entering battle", triggers: [ { event: 'onSwitchIn', effects: [ { type: 'modifyStats', target: 'opponent', stats: { attack: 'decrease' } } ] } ] }; expect(intimidate.triggers![0].event).toBe('onSwitchIn'); expect(intimidate.triggers![0].effects[0].target).toBe('opponent'); }); it('should handle onSwitchOut triggers', () => { const regenerator: SpecialAbility = { name: "Regenerator", description: "Restores HP when switching out", triggers: [ { event: 'onSwitchOut', effects: [ { type: 'heal', target: 'self', amount: 'small' } ] } ] }; expect(regenerator.triggers![0].event).toBe('onSwitchOut'); }); it('should handle conditional switch-in triggers', () => { const stormCaller: SpecialAbility = { name: "Storm Caller", description: "Boosts attack when entering during storm weather", triggers: [ { event: 'onSwitchIn', condition: 'ifWeather:storm', effects: [ { type: 'modifyStats', target: 'self', stats: { attack: 'increase' } } ] } ] }; expect(stormCaller.triggers![0].condition).toBe('ifWeather:storm'); }); }); describe('Weather Triggers', () => { it('should handle onWeatherChange triggers', () => { const weatherAdapt: SpecialAbility = { name: "Weather Adaptation", description: "Adapts stats based on weather changes", triggers: [ { event: 'onWeatherChange', effects: [ { type: 'modifyStats', target: 'self', stats: { speed: 'increase' } } ] } ] }; expect(weatherAdapt.triggers![0].event).toBe('onWeatherChange'); }); }); describe('Move Use Triggers', () => { it('should handle beforeMoveUse triggers', () => { const movePrep: SpecialAbility = { name: "Move Preparation", description: "Boosts accuracy before using moves", triggers: [ { event: 'beforeMoveUse', effects: [ { type: 'modifyStats', target: 'self', stats: { accuracy: 'increase' } } ] } ] }; expect(movePrep.triggers![0].event).toBe('beforeMoveUse'); }); it('should handle afterMoveUse triggers', () => { const moveRecovery: SpecialAbility = { name: "Move Recovery", description: "Heals slightly after using any move", triggers: [ { event: 'afterMoveUse', effects: [ { type: 'heal', target: 'self', formula: 'fixed', value: 5 } ] } ] }; expect(moveRecovery.triggers![0].event).toBe('afterMoveUse'); }); }); describe('HP Threshold Triggers', () => { it('should handle onLowHP triggers', () => { const emergencyMode: SpecialAbility = { name: "Emergency Mode", description: "Activates emergency protocols when HP is low", triggers: [ { event: 'onLowHP', effects: [ { type: 'modifyStats', target: 'self', stats: { speed: 'greatly_increase', attack: 'increase' } }, { type: 'mechanicOverride', mechanic: 'statusImmunity', value: ['burn', 'poison', 'paralyze'] } ] } ] }; expect(emergencyMode.triggers![0].event).toBe('onLowHP'); expect(emergencyMode.triggers![0].effects).toHaveLength(2); }); it('should handle onFullHP triggers', () => { const fullPower: SpecialAbility = { name: "Full Power", description: "Maximum power when at full health", triggers: [ { event: 'onFullHP', effects: [ { type: 'modifyStats', target: 'self', stats: { attack: 'greatly_increase' } } ] } ] }; expect(fullPower.triggers![0].event).toBe('onFullHP'); }); }); describe('Turn-Based Triggers', () => { it('should handle endOfTurn triggers', () => { const turnRegeneration: SpecialAbility = { name: "Slow Regeneration", description: "Heals at the end of each turn", triggers: [ { event: 'endOfTurn', effects: [ { type: 'heal', target: 'self', formula: 'percentage', value: 10 } ] } ] }; expect(turnRegeneration.triggers![0].event).toBe('endOfTurn'); }); it('should handle conditional turn triggers', () => { const sleepHeal: SpecialAbility = { name: "Slumber Heal", description: "Restores HP while sleeping instead of being unable to act", triggers: [ { event: 'endOfTurn', condition: 'ifStatus:sleep', effects: [ { type: 'heal', target: 'self', formula: 'percentage', value: 15 } ] } ] }; expect(sleepHeal.triggers![0].condition).toBe('ifStatus:sleep'); }); }); describe('Opponent Move Triggers', () => { it('should handle onOpponentContactMove triggers', () => { const contactPunish: SpecialAbility = { name: "Contact Punishment", description: "Damages opponents who use contact moves", triggers: [ { event: 'onOpponentContactMove', effects: [ { type: 'damage', target: 'attacker', formula: 'fixed', value: 15 } ] } ] }; expect(contactPunish.triggers![0].event).toBe('onOpponentContactMove'); }); it('should handle wind currents ability from design doc', () => { const windCurrents: SpecialAbility = { name: "Wind Currents", description: "Gains +25% speed when opponent uses a contact move", triggers: [ { event: 'onOpponentContactMove', effects: [ { type: 'modifyStats', target: 'self', stats: { speed: 'increase' } } ] } ] }; const zephyrSprite: PicletDefinition = { name: "Zephyr Sprite", description: "A mysterious floating creature that manipulates wind currents", tier: 'medium', primaryType: PicletType.SPACE, baseStats: { hp: 65, attack: 85, defense: 40, speed: 90 }, nature: "hasty", specialAbility: windCurrents, movepool: [{ name: "Tackle", type: AttackType.NORMAL, power: 40, accuracy: 100, pp: 35, priority: 0, flags: [], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] }] }; expect(windCurrents.triggers![0].event).toBe('onOpponentContactMove'); expect(zephyrSprite.specialAbility.name).toBe('Wind Currents'); }); }); describe('Complex Multi-Trigger Abilities', () => { it('should handle abilities with multiple triggers', () => { const complexAbility: SpecialAbility = { name: "Adaptive Guardian", description: "Complex ability with multiple trigger conditions", triggers: [ { event: 'onSwitchIn', effects: [ { type: 'modifyStats', target: 'self', stats: { defense: 'increase' } } ] }, { event: 'onDamageTaken', condition: 'ifLowHp', effects: [ { type: 'mechanicOverride', mechanic: 'damageReflection', value: 0.3 } ] }, { event: 'endOfTurn', condition: 'ifStatus:burn', effects: [ { type: 'removeStatus', target: 'self', status: 'burn' }, { type: 'modifyStats', target: 'self', stats: { attack: 'increase' } } ] } ] }; expect(complexAbility.triggers).toHaveLength(3); expect(complexAbility.triggers![1].condition).toBe('ifLowHp'); expect(complexAbility.triggers![2].effects).toHaveLength(2); }); }); describe('Status-Specific Ability Examples', () => { it('should handle Glacial Birth - starts battle frozen', () => { const glacialBirth: SpecialAbility = { name: "Glacial Birth", description: "Enters battle in a frozen state but gains defensive bonuses", triggers: [ { event: 'onSwitchIn', effects: [ { type: 'applyStatus', target: 'self', status: 'freeze', chance: 100 }, { type: 'modifyStats', target: 'self', stats: { defense: 'greatly_increase' }, condition: 'whileFrozen' } ] } ] }; expect(glacialBirth.triggers![0].effects[0].status).toBe('freeze'); expect(glacialBirth.triggers![0].effects[1].condition).toBe('whileFrozen'); }); it('should handle Cryogenic Touch - freezes on contact', () => { const cryogenicTouch: SpecialAbility = { name: "Cryogenic Touch", description: "Contact moves have a chance to freeze the attacker", triggers: [ { event: 'onContactDamage', effects: [ { type: 'applyStatus', target: 'attacker', status: 'freeze', chance: 30 } ] } ] }; expect(cryogenicTouch.triggers![0].effects[0].chance).toBe(30); }); it('should handle Paralytic Aura - paralyzes on entry', () => { const paralyticAura: SpecialAbility = { name: "Paralytic Aura", description: "Intimidating presence paralyzes the opponent upon entry", triggers: [ { event: 'onSwitchIn', effects: [ { type: 'applyStatus', target: 'opponent', status: 'paralyze', chance: 75 } ] } ] }; expect(paralyticAura.triggers![0].effects[0].target).toBe('opponent'); expect(paralyticAura.triggers![0].effects[0].chance).toBe(75); }); it('should handle Confusion Clarity - team status removal', () => { const confusionClarity: SpecialAbility = { name: "Confusion Clarity", description: "Clear mind prevents confusion and helps allies focus", effects: [ { type: 'mechanicOverride', mechanic: 'statusImmunity', value: ['confuse'] } ], triggers: [ { event: 'onSwitchIn', effects: [ { type: 'removeStatus', target: 'allies', status: 'confuse' } ] } ] }; expect(confusionClarity.effects![0].value).toContain('confuse'); expect(confusionClarity.triggers![0].effects[0].target).toBe('allies'); }); }); });