c1cc70eeb9
Full rebrand: UI display text, package scope (@ai-tycoon/* -> @token-empire/*), localStorage keys, Docker/CI image paths, database names, and documentation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
181 lines
5.7 KiB
TypeScript
181 lines
5.7 KiB
TypeScript
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> = {}): 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']);
|
|
});
|
|
});
|