feat: recreate Achievements tab with category sections, progress tracking, and hidden achievement logic
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
ACHIEVEMENTS,
|
||||
ACHIEVEMENT_CATEGORY_COLORS,
|
||||
getAchievementsByCategory,
|
||||
isAchievementRevealed,
|
||||
} from '../data/achievements';
|
||||
|
||||
describe('Achievement Definitions', () => {
|
||||
it('should have 24 achievements defined', () => {
|
||||
expect(Object.keys(ACHIEVEMENTS).length).toBe(24);
|
||||
});
|
||||
|
||||
it('should have achievements across 5 categories', () => {
|
||||
const byCategory = getAchievementsByCategory();
|
||||
const categories = Object.keys(byCategory).sort();
|
||||
expect(categories).toEqual(['combat', 'crafting', 'magic', 'progression', 'special']);
|
||||
});
|
||||
|
||||
it('should have valid structure for every achievement', () => {
|
||||
Object.values(ACHIEVEMENTS).forEach((a) => {
|
||||
expect(a.id).toBeTruthy();
|
||||
expect(a.name).toBeTruthy();
|
||||
expect(a.desc).toBeTruthy();
|
||||
expect(a.category).toBeTruthy();
|
||||
expect(a.requirement.type).toBeTruthy();
|
||||
expect(a.requirement.value).toBeGreaterThan(0);
|
||||
// At least one reward key must be present
|
||||
const rewardKeys = Object.keys(a.reward);
|
||||
expect(rewardKeys.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have category colors for all used categories', () => {
|
||||
const byCategory = getAchievementsByCategory();
|
||||
Object.keys(byCategory).forEach((cat) => {
|
||||
expect(ACHIEVEMENT_CATEGORY_COLORS[cat]).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should have hidden flag only on special category achievements', () => {
|
||||
Object.values(ACHIEVEMENTS).forEach((a) => {
|
||||
if (a.hidden) {
|
||||
expect(a.category).toBe('special');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should have exactly 2 hidden achievements', () => {
|
||||
const hidden = Object.values(ACHIEVEMENTS).filter((a) => a.hidden);
|
||||
expect(hidden.length).toBe(2); // speedRunner and perfectionist
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAchievementsByCategory', () => {
|
||||
it('should group achievements correctly', () => {
|
||||
const byCategory = getAchievementsByCategory();
|
||||
// Combat has 6 floor + 3 damage = 9
|
||||
expect(byCategory['combat'].length).toBe(9);
|
||||
// Progression has 3 pacts
|
||||
expect(byCategory['progression'].length).toBe(3);
|
||||
// Magic has 3 spells + 3 mana = 6
|
||||
expect(byCategory['magic'].length).toBe(6);
|
||||
// Crafting has 3
|
||||
expect(byCategory['crafting'].length).toBe(3);
|
||||
// Special has 3 (2 hidden + 1 survivor)
|
||||
expect(byCategory['special'].length).toBe(3);
|
||||
});
|
||||
|
||||
it('should return a new object each call (no mutation)', () => {
|
||||
const result1 = getAchievementsByCategory();
|
||||
const result2 = getAchievementsByCategory();
|
||||
expect(result1).not.toBe(result2);
|
||||
expect(result1).toEqual(result2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAchievementRevealed', () => {
|
||||
it('should always reveal non-hidden achievements', () => {
|
||||
const nonHidden = Object.values(ACHIEVEMENTS).find((a) => !a.hidden);
|
||||
expect(nonHidden).toBeDefined();
|
||||
expect(isAchievementRevealed(nonHidden!, 0)).toBe(true);
|
||||
expect(isAchievementRevealed(nonHidden!, 999)).toBe(true);
|
||||
});
|
||||
|
||||
it('should hide hidden achievements below 50% progress', () => {
|
||||
const hidden = Object.values(ACHIEVEMENTS).find((a) => a.hidden);
|
||||
expect(hidden).toBeDefined();
|
||||
// At 0% progress
|
||||
expect(isAchievementRevealed(hidden!, 0)).toBe(false);
|
||||
// At 25% progress
|
||||
expect(isAchievementRevealed(hidden!, hidden!.requirement.value * 0.25)).toBe(false);
|
||||
// At exactly 49% progress
|
||||
expect(isAchievementRevealed(hidden!, hidden!.requirement.value * 0.49)).toBe(false);
|
||||
});
|
||||
|
||||
it('should reveal hidden achievements at 50% progress', () => {
|
||||
const hidden = Object.values(ACHIEVEMENTS).find((a) => a.hidden);
|
||||
expect(hidden).toBeDefined();
|
||||
// At exactly 50%
|
||||
expect(isAchievementRevealed(hidden!, hidden!.requirement.value * 0.5)).toBe(true);
|
||||
// At 75%
|
||||
expect(isAchievementRevealed(hidden!, hidden!.requirement.value * 0.75)).toBe(true);
|
||||
// At 100%
|
||||
expect(isAchievementRevealed(hidden!, hidden!.requirement.value)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Achievement Rewards', () => {
|
||||
it('should have insight rewards on all achievements', () => {
|
||||
Object.values(ACHIEVEMENTS).forEach((a) => {
|
||||
expect(a.reward.insight).toBeDefined();
|
||||
expect(a.reward.insight).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should scale rewards with difficulty', () => {
|
||||
const firstBlood = ACHIEVEMENTS['firstBlood'];
|
||||
const apexReached = ACHIEVEMENTS['apexReached'];
|
||||
expect(apexReached.reward.insight!).toBeGreaterThan(firstBlood.reward.insight!);
|
||||
});
|
||||
|
||||
it('should have title rewards on top-tier achievements', () => {
|
||||
const topTiers = ['apexReached', 'spireMaster', 'tenThousandDamage', 'spellStorm', 'manaOcean', 'pactMaster', 'legendaryEnchanter'];
|
||||
topTiers.forEach((id) => {
|
||||
expect(ACHIEVEMENTS[id].reward.title).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user