fix: Use BASE_ELEMENTS constant for debug unlock to prevent unlocking all 22 elements
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s

This commit is contained in:
2026-06-12 09:45:15 +02:00
parent 4b8cdb97d7
commit 7e0e9b9f7c
8 changed files with 149 additions and 8 deletions
@@ -0,0 +1,125 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { useManaStore } from '../stores/manaStore';
import { ELEMENTS, BASE_UNLOCKED_ELEMENTS, BASE_ELEMENTS } from '../constants/elements';
// ─── Regression test: "Unlock All Base Elements" only unlocks 7 base ───────
// Issue #374: Button was unlocking all 22 elements instead of just 7 base.
function resetManaStore() {
useManaStore.setState({
rawMana: 10000,
meditateTicks: 0,
totalManaGathered: 0,
elements: Object.fromEntries(
Object.keys(ELEMENTS).map(k => [
k,
{
current: 0,
max: 10,
baseMax: 10,
unlocked: BASE_UNLOCKED_ELEMENTS.includes(k),
},
])
) as Record<string, { current: number; max: number; baseMax: number; unlocked: boolean }>,
elementRegen: {},
});
}
describe('Unlock All Base Elements debug button', () => {
beforeEach(() => {
resetManaStore();
});
it('BASE_ELEMENTS contains exactly 7 base elements', () => {
expect(BASE_ELEMENTS).toHaveLength(7);
expect(BASE_ELEMENTS).toContain('fire');
expect(BASE_ELEMENTS).toContain('water');
expect(BASE_ELEMENTS).toContain('air');
expect(BASE_ELEMENTS).toContain('earth');
expect(BASE_ELEMENTS).toContain('light');
expect(BASE_ELEMENTS).toContain('dark');
expect(BASE_ELEMENTS).toContain('death');
});
it('BASE_ELEMENTS does NOT contain utility, composite, or exotic elements', () => {
expect(BASE_ELEMENTS).not.toContain('transference');
expect(BASE_ELEMENTS).not.toContain('metal');
expect(BASE_ELEMENTS).not.toContain('sand');
expect(BASE_ELEMENTS).not.toContain('lightning');
expect(BASE_ELEMENTS).not.toContain('frost');
expect(BASE_ELEMENTS).not.toContain('blackflame');
expect(BASE_ELEMENTS).not.toContain('radiantflames');
expect(BASE_ELEMENTS).not.toContain('miasma');
expect(BASE_ELEMENTS).not.toContain('shadowglass');
expect(BASE_ELEMENTS).not.toContain('crystal');
expect(BASE_ELEMENTS).not.toContain('stellar');
expect(BASE_ELEMENTS).not.toContain('void');
expect(BASE_ELEMENTS).not.toContain('soul');
expect(BASE_ELEMENTS).not.toContain('time');
expect(BASE_ELEMENTS).not.toContain('plasma');
});
it('unlockElement for all BASE_ELEMENTS only unlocks those 7', () => {
const store = useManaStore.getState();
// All base elements should start locked (only transference is base-unlocked)
BASE_ELEMENTS.forEach(e => {
expect(store.elements[e]?.unlocked).toBe(false);
});
// Unlock all base elements (cost 0, simulating debug button)
BASE_ELEMENTS.forEach(e => {
store.unlockElement(e, 0);
});
const afterState = useManaStore.getState();
// All 7 base elements should now be unlocked
BASE_ELEMENTS.forEach(e => {
expect(afterState.elements[e]?.unlocked).toBe(true);
});
// Non-base elements should NOT be unlocked
const nonBaseElements = Object.keys(ELEMENTS).filter(
e => !BASE_ELEMENTS.includes(e as never) && !BASE_UNLOCKED_ELEMENTS.includes(e)
);
nonBaseElements.forEach(e => {
expect(afterState.elements[e]?.unlocked).toBe(false);
});
});
it('debug handleUnlockBase does not unlock transference', () => {
const store = useManaStore.getState();
// Verify transference starts unlocked
expect(store.elements['transference']?.unlocked).toBe(true);
// Run the unlock logic
BASE_ELEMENTS.forEach(e => {
if (!store.elements[e]?.unlocked) {
store.unlockElement(e, 0);
}
});
const afterState = useManaStore.getState();
// transference was already unlocked, should still be unlocked
expect(afterState.elements['transference']?.unlocked).toBe(true);
// But no new utility elements were unlocked
expect(afterState.elements['metal']?.unlocked).toBe(false);
});
it('total unlocked count after unlock base is exactly 8 (7 base + transference)', () => {
const store = useManaStore.getState();
const unlockedBefore = Object.values(store.elements).filter(e => e.unlocked).length;
// Only transference should be unlocked initially
expect(unlockedBefore).toBe(1);
BASE_ELEMENTS.forEach(e => {
store.unlockElement(e, 0);
});
const afterState = useManaStore.getState();
const unlockedAfter = Object.values(afterState.elements).filter(e => e.unlocked).length;
expect(unlockedAfter).toBe(8); // 7 base + transference
});
});
+3
View File
@@ -110,3 +110,6 @@ export const ELEMENT_ICON_NAMES: Record<string, string> = {
// ─── Base Unlocked Elements ───────────────────────────────────────────────────
export const BASE_UNLOCKED_ELEMENTS = ['transference'];
// ─── Base Element IDs (7 base elements only — NOT composite/exotic/utility) ──
export const BASE_ELEMENTS = ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'] as const;
+1 -1
View File
@@ -8,7 +8,7 @@ export { getStudySpeedMultiplier, getStudyCostMultiplier } from './core';
// Element-related constants
export { MANA_PER_ELEMENT, rawCost, elemCost, ELEMENTS, FLOOR_ELEM_CYCLE } from './elements';
export { ELEMENT_OPPOSITES, ELEMENT_ICON_NAMES, BASE_UNLOCKED_ELEMENTS } from './elements';
export { ELEMENT_OPPOSITES, ELEMENT_ICON_NAMES, BASE_UNLOCKED_ELEMENTS, BASE_ELEMENTS } from './elements';
// Spell constants
export { SPELLS_DEF } from './spells';