import { describe, it, expect } from 'vitest'; import { processAchievements } from './achievementSystem'; import { createTestState } from '../__test-utils__'; import type { AchievementDefinition } from '@token-empire/shared'; function makeDef(overrides: Partial = {}): AchievementDefinition { return { id: 'ach-1', name: 'First Million', description: 'Earn $1,000', icon: 'money', condition: { field: 'economy.money', operator: 'gte', value: 1000 }, ...overrides, }; } describe('processAchievements', () => { it('returns unchanged state when tickCount is not divisible by 10', () => { const state = createTestState({ meta: { tickCount: 7 }, economy: { money: 999999 }, }); const defs = [makeDef()]; const result = processAchievements(state, defs); expect(result.newAchievements).toEqual([]); expect(result.achievements).toBe(state.achievements); }); it('checks achievements when tickCount % 10 === 0', () => { const state = createTestState({ meta: { tickCount: 10 }, economy: { money: 5000 }, }); const defs = [makeDef({ condition: { field: 'economy.money', operator: 'gte', value: 1000 } })]; const result = processAchievements(state, defs); expect(result.newAchievements).toEqual(['First Million']); expect(result.achievements.unlocked).toHaveLength(1); expect(result.achievements.unlocked[0]).toMatchObject({ id: 'ach-1', unlockedAtTick: 10 }); }); it('does not unlock when gte condition is not met', () => { const state = createTestState({ meta: { tickCount: 10 }, economy: { money: 500 }, }); const defs = [makeDef({ condition: { field: 'economy.money', operator: 'gte', value: 1000 } })]; const result = processAchievements(state, defs); expect(result.newAchievements).toEqual([]); expect(result.achievements.unlocked).toHaveLength(0); }); it('handles gt operator as strictly greater than', () => { const state = createTestState({ meta: { tickCount: 20 }, economy: { money: 1000 }, }); const defs = [makeDef({ condition: { field: 'economy.money', operator: 'gt', value: 1000 } })]; const result = processAchievements(state, defs); expect(result.newAchievements).toEqual([]); const state2 = createTestState({ meta: { tickCount: 20 }, economy: { money: 1001 }, }); const result2 = processAchievements(state2, defs); expect(result2.newAchievements).toEqual(['First Million']); }); it('handles eq operator as exact match', () => { const state = createTestState({ meta: { tickCount: 30 }, economy: { money: 1000 }, }); const defs = [makeDef({ condition: { field: 'economy.money', operator: 'eq', value: 1000 } })]; const result = processAchievements(state, defs); expect(result.newAchievements).toEqual(['First Million']); const state2 = createTestState({ meta: { tickCount: 30 }, economy: { money: 1001 }, }); const result2 = processAchievements(state2, defs); expect(result2.newAchievements).toEqual([]); }); it('does not duplicate already-unlocked achievements', () => { const state = createTestState({ meta: { tickCount: 20 }, economy: { money: 5000 }, achievements: { unlocked: [{ id: 'ach-1', unlockedAtTick: 10 }], progress: {}, }, }); const defs = [makeDef()]; const result = processAchievements(state, defs); expect(result.newAchievements).toEqual([]); expect(result.achievements.unlocked).toHaveLength(1); }); it('can unlock multiple achievements in one tick', () => { const state = createTestState({ meta: { tickCount: 10 }, economy: { money: 5000 }, }); const defs = [ makeDef({ id: 'ach-1', name: 'First K', condition: { field: 'economy.money', operator: 'gte', value: 1000 } }), makeDef({ id: 'ach-2', name: 'Five K', condition: { field: 'economy.money', operator: 'gte', value: 5000 } }), makeDef({ id: 'ach-3', name: 'Ten K', condition: { field: 'economy.money', operator: 'gte', value: 10000 } }), ]; const result = processAchievements(state, defs); expect(result.newAchievements).toEqual(['First K', 'Five K']); expect(result.achievements.unlocked).toHaveLength(2); }); it('resolves meta._eraIndex for era-based achievements', () => { const state = createTestState({ meta: { tickCount: 10, currentEra: 'bigtech' }, }); const defs = [ makeDef({ id: 'era-ach', name: 'Big Tech Era', condition: { field: 'meta._eraIndex', operator: 'gte', value: 2 } }), ]; const result = processAchievements(state, defs); expect(result.newAchievements).toEqual(['Big Tech Era']); }); it('resolves meta._deployedModelCount for deployed model achievements', () => { const state = createTestState({ meta: { tickCount: 10 }, models: { baseModels: [ { isDeployed: true }, { isDeployed: true }, { isDeployed: false }, ] as any, }, }); const defs = [ makeDef({ id: 'model-ach', name: 'Two Models', condition: { field: 'meta._deployedModelCount', operator: 'eq', value: 2 } }), ]; const result = processAchievements(state, defs); expect(result.newAchievements).toEqual(['Two Models']); }); it('resolves nested fields like economy.money', () => { const state = createTestState({ meta: { tickCount: 0 }, economy: { money: 42 }, }); const defs = [ makeDef({ id: 'exact', name: 'Exact 42', condition: { field: 'economy.money', operator: 'eq', value: 42 } }), ]; const result = processAchievements(state, defs); expect(result.newAchievements).toEqual(['Exact 42']); }); });